@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
@@ -0,0 +1,124 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ import { LanesError } from "./errors.js";
5
+ import { normalizeIsoTimestamp } from "./timestamp.js";
6
+ export const FIRST_TRACKER_REPORT_SINCE = "1970-01-01T00:00:00.000Z";
7
+ export function trackerReportStatePath(root, company) {
8
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/.test(company) || company === "." || company === "..") {
9
+ throw new LanesError("tracker_report_company_invalid", "Company slug must be a safe path segment", { company });
10
+ }
11
+ return path.join(root, "workspace", "lane-briefs", "tracker-report-state", `${company}.json`);
12
+ }
13
+ function stateError(error, message, file) {
14
+ throw new LanesError(error, `${message}: ${file}`, { path: file });
15
+ }
16
+ function validReportTime(value, file) {
17
+ const normalized = normalizeIsoTimestamp(value);
18
+ if (normalized === null) {
19
+ return stateError("tracker_report_state_invalid", "Report state has an invalid last_report_at", file);
20
+ }
21
+ return normalized;
22
+ }
23
+ export function readTrackerReportState(root, company) {
24
+ const file = trackerReportStatePath(root, company);
25
+ if (stateDirectory(root, false) === null)
26
+ return null;
27
+ let raw;
28
+ try {
29
+ const stat = fs.lstatSync(file);
30
+ if (!stat.isFile() || stat.isSymbolicLink()) {
31
+ return stateError("tracker_report_state_invalid", "Report state path must be a regular file", file);
32
+ }
33
+ raw = fs.readFileSync(file, "utf8");
34
+ }
35
+ catch (err) {
36
+ if (err.code === "ENOENT")
37
+ return null;
38
+ if (err instanceof LanesError)
39
+ throw err;
40
+ return stateError("tracker_report_state_unreadable", `Report state could not be read: ${err instanceof Error ? err.message : String(err)}`, file);
41
+ }
42
+ let value;
43
+ try {
44
+ value = JSON.parse(raw);
45
+ }
46
+ catch {
47
+ return stateError("tracker_report_state_malformed", "Report state contains invalid JSON", file);
48
+ }
49
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
50
+ return stateError("tracker_report_state_invalid", "Report state must be an object", file);
51
+ }
52
+ const record = value;
53
+ if (record.schema_version !== "hq-lanes-report-state.v1" || record.company !== company) {
54
+ return stateError("tracker_report_state_invalid", "Report state schema or company does not match", file);
55
+ }
56
+ return {
57
+ schema_version: "hq-lanes-report-state.v1",
58
+ company,
59
+ last_report_at: validReportTime(record.last_report_at, file),
60
+ };
61
+ }
62
+ function stateDirectory(root, create) {
63
+ let rootReal;
64
+ try {
65
+ rootReal = fs.realpathSync(root);
66
+ }
67
+ catch (err) {
68
+ return stateError("tracker_report_state_root_invalid", `HQ root could not be resolved: ${err instanceof Error ? err.message : String(err)}`, root);
69
+ }
70
+ let current = rootReal;
71
+ for (const segment of ["workspace", "lane-briefs", "tracker-report-state"]) {
72
+ current = path.join(current, segment);
73
+ let stat;
74
+ try {
75
+ stat = fs.lstatSync(current);
76
+ }
77
+ catch (err) {
78
+ if (err.code === "ENOENT" && !create)
79
+ return null;
80
+ if (err.code !== "ENOENT") {
81
+ return stateError("tracker_report_state_path_unreadable", `Report state directory could not be inspected: ${err instanceof Error ? err.message : String(err)}`, current);
82
+ }
83
+ try {
84
+ fs.mkdirSync(current, { mode: 0o700 });
85
+ }
86
+ catch (mkdirError) {
87
+ return stateError("tracker_report_state_path_unwritable", `Report state directory could not be created: ${mkdirError instanceof Error ? mkdirError.message : String(mkdirError)}`, current);
88
+ }
89
+ stat = fs.lstatSync(current);
90
+ }
91
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
92
+ return stateError("tracker_report_state_path_invalid", "Report state directory must be a real directory", current);
93
+ }
94
+ }
95
+ return current;
96
+ }
97
+ export function writeTrackerReportState(root, company, at) {
98
+ const canonicalAt = validReportTime(at, trackerReportStatePath(root, company));
99
+ const directory = stateDirectory(root, true);
100
+ if (directory === null)
101
+ return stateError("tracker_report_state_path_unavailable", "Report state directory is unavailable", trackerReportStatePath(root, company));
102
+ const file = trackerReportStatePath(root, company);
103
+ const temp = path.join(directory, `.${company}.${randomUUID()}.tmp`);
104
+ const value = { schema_version: "hq-lanes-report-state.v1", company, last_report_at: canonicalAt };
105
+ try {
106
+ fs.writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
107
+ fs.renameSync(temp, file);
108
+ }
109
+ catch (err) {
110
+ try {
111
+ fs.unlinkSync(temp);
112
+ }
113
+ catch (cleanupError) {
114
+ if (cleanupError.code !== "ENOENT") {
115
+ throw new LanesError("tracker_report_state_cleanup_failed", `Report state temporary file could not be removed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`, { path: temp });
116
+ }
117
+ }
118
+ if (err instanceof LanesError)
119
+ throw err;
120
+ return stateError("tracker_report_state_write_failed", `Report state could not be written: ${err instanceof Error ? err.message : String(err)}`, file);
121
+ }
122
+ return value;
123
+ }
124
+ //# sourceMappingURL=tracker-report-state.js.map
@@ -0,0 +1,203 @@
1
+ import type { QuestionBody } from "./questions.js";
2
+ import type { QuestionRecord } from "./types.js";
3
+ import type { TrackerRecord } from "./tracker-store.js";
4
+ export type ReportStoryStatus = "queued" | "in_progress" | "review" | "done" | "dropped";
5
+ export type ReportAudience = "owner" | "operator" | "lane";
6
+ export type ApprovalState = "proposal" | "approved";
7
+ export interface TrackerReportRecord extends TrackerRecord {
8
+ /** Absolute path of the validated on-disk record; used only in errors. */
9
+ source_path: string;
10
+ }
11
+ export interface TrackerReportStory {
12
+ id: string;
13
+ title: string;
14
+ status: ReportStoryStatus;
15
+ references?: string[];
16
+ updated_at?: string;
17
+ source_path: string;
18
+ }
19
+ export interface TrackerReportIssue {
20
+ path: string;
21
+ field: string;
22
+ message: string;
23
+ story_id?: string;
24
+ record_id?: string;
25
+ question_id?: string;
26
+ }
27
+ export interface TrackerReportPullRequest {
28
+ number: string;
29
+ title: string;
30
+ merged_at: string;
31
+ source_path: string;
32
+ }
33
+ export interface TrackerReportRelease {
34
+ version: string;
35
+ released_at: string;
36
+ source_path: string;
37
+ }
38
+ export interface TrackerReportQuestion {
39
+ record: QuestionRecord;
40
+ /** Loaded through the existing company-checked question body reader. */
41
+ body: QuestionBody;
42
+ current_audience: ReportAudience;
43
+ source_path: string;
44
+ }
45
+ export interface TrackerReportTeamInput {
46
+ company: string;
47
+ team: string;
48
+ project_id: string;
49
+ stories: readonly TrackerReportStory[];
50
+ pull_requests: readonly TrackerReportPullRequest[];
51
+ releases: readonly TrackerReportRelease[];
52
+ blockers: readonly TrackerReportRecord[];
53
+ kpis: readonly TrackerReportRecord[];
54
+ okrs: readonly TrackerReportRecord[];
55
+ directives: readonly TrackerReportRecord[];
56
+ questions: readonly TrackerReportQuestion[];
57
+ errors?: readonly TrackerReportIssue[];
58
+ }
59
+ export interface TrackerReportInput {
60
+ company: string;
61
+ generated_at: string;
62
+ since: string;
63
+ since_source: "explicit" | "previous_report" | "first_run";
64
+ teams: readonly TrackerReportTeamInput[];
65
+ }
66
+ export interface ReportTimedStory {
67
+ id: string;
68
+ title: string;
69
+ at: string;
70
+ references: string[];
71
+ }
72
+ export interface ReportPullRequest {
73
+ number: string;
74
+ title: string;
75
+ merged_at: string;
76
+ }
77
+ export interface ReportRelease {
78
+ version: string;
79
+ released_at: string;
80
+ }
81
+ export interface ReportKpiValue {
82
+ kpi_id: string;
83
+ name: string;
84
+ at: string;
85
+ value: number;
86
+ unit: string;
87
+ source: string;
88
+ }
89
+ export interface ReportQuestion {
90
+ question_id: string;
91
+ question: string;
92
+ asked_at: string;
93
+ audience: ReportAudience;
94
+ options: string[];
95
+ recommended?: string;
96
+ answer?: {
97
+ choice: string;
98
+ answered_at: string;
99
+ answered_by: string;
100
+ };
101
+ }
102
+ export interface ReportBlocker {
103
+ id: string;
104
+ story_id: string;
105
+ what: string;
106
+ waiting_on: string;
107
+ next_step: string;
108
+ opened_at: string;
109
+ }
110
+ export interface ReportDirective {
111
+ id: string;
112
+ text: string;
113
+ date: string;
114
+ acknowledgements: Array<{
115
+ team: string;
116
+ lane_id: string;
117
+ at: string;
118
+ }>;
119
+ }
120
+ export interface TrackerOwnerTeamReport {
121
+ team: string;
122
+ project_id: string;
123
+ errors: TrackerReportIssue[];
124
+ progress: {
125
+ stories_moved_to_done: ReportTimedStory[];
126
+ pull_requests_merged: ReportPullRequest[];
127
+ releases: ReportRelease[];
128
+ kpi_values: ReportKpiValue[];
129
+ };
130
+ questions_waiting_on_owner: ReportQuestion[];
131
+ open_blockers: ReportBlocker[];
132
+ decisions: {
133
+ directives: ReportDirective[];
134
+ answered_questions: ReportQuestion[];
135
+ };
136
+ }
137
+ export interface TrackerOwnerReport {
138
+ schema_version: "hq-lanes-owner-report.v1";
139
+ company: string;
140
+ generated_at: string;
141
+ since: string;
142
+ since_source: TrackerReportInput["since_source"];
143
+ teams: TrackerOwnerTeamReport[];
144
+ }
145
+ export interface TrackerTeamKpi {
146
+ id: string;
147
+ name: string;
148
+ definition: string;
149
+ unit: string;
150
+ target: number;
151
+ direction: "up" | "down";
152
+ status: ApprovalState;
153
+ latest_value: {
154
+ at: string;
155
+ value: number;
156
+ source: string;
157
+ } | null;
158
+ recent_values: Array<{
159
+ at: string;
160
+ value: number;
161
+ source: string;
162
+ }>;
163
+ trend: "improving" | "regressing" | "flat" | "insufficient_data";
164
+ }
165
+ export interface TrackerTeamOkr {
166
+ id: string;
167
+ objective: string;
168
+ period: string;
169
+ status: ApprovalState;
170
+ key_results: Array<{
171
+ text: string;
172
+ kpi_id: string;
173
+ target: number;
174
+ }>;
175
+ }
176
+ export interface TrackerTeamPage {
177
+ schema_version: "hq-lanes-team-page.v1";
178
+ company: string;
179
+ team: string;
180
+ project_id: string;
181
+ since: string;
182
+ errors: TrackerReportIssue[];
183
+ okrs: TrackerTeamOkr[];
184
+ kpis: TrackerTeamKpi[];
185
+ work_in_progress: Array<{
186
+ id: string;
187
+ title: string;
188
+ status: ReportStoryStatus;
189
+ references: string[];
190
+ }>;
191
+ work_done_since: ReportTimedStory[];
192
+ blockers: ReportBlocker[];
193
+ questions: ReportQuestion[];
194
+ }
195
+ /** Pure report projection. The caller owns loading, tenant checks, body reads and report-time persistence. */
196
+ export declare function buildTrackerOwnerReport(input: TrackerReportInput): TrackerOwnerReport;
197
+ /** Pure team page projection for already loaded, company-checked records. */
198
+ export declare function buildTrackerTeamPage(team: TrackerReportTeamInput, company: string, sinceValue: string, generatedAtValue: string): TrackerTeamPage;
199
+ /** Human-readable owner report, rendered without editing after the command runs. */
200
+ export declare function renderTrackerOwnerReportText(report: TrackerOwnerReport): string;
201
+ /** Human-readable team page, with proposal status visible beside each objective and KPI. */
202
+ export declare function renderTrackerTeamPageText(page: TrackerTeamPage): string;
203
+ //# sourceMappingURL=tracker-report.d.ts.map