@indigoai-us/hq-cli 5.220.0 → 5.222.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.
Files changed (30) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/command-catalog.generated.d.ts +55 -0
  3. package/dist/command-catalog.generated.js +71 -0
  4. package/dist/commands/core-ontology.d.ts +6 -0
  5. package/dist/commands/core-ontology.js +61 -5
  6. package/dist/commands/lanes-checks.d.ts +36 -2
  7. package/dist/commands/lanes-checks.js +67 -9
  8. package/dist/commands/lanes-report.d.ts +11 -0
  9. package/dist/commands/lanes-report.js +330 -0
  10. package/dist/commands/lanes.d.ts +2 -1
  11. package/dist/commands/lanes.js +24 -6
  12. package/dist/lib/checkpoint-ontology/aliases.d.ts +17 -0
  13. package/dist/lib/checkpoint-ontology/aliases.js +44 -0
  14. package/dist/lib/checkpoint-ontology/apply.d.ts +10 -0
  15. package/dist/lib/checkpoint-ontology/apply.js +61 -44
  16. package/dist/lib/checkpoint-ontology/entity-file.d.ts +2 -0
  17. package/dist/lib/checkpoint-ontology/entity-file.js +2 -1
  18. package/dist/lib/checkpoint-ontology/multi-writer.d.ts +106 -0
  19. package/dist/lib/checkpoint-ontology/multi-writer.js +391 -0
  20. package/dist/lib/checkpoint-ontology/plan.d.ts +10 -0
  21. package/dist/lib/checkpoint-ontology/plan.js +23 -5
  22. package/dist/lib/checkpoint-ontology/tree.d.ts +2 -0
  23. package/dist/lib/checkpoint-ontology/tree.js +2 -1
  24. package/dist/lib/lanes/timestamp.d.ts +3 -0
  25. package/dist/lib/lanes/timestamp.js +26 -0
  26. package/dist/lib/lanes/tracker-report-state.d.ts +10 -0
  27. package/dist/lib/lanes/tracker-report-state.js +124 -0
  28. package/dist/lib/lanes/tracker-report.d.ts +203 -0
  29. package/dist/lib/lanes/tracker-report.js +522 -0
  30. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.222.0] — 2026-09-26
6
+
7
+ - Checkpoint ontology, behind `checkpoint.ontology.multi-writer` (off by
8
+ default): entity versions that sync set aside after losing a push race are
9
+ merged back at the start of each plan (aliases, back-references, the higher
10
+ `signal_count`, the later `last_updated`). Any other difference keeps both
11
+ and leaves the set-aside copy, and an entity deleted since is not
12
+ re-created. Every entity file the step writes is recorded in
13
+ `.hq/ontology-outbox/<company>.json`. The apply report counts duplicate
14
+ entity groups, and `hq core ontology duplicates --company <slug> [--json]`
15
+ lists them.
16
+
17
+ ## [5.221.0] — 2026-09-26
18
+
19
+ ### Added
20
+
21
+ - `hq lanes report` prints the company-scoped owner report and `hq lanes team
22
+ <slug>` shows OKRs, KPI trends, work, blockers, and questions. Successful
23
+ reports save their per-company report time for the next report and team page.
24
+ PR, release, and deployment references on stories may be strings, integers,
25
+ or objects. Only timestamped objects count as activity. Invalid rows appear
26
+ under their team's errors, set exit status 2, and leave the saved report time
27
+ unchanged.
28
+
5
29
  ## [5.220.0] — 2026-09-26
6
30
 
7
31
  ### Fixed
@@ -5124,6 +5124,24 @@ export declare const COMMAND_CATALOG: readonly [{
5124
5124
  readonly description: "HQ installation to operate on";
5125
5125
  }];
5126
5126
  readonly subcommands: readonly [];
5127
+ }, {
5128
+ readonly name: "duplicates";
5129
+ readonly description: "List same-type entities whose names collide, with a proposed survivor (read-only)";
5130
+ readonly aliases: readonly [];
5131
+ readonly hidden: false;
5132
+ readonly usage: "[options]";
5133
+ readonly arguments: readonly [];
5134
+ readonly options: readonly [{
5135
+ readonly flags: "--company <slug>";
5136
+ readonly description: "company to scan";
5137
+ }, {
5138
+ readonly flags: "--json";
5139
+ readonly description: "print JSON";
5140
+ }, {
5141
+ readonly flags: "--hq-root <path>";
5142
+ readonly description: "HQ installation to operate on";
5143
+ }];
5144
+ readonly subcommands: readonly [];
5127
5145
  }, {
5128
5146
  readonly name: "apply";
5129
5147
  readonly description: "Validate the run's entity candidates and write them to the payload company";
@@ -7257,6 +7275,43 @@ export declare const COMMAND_CATALOG: readonly [{
7257
7275
  readonly description: "JSON on stdout (always)";
7258
7276
  }];
7259
7277
  readonly subcommands: readonly [];
7278
+ }, {
7279
+ readonly name: "report";
7280
+ readonly description: "Generate the owner report for tracker teams";
7281
+ readonly aliases: readonly [];
7282
+ readonly hidden: false;
7283
+ readonly usage: "[options]";
7284
+ readonly arguments: readonly [];
7285
+ readonly options: readonly [{
7286
+ readonly flags: "--company <slug>";
7287
+ readonly description: "Select the company by slug";
7288
+ }, {
7289
+ readonly flags: "--since <iso>";
7290
+ readonly description: "Report activity from this ISO timestamp";
7291
+ }, {
7292
+ readonly flags: "--json";
7293
+ readonly description: "Emit JSON instead of the owner report text";
7294
+ }];
7295
+ readonly subcommands: readonly [];
7296
+ }, {
7297
+ readonly name: "team";
7298
+ readonly description: "Show a team's OKRs, KPIs, work, blockers, and questions";
7299
+ readonly aliases: readonly [];
7300
+ readonly hidden: false;
7301
+ readonly usage: "[options] <slug>";
7302
+ readonly arguments: readonly [{
7303
+ readonly name: "slug";
7304
+ readonly required: true;
7305
+ readonly variadic: false;
7306
+ }];
7307
+ readonly options: readonly [{
7308
+ readonly flags: "--company <slug>";
7309
+ readonly description: "Select the company by slug";
7310
+ }, {
7311
+ readonly flags: "--json";
7312
+ readonly description: "Emit JSON instead of the team page text";
7313
+ }];
7314
+ readonly subcommands: readonly [];
7260
7315
  }, {
7261
7316
  readonly name: "outpost-pruner";
7262
7317
  readonly description: "Inspect, install, and run safe outpost worktree pruning";
@@ -6626,6 +6626,29 @@ export const COMMAND_CATALOG = [
6626
6626
  ],
6627
6627
  "subcommands": []
6628
6628
  },
6629
+ {
6630
+ "name": "duplicates",
6631
+ "description": "List same-type entities whose names collide, with a proposed survivor (read-only)",
6632
+ "aliases": [],
6633
+ "hidden": false,
6634
+ "usage": "[options]",
6635
+ "arguments": [],
6636
+ "options": [
6637
+ {
6638
+ "flags": "--company <slug>",
6639
+ "description": "company to scan"
6640
+ },
6641
+ {
6642
+ "flags": "--json",
6643
+ "description": "print JSON"
6644
+ },
6645
+ {
6646
+ "flags": "--hq-root <path>",
6647
+ "description": "HQ installation to operate on"
6648
+ }
6649
+ ],
6650
+ "subcommands": []
6651
+ },
6629
6652
  {
6630
6653
  "name": "apply",
6631
6654
  "description": "Validate the run's entity candidates and write them to the payload company",
@@ -9381,6 +9404,54 @@ export const COMMAND_CATALOG = [
9381
9404
  ],
9382
9405
  "subcommands": []
9383
9406
  },
9407
+ {
9408
+ "name": "report",
9409
+ "description": "Generate the owner report for tracker teams",
9410
+ "aliases": [],
9411
+ "hidden": false,
9412
+ "usage": "[options]",
9413
+ "arguments": [],
9414
+ "options": [
9415
+ {
9416
+ "flags": "--company <slug>",
9417
+ "description": "Select the company by slug"
9418
+ },
9419
+ {
9420
+ "flags": "--since <iso>",
9421
+ "description": "Report activity from this ISO timestamp"
9422
+ },
9423
+ {
9424
+ "flags": "--json",
9425
+ "description": "Emit JSON instead of the owner report text"
9426
+ }
9427
+ ],
9428
+ "subcommands": []
9429
+ },
9430
+ {
9431
+ "name": "team",
9432
+ "description": "Show a team's OKRs, KPIs, work, blockers, and questions",
9433
+ "aliases": [],
9434
+ "hidden": false,
9435
+ "usage": "[options] <slug>",
9436
+ "arguments": [
9437
+ {
9438
+ "name": "slug",
9439
+ "required": true,
9440
+ "variadic": false
9441
+ }
9442
+ ],
9443
+ "options": [
9444
+ {
9445
+ "flags": "--company <slug>",
9446
+ "description": "Select the company by slug"
9447
+ },
9448
+ {
9449
+ "flags": "--json",
9450
+ "description": "Emit JSON instead of the team page text"
9451
+ }
9452
+ ],
9453
+ "subcommands": []
9454
+ },
9384
9455
  {
9385
9456
  "name": "outpost-pruner",
9386
9457
  "description": "Inspect, install, and run safe outpost worktree pruning",
@@ -18,5 +18,11 @@ export interface OntologyCommandDeps {
18
18
  }
19
19
  export declare function runOntologyPlan(hqRoot: string, runDirOption: string | undefined, deps?: OntologyCommandDeps): Promise<string>;
20
20
  export declare function runOntologyApply(hqRoot: string, runDirOption: string | undefined, deps?: OntologyCommandDeps): string;
21
+ /**
22
+ * `hq core ontology duplicates`: every group of same-type entities whose
23
+ * names collide, with a proposed survivor. Read-only; gated by
24
+ * `checkpoint.ontology.multi-writer`.
25
+ */
26
+ export declare function runOntologyDuplicates(hqRoot: string, slug: string | undefined, json: boolean, deps?: OntologyCommandDeps): Promise<string>;
21
27
  export declare function registerCoreOntologyCommand(core: Command): void;
22
28
  //# sourceMappingURL=core-ontology.d.ts.map
@@ -11,9 +11,11 @@ import * as fs from "node:fs";
11
11
  import * as path from "node:path";
12
12
  import { resolveLiveRoot } from "../utils/hq-roots.js";
13
13
  import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
14
- import { planOntologyRun } from "../lib/checkpoint-ontology/plan.js";
14
+ import { MULTI_WRITER_FLAG, planOntologyRun, } from "../lib/checkpoint-ontology/plan.js";
15
15
  import { applyOntologyRun, ApplyError } from "../lib/checkpoint-ontology/apply.js";
16
- import { WriteBoundsError } from "../lib/checkpoint-ontology/company.js";
16
+ import { resolveOntologyCompany, WriteBoundsError } from "../lib/checkpoint-ontology/company.js";
17
+ import { findDuplicateGroups, summarizeMergeBack, } from "../lib/checkpoint-ontology/multi-writer.js";
18
+ import { buildEntityIndex } from "../lib/checkpoint-ontology/tree.js";
17
19
  export const ONTOLOGY_PAYLOAD_FILE = "payload.json";
18
20
  class OntologyUsageError extends Error {
19
21
  }
@@ -49,6 +51,16 @@ function companyBriefSuffix(brief) {
49
51
  return "";
50
52
  return `; company brief ${brief.status}${brief.reason ? ` (${brief.reason})` : ""}`;
51
53
  }
54
+ function multiWriterSuffix(start) {
55
+ if (!start)
56
+ return "";
57
+ const parts = [];
58
+ if (start.merge_back.length > 0)
59
+ parts.push(`set-aside versions: ${summarizeMergeBack(start.merge_back)}`);
60
+ if (start.error)
61
+ parts.push(`multi-writer error ${start.error}`);
62
+ return parts.length > 0 ? `; ${parts.join("; ")}` : "";
63
+ }
52
64
  export async function runOntologyPlan(hqRoot, runDirOption, deps = {}) {
53
65
  const runDir = requireRunDir(runDirOption);
54
66
  const outcome = await planOntologyRun({
@@ -59,12 +71,13 @@ export async function runOntologyPlan(hqRoot, runDirOption, deps = {}) {
59
71
  now: deps.now?.(),
60
72
  });
61
73
  if (outcome.status === "skipped") {
62
- return `ontology: skipped (${outcome.reason})${companyBriefSuffix(outcome.companyBrief)}`;
74
+ return `ontology: skipped (${outcome.reason})${companyBriefSuffix(outcome.companyBrief)}${multiWriterSuffix(outcome.multiWriter)}`;
63
75
  }
64
76
  const { plan } = outcome;
65
77
  return (`ontology: planned ${plan.inputs.length} input(s), ~${plan.budget.planned_tokens} tokens, ` +
66
78
  `${plan.pending_after_run} left for later runs; plan at ${outcome.planPath}` +
67
- companyBriefSuffix(outcome.companyBrief));
79
+ companyBriefSuffix(outcome.companyBrief) +
80
+ multiWriterSuffix(plan.multi_writer));
68
81
  }
69
82
  export function runOntologyApply(hqRoot, runDirOption, deps = {}) {
70
83
  const runDir = requireRunDir(runDirOption);
@@ -78,7 +91,36 @@ export function runOntologyApply(hqRoot, runDirOption, deps = {}) {
78
91
  return (`ontology: ${report.created.length} created, ${report.updated.length} updated, ` +
79
92
  `${report.matched} matched, ${report.rejected.length} rejected, ${report.deferred.length} deferred, ` +
80
93
  `${report.suspect_duplicates.length} suspect duplicate(s), ${report.write_errors.length} write error(s)` +
81
- companyBriefSuffix(report.company_brief));
94
+ companyBriefSuffix(report.company_brief) +
95
+ (report.multi_writer?.duplicates ? `; ${report.multi_writer.duplicates.groups} duplicate group(s) in the company` : "") +
96
+ (report.multi_writer?.error ? `; multi-writer error ${report.multi_writer.error}` : ""));
97
+ }
98
+ /**
99
+ * `hq core ontology duplicates`: every group of same-type entities whose
100
+ * names collide, with a proposed survivor. Read-only; gated by
101
+ * `checkpoint.ontology.multi-writer`.
102
+ */
103
+ export async function runOntologyDuplicates(hqRoot, slug, json, deps = {}) {
104
+ const resolution = resolveOntologyCompany(hqRoot, slug);
105
+ if (!resolution.ok)
106
+ throw new OntologyUsageError(`ontology: company rejected: ${resolution.reason}`);
107
+ const company = resolution.company;
108
+ if (!(await (deps.readFlag ?? registryFlagReader)(MULTI_WRITER_FLAG, company))) {
109
+ return json ? JSON.stringify({ company: company.slug, skipped: "flag off" }) : `ontology duplicates: skipped (${MULTI_WRITER_FLAG} is off)`;
110
+ }
111
+ const groups = findDuplicateGroups(buildEntityIndex(company.dir));
112
+ if (json)
113
+ return JSON.stringify({ company: company.slug, groups }, null, 2);
114
+ if (groups.length === 0)
115
+ return `ontology duplicates: none in ${company.slug}`;
116
+ const lines = [`ontology duplicates: ${groups.length} group(s) in ${company.slug}`];
117
+ for (const group of groups) {
118
+ lines.push(`${group.type} (${group.reason.replaceAll("_", " ")}), keep ${group.survivor}`);
119
+ for (const rel of group.paths)
120
+ if (rel !== group.survivor)
121
+ lines.push(` ${rel}`);
122
+ }
123
+ return lines.join("\n");
82
124
  }
83
125
  function fail(error) {
84
126
  if (error instanceof OntologyUsageError || error instanceof ApplyError || error instanceof WriteBoundsError) {
@@ -105,6 +147,20 @@ export function registerCoreOntologyCommand(core) {
105
147
  fail(error);
106
148
  }
107
149
  });
150
+ ontology
151
+ .command("duplicates")
152
+ .description("List same-type entities whose names collide, with a proposed survivor (read-only)")
153
+ .requiredOption("--company <slug>", "company to scan")
154
+ .option("--json", "print JSON")
155
+ .option("--hq-root <path>", "HQ installation to operate on")
156
+ .action(async (options) => {
157
+ try {
158
+ process.stdout.write(`${await runOntologyDuplicates(rootFor(options), options.company, options.json === true)}\n`);
159
+ }
160
+ catch (error) {
161
+ fail(error);
162
+ }
163
+ });
108
164
  ontology
109
165
  .command("apply")
110
166
  .description("Validate the run's entity candidates and write them to the payload company")
@@ -1,6 +1,7 @@
1
1
  import type { Command } from "commander";
2
- import type { LaneCompanyRef } from "../lib/lanes/types.js";
3
- interface LaneChecksDeps {
2
+ import { type TrackerKind, type TrackerRecord } from "../lib/lanes/tracker-store.js";
3
+ import type { LaneCompanyRef, QuestionRecord } from "../lib/lanes/types.js";
4
+ export interface LaneChecksDeps {
4
5
  hqRoot: () => string;
5
6
  resolveCompany: (company?: string) => Promise<LaneCompanyRef>;
6
7
  trackerEnabled: (company: LaneCompanyRef) => boolean | Promise<boolean>;
@@ -8,6 +9,39 @@ interface LaneChecksDeps {
8
9
  now: () => Date;
9
10
  }
10
11
  type WrapHandler = <Args extends unknown[]>(action: (...args: Args) => Promise<void>) => (...args: Args) => Promise<void>;
12
+ export interface ProjectMapping {
13
+ projectId: string;
14
+ team: string;
15
+ leadLaneId?: string;
16
+ statusPath: string;
17
+ }
18
+ export interface ProjectReadResult {
19
+ stories: Array<Record<string, unknown>>;
20
+ mtime: number;
21
+ errors?: Array<{
22
+ path: string;
23
+ field: string;
24
+ message: string;
25
+ story_id?: string;
26
+ }>;
27
+ }
28
+ export declare function loadProjectMappings(root: string, company: string, requestedProject?: string): Map<string, ProjectMapping>;
29
+ export declare function readProject(root: string, company: string, prdPath: string, tolerantStories?: boolean): ProjectReadResult;
30
+ export declare function loadCompanyQuestionRecords(root: string, company: LaneCompanyRef, projectId?: string): QuestionRecord[];
31
+ export declare function pathForTrackerRecord(projectDirectory: string, kind: TrackerKind, id: string): string;
32
+ export declare function listTrackerRecords(projectDirectory: string, kind: TrackerKind): TrackerRecord[];
33
+ export interface TolerantTrackerReadIssue {
34
+ path: string;
35
+ field: string;
36
+ message: string;
37
+ record_id?: string;
38
+ }
39
+ /** Report loader that preserves valid records while making every unreadable file visible. */
40
+ export declare function listTrackerRecordsTolerant(projectDirectory: string, kind: TrackerKind): {
41
+ records: TrackerRecord[];
42
+ errors: TolerantTrackerReadIssue[];
43
+ };
44
+ export declare function resolveTrackerCompany(deps: LaneChecksDeps, requested?: string): Promise<LaneCompanyRef>;
11
45
  export declare function registerLaneChecksCommand(parent: Command, deps: LaneChecksDeps, wrap: WrapHandler): void;
12
46
  export {};
13
47
  //# sourceMappingURL=lanes-checks.d.ts.map
@@ -171,7 +171,7 @@ function projectHasTrackerRecords(root, company, projectId) {
171
171
  }
172
172
  return false;
173
173
  }
174
- function loadProjectMappings(root, company, requestedProject) {
174
+ export function loadProjectMappings(root, company, requestedProject) {
175
175
  const registryPath = path.join(root, "workspace", "lane-briefs", "juniors.json");
176
176
  const registry = readJsonObject(registryPath, "empty");
177
177
  const mappings = new Map();
@@ -215,7 +215,7 @@ function loadProjectMappings(root, company, requestedProject) {
215
215
  }
216
216
  return mappings;
217
217
  }
218
- function readProject(root, company, prdPath) {
218
+ export function readProject(root, company, prdPath, tolerantStories = false) {
219
219
  checkCompanyPath(root, company, path.relative(root, prdPath), prdPath);
220
220
  const parsed = readJsonObject(prdPath);
221
221
  if (parsed === null)
@@ -236,17 +236,26 @@ function readProject(root, company, prdPath) {
236
236
  const rawStories = parsed.userStories === undefined ? parsed.stories : parsed.userStories;
237
237
  if (!Array.isArray(rawStories))
238
238
  return fail("tracker_checks_input_invalid", "Project task list must contain a userStories or stories array", { path: prdPath });
239
- const stories = rawStories.map((story, index) => {
239
+ const errors = [];
240
+ const stories = rawStories.flatMap((story, index) => {
240
241
  if (!isObject(story) || typeof story.id !== "string" || story.id.trim() === "") {
242
+ if (tolerantStories) {
243
+ errors.push({ path: prdPath, field: `userStories[${index}]`, message: "Project task list contains a story without an id" });
244
+ return [];
245
+ }
241
246
  return fail("tracker_checks_input_invalid", "Project task list contains a story without an id", { path: prdPath, index });
242
247
  }
243
248
  if (story.prs !== undefined && !Array.isArray(story.prs)) {
249
+ if (tolerantStories) {
250
+ errors.push({ path: prdPath, field: "prs", message: "Project story prs must be an array", story_id: story.id });
251
+ return [{ ...story, prs: [] }];
252
+ }
244
253
  return fail("tracker_checks_input_invalid", "Project story prs must be an array", { path: prdPath, story_id: story.id });
245
254
  }
246
- return story;
255
+ return [story];
247
256
  });
248
257
  try {
249
- return { stories, mtime: fs.statSync(prdPath).mtimeMs };
258
+ return { stories, mtime: fs.statSync(prdPath).mtimeMs, ...(tolerantStories ? { errors } : {}) };
250
259
  }
251
260
  catch (err) {
252
261
  return fail("tracker_checks_input_unreadable", `Project task list could not be stat'ed: ${err instanceof Error ? err.message : String(err)}`, { path: prdPath });
@@ -352,7 +361,7 @@ function questionMetadataMatchesScope(record, company, projectId) {
352
361
  return false;
353
362
  return true;
354
363
  }
355
- function readCompanyQuestions(root, company, projectId) {
364
+ export function loadCompanyQuestionRecords(root, company, projectId) {
356
365
  const records = listQuestionIds(root)
357
366
  .flatMap((id) => {
358
367
  const file = questionRecordPath(root, id);
@@ -365,6 +374,10 @@ function readCompanyQuestions(root, company, projectId) {
365
374
  return question.company?.slug === company.slug && question.company.uid === company.uid &&
366
375
  (projectId === undefined || question.project_id === projectId) ? [question] : [];
367
376
  });
377
+ return records;
378
+ }
379
+ function readCompanyQuestions(root, company, projectId) {
380
+ const records = loadCompanyQuestionRecords(root, company, projectId);
368
381
  const surfacedAt = questionSurfacingState(root, records.map((question) => ({
369
382
  id: question.question_id,
370
383
  status: question.status,
@@ -418,7 +431,7 @@ function readTeamInputs(root, company, projects, lanes) {
418
431
  }
419
432
  return inputs;
420
433
  }
421
- function pathForTrackerRecord(projectDirectory, kind, id) {
434
+ export function pathForTrackerRecord(projectDirectory, kind, id) {
422
435
  return path.join(projectDirectory, "tracker", kind, `${id}.json`);
423
436
  }
424
437
  function recordInvalid(kind, record, projectDirectory, field) {
@@ -435,7 +448,7 @@ function requiredRecordDate(kind, record, projectDirectory, field) {
435
448
  return recordInvalid(kind, record, projectDirectory, field);
436
449
  return value;
437
450
  }
438
- function listTrackerRecords(projectDirectory, kind) {
451
+ export function listTrackerRecords(projectDirectory, kind) {
439
452
  try {
440
453
  return createLocalTrackerStore(projectDirectory).list(kind);
441
454
  }
@@ -445,6 +458,51 @@ function listTrackerRecords(projectDirectory, kind) {
445
458
  return fail("tracker_checks_store_unreadable", `Tracker ${kind} records could not be read: ${err instanceof Error ? err.message : String(err)}`, { path: path.join(projectDirectory, "tracker", kind) });
446
459
  }
447
460
  }
461
+ /** Report loader that preserves valid records while making every unreadable file visible. */
462
+ export function listTrackerRecordsTolerant(projectDirectory, kind) {
463
+ const root = path.join(projectDirectory, "tracker");
464
+ const directory = path.join(projectDirectory, "tracker", kind);
465
+ let names;
466
+ try {
467
+ const rootStat = fs.lstatSync(root);
468
+ if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
469
+ return { records: [], errors: [{ path: root, field: "directory", message: "Tracker storage path must be a regular directory" }] };
470
+ }
471
+ const stat = fs.lstatSync(directory);
472
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
473
+ return { records: [], errors: [{ path: directory, field: "directory", message: "Tracker record path must be a regular directory" }] };
474
+ }
475
+ names = fs.readdirSync(directory).filter((name) => name.endsWith(".json")).sort();
476
+ }
477
+ catch (err) {
478
+ if (err.code === "ENOENT")
479
+ return { records: [], errors: [] };
480
+ return { records: [], errors: [{ path: directory, field: "directory", message: `Tracker records could not be listed: ${err instanceof Error ? err.message : String(err)}` }] };
481
+ }
482
+ const store = createLocalTrackerStore(projectDirectory);
483
+ const records = [];
484
+ const errors = [];
485
+ for (const name of names) {
486
+ const id = name.slice(0, -5);
487
+ const file = path.join(directory, name);
488
+ try {
489
+ const record = store.read(kind, id);
490
+ if (record !== null)
491
+ records.push(record);
492
+ }
493
+ catch (err) {
494
+ const details = isLanesError(err) ? err.details : {};
495
+ errors.push({
496
+ path: typeof details.path === "string" ? details.path : file,
497
+ field: typeof details.field === "string" ? details.field : "record",
498
+ message: err instanceof Error ? err.message : String(err),
499
+ record_id: typeof details.id === "string" ? details.id : id,
500
+ });
501
+ }
502
+ }
503
+ records.sort((a, b) => b.updated_at.localeCompare(a.updated_at) || a.id.localeCompare(b.id));
504
+ return { records, errors };
505
+ }
448
506
  function readTrackerCheckRecords(root, company, projects) {
449
507
  const blockers = [];
450
508
  const kpis = [];
@@ -499,7 +557,7 @@ function readTrackerCheckRecords(root, company, projects) {
499
557
  }
500
558
  return { blockers, kpis, directives };
501
559
  }
502
- async function resolveTrackerCompany(deps, requested) {
560
+ export async function resolveTrackerCompany(deps, requested) {
503
561
  const company = await deps.resolveCompany(requested);
504
562
  if (!company.slug)
505
563
  fail("tracker_company_unresolved", "The active HQ company could not be resolved", {});
@@ -0,0 +1,11 @@
1
+ import type { Command } from "commander";
2
+ import { type QuestionBody } from "../lib/lanes/questions.js";
3
+ import type { QuestionRecord } from "../lib/lanes/types.js";
4
+ import { type LaneChecksDeps } from "./lanes-checks.js";
5
+ type WrapHandler = <Args extends unknown[]>(action: (...args: Args) => Promise<void>) => (...args: Args) => Promise<void>;
6
+ export interface LaneReportsDeps extends LaneChecksDeps {
7
+ readQuestionBody: (root: string, question: QuestionRecord) => QuestionBody;
8
+ }
9
+ export declare function registerLaneReportCommands(parent: Command, deps: LaneReportsDeps, wrap: WrapHandler): void;
10
+ export {};
11
+ //# sourceMappingURL=lanes-report.d.ts.map