@indigoai-us/hq-cloud 6.14.28 → 6.14.30

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 (93) hide show
  1. package/dist/bin/sync-runner-company.d.ts +4 -0
  2. package/dist/bin/sync-runner-company.d.ts.map +1 -1
  3. package/dist/bin/sync-runner-company.js +3 -0
  4. package/dist/bin/sync-runner-company.js.map +1 -1
  5. package/dist/bin/sync-runner-planning.d.ts +7 -6
  6. package/dist/bin/sync-runner-planning.d.ts.map +1 -1
  7. package/dist/bin/sync-runner-planning.js +1 -1
  8. package/dist/bin/sync-runner-planning.js.map +1 -1
  9. package/dist/bin/sync-runner-planning.test.js +23 -0
  10. package/dist/bin/sync-runner-planning.test.js.map +1 -1
  11. package/dist/bin/sync-runner-rollup.d.ts.map +1 -1
  12. package/dist/bin/sync-runner-rollup.js +3 -1
  13. package/dist/bin/sync-runner-rollup.js.map +1 -1
  14. package/dist/bin/sync-runner-rollup.test.d.ts +2 -0
  15. package/dist/bin/sync-runner-rollup.test.d.ts.map +1 -0
  16. package/dist/bin/sync-runner-rollup.test.js +24 -0
  17. package/dist/bin/sync-runner-rollup.test.js.map +1 -0
  18. package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
  19. package/dist/bin/sync-runner-watch-loop.js +15 -1
  20. package/dist/bin/sync-runner-watch-loop.js.map +1 -1
  21. package/dist/bin/sync-runner.d.ts +11 -7
  22. package/dist/bin/sync-runner.d.ts.map +1 -1
  23. package/dist/bin/sync-runner.js +51 -9
  24. package/dist/bin/sync-runner.js.map +1 -1
  25. package/dist/bin/sync-runner.test.js +97 -0
  26. package/dist/bin/sync-runner.test.js.map +1 -1
  27. package/dist/cli/doctor.d.ts.map +1 -1
  28. package/dist/cli/doctor.js +3 -1
  29. package/dist/cli/doctor.js.map +1 -1
  30. package/dist/cli/reindex.d.ts.map +1 -1
  31. package/dist/cli/reindex.js +24 -13
  32. package/dist/cli/reindex.js.map +1 -1
  33. package/dist/cli/rescue-core.js +3 -1
  34. package/dist/cli/rescue-core.js.map +1 -1
  35. package/dist/cli/share.d.ts +35 -0
  36. package/dist/cli/share.d.ts.map +1 -1
  37. package/dist/cli/share.js +29 -11
  38. package/dist/cli/share.js.map +1 -1
  39. package/dist/cli/share.test.js +46 -0
  40. package/dist/cli/share.test.js.map +1 -1
  41. package/dist/ignore.d.ts.map +1 -1
  42. package/dist/ignore.js +5 -0
  43. package/dist/ignore.js.map +1 -1
  44. package/dist/ignore.test.js +9 -0
  45. package/dist/ignore.test.js.map +1 -1
  46. package/dist/outcome-telemetry.d.ts +167 -0
  47. package/dist/outcome-telemetry.d.ts.map +1 -0
  48. package/dist/outcome-telemetry.js +479 -0
  49. package/dist/outcome-telemetry.js.map +1 -0
  50. package/dist/outcome-telemetry.test.d.ts +9 -0
  51. package/dist/outcome-telemetry.test.d.ts.map +1 -0
  52. package/dist/outcome-telemetry.test.js +412 -0
  53. package/dist/outcome-telemetry.test.js.map +1 -0
  54. package/dist/qmd-reindex.d.ts +48 -35
  55. package/dist/qmd-reindex.d.ts.map +1 -1
  56. package/dist/qmd-reindex.js +188 -60
  57. package/dist/qmd-reindex.js.map +1 -1
  58. package/dist/qmd-reindex.test.d.ts +3 -3
  59. package/dist/qmd-reindex.test.js +203 -41
  60. package/dist/qmd-reindex.test.js.map +1 -1
  61. package/dist/telemetry.d.ts +5 -4
  62. package/dist/telemetry.d.ts.map +1 -1
  63. package/dist/telemetry.js +187 -13
  64. package/dist/telemetry.js.map +1 -1
  65. package/dist/telemetry.test.js +157 -0
  66. package/dist/telemetry.test.js.map +1 -1
  67. package/dist/vault-client.d.ts +34 -0
  68. package/dist/vault-client.d.ts.map +1 -1
  69. package/dist/vault-client.js +23 -0
  70. package/dist/vault-client.js.map +1 -1
  71. package/package.json +1 -1
  72. package/src/bin/sync-runner-company.ts +4 -0
  73. package/src/bin/sync-runner-planning.test.ts +26 -0
  74. package/src/bin/sync-runner-planning.ts +8 -7
  75. package/src/bin/sync-runner-rollup.test.ts +37 -0
  76. package/src/bin/sync-runner-rollup.ts +3 -1
  77. package/src/bin/sync-runner-watch-loop.ts +22 -1
  78. package/src/bin/sync-runner.test.ts +111 -0
  79. package/src/bin/sync-runner.ts +62 -17
  80. package/src/cli/doctor.ts +3 -1
  81. package/src/cli/reindex.ts +24 -12
  82. package/src/cli/rescue-core.ts +3 -1
  83. package/src/cli/share.test.ts +60 -0
  84. package/src/cli/share.ts +29 -11
  85. package/src/ignore.test.ts +10 -0
  86. package/src/ignore.ts +5 -0
  87. package/src/outcome-telemetry.test.ts +498 -0
  88. package/src/outcome-telemetry.ts +639 -0
  89. package/src/qmd-reindex.test.ts +226 -40
  90. package/src/qmd-reindex.ts +209 -61
  91. package/src/telemetry.test.ts +194 -0
  92. package/src/telemetry.ts +233 -14
  93. package/src/vault-client.ts +55 -0
@@ -0,0 +1,479 @@
1
+ /**
2
+ * Outcome-event collector — story-completion + project-shipped emitter
3
+ * (outcome-leaderboard US-004).
4
+ *
5
+ * Sibling to `./telemetry.ts` (token usage) and `./skill-telemetry.ts` (skill
6
+ * invocations). Where those diff `~/.claude/projects/**\/*.jsonl` session logs,
7
+ * this one diffs HQ PROJECT STATE against a persisted cursor at
8
+ * `~/.hq/outcome-telemetry-cursor.json`:
9
+ *
10
+ * - `prd.json` `userStories[].passes` flipping `false → true`
11
+ * → a `story-completed` outcome event.
12
+ * - `board.json` project `status` transitioning to a DONE status
13
+ * → a `project-shipped` outcome event.
14
+ *
15
+ * After each successful sync (the `all-complete` arm of `bin/sync-runner.ts`,
16
+ * via `defaultCollectTelemetry`), it walks `<hqRoot>/companies/*` for those two
17
+ * files, compares each transition-eligible value against the cursor, and POSTs
18
+ * new transitions to `/v1/outcome-events`. The cursor is only advanced for the
19
+ * transitions the server 2xx'd, so a transient outage retries next sync.
20
+ *
21
+ * Trust model (identical to `./telemetry.ts`): the caller's `personUid` is
22
+ * resolved SERVER-side from the Cognito JWT — never from the body. The wire row
23
+ * carries ONLY the outcome type, the ISO-8601 `occurredAt`, the resolved
24
+ * `companyUid`, `repo`/`branch` context, a `dedupeKey`, and the type-specific
25
+ * refs (`projectName`, and `storyId` for stories). NO prd/board file content
26
+ * beyond project name + story id + the status transition ever leaves the machine
27
+ * — enforced by the `toWireRow` allowlist, matching the server's KEEP_FIELDS in
28
+ * `apps/hq-pro/src/vault-service/handlers/outcome-events.ts`.
29
+ *
30
+ * companyUid resolution mirrors `./telemetry.ts`: the manifest at
31
+ * `<hqRoot>/companies/manifest.yaml` is parsed ONCE per run (`buildRepoCompanyMap`)
32
+ * and a project's owning company `cmp_*` uid is looked up by the `companies/<slug>`
33
+ * directory the prd/board lives under (`RepoCompanyMap.bySlug`). A project whose
34
+ * company is not cloud-backed resolves to no uid and is SKIPPED — an outcome event
35
+ * requires a company (the ingest handler makes companyUid a required field).
36
+ *
37
+ * dedupeKey (idempotency anchor — replays + multi-machine syncs never
38
+ * double-count):
39
+ * - story-completed → `<companyUid>#<projectName>#<storyId>`
40
+ * - project-shipped → `<companyUid>#<projectName>`
41
+ * The server's conditional PutItem collapses a re-synced event with the same
42
+ * dedupeKey to a single stored item.
43
+ *
44
+ * Errors are swallowed by design — an outcome collector must never abort or
45
+ * delay a sync (matches the existing collectors). The opt-in gate is the same
46
+ * `getTelemetryOptIn()` used by usage/skill telemetry.
47
+ */
48
+ import { promises as fs } from "node:fs";
49
+ import * as os from "node:os";
50
+ import * as path from "node:path";
51
+ import { buildRepoCompanyMap, } from "./company-resolver.js";
52
+ function emptyCursor() {
53
+ return { version: "2", emitted: {} };
54
+ }
55
+ /**
56
+ * Coerce a raw parsed `emitted` map into the current entry shape. Older cursors
57
+ * (version "1") stored `dedupeKey → typeString`; those entries have no pinned
58
+ * `occurredAt`, so they are migrated to `{ type, occurredAt: <legacy sentinel> }`
59
+ * and treated as already-confirmed (a v1 cursor only ever held CONFIRMED
60
+ * transitions, so migrating them as non-pending preserves "never re-emit").
61
+ */
62
+ function normalizeEmitted(raw) {
63
+ const out = {};
64
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
65
+ return out;
66
+ for (const [key, value] of Object.entries(raw)) {
67
+ if (typeof value === "string") {
68
+ // Legacy v1 entry: `dedupeKey → type`. No stored occurredAt; it was already
69
+ // confirmed-emitted, so keep it as a non-pending marker with no timestamp
70
+ // to reuse (empty string means "assign fresh if it somehow re-emits").
71
+ out[key] = { type: value, occurredAt: "" };
72
+ }
73
+ else if (value && typeof value === "object" && !Array.isArray(value)) {
74
+ const v = value;
75
+ if (typeof v.type === "string") {
76
+ out[key] = {
77
+ type: v.type,
78
+ occurredAt: typeof v.occurredAt === "string" ? v.occurredAt : "",
79
+ ...(v.pending === true ? { pending: true } : {}),
80
+ };
81
+ }
82
+ }
83
+ }
84
+ return out;
85
+ }
86
+ async function loadCursor(cursorPath) {
87
+ try {
88
+ const raw = await fs.readFile(cursorPath, "utf-8");
89
+ const parsed = JSON.parse(raw);
90
+ if (parsed &&
91
+ typeof parsed === "object" &&
92
+ parsed.emitted &&
93
+ typeof parsed.emitted === "object" &&
94
+ !Array.isArray(parsed.emitted)) {
95
+ return {
96
+ version: parsed.version ?? "2",
97
+ emitted: normalizeEmitted(parsed.emitted),
98
+ };
99
+ }
100
+ }
101
+ catch {
102
+ // Missing / unparseable — start fresh.
103
+ }
104
+ return emptyCursor();
105
+ }
106
+ async function saveCursor(cursorPath, cursor) {
107
+ // Atomic write: tmp + rename (matches ./telemetry.ts).
108
+ await fs.mkdir(path.dirname(cursorPath), { recursive: true });
109
+ const tmp = `${cursorPath}.tmp`;
110
+ await fs.writeFile(tmp, JSON.stringify(cursor, null, 2), "utf-8");
111
+ await fs.rename(tmp, cursorPath);
112
+ }
113
+ // ── Local opt-in fallback ─────────────────────────────────────────────────────
114
+ async function readLocalTelemetryEnabled(menubarPath) {
115
+ try {
116
+ const raw = await fs.readFile(menubarPath, "utf-8");
117
+ const parsed = JSON.parse(raw);
118
+ return parsed.telemetryEnabled === true;
119
+ }
120
+ catch {
121
+ return false;
122
+ }
123
+ }
124
+ // ── Transition detection ──────────────────────────────────────────────────────
125
+ /**
126
+ * A board project status is treated as SHIPPED for these values. The Indigo
127
+ * board uses both `done` and `completed` for finished projects; both count as a
128
+ * project-shipped transition. Kept lowercase-compared so a `Done`/`DONE`
129
+ * casing variant still matches.
130
+ */
131
+ const DONE_STATUSES = new Set(["done", "completed", "shipped", "complete"]);
132
+ export function isDoneStatus(status) {
133
+ return typeof status === "string" && DONE_STATUSES.has(status.trim().toLowerCase());
134
+ }
135
+ /**
136
+ * Extract the story-completed transitions from a parsed prd.json. A transition
137
+ * is a story whose `passes` is currently `true` — the cursor decides whether it
138
+ * is NEW (false→true since last sync) vs already-emitted. `passes` that is not
139
+ * boolean `true`, or a story with no string `id`, is not a completion.
140
+ *
141
+ * `projectName` is the prd's `name`; a prd without one is skipped (the ingest
142
+ * requires a non-empty projectName). NOTHING else from the prd — no
143
+ * description, acceptance criteria, files, notes — is read.
144
+ */
145
+ export function extractStoryTransitions(prd, companyUid) {
146
+ if (!prd || typeof prd !== "object" || Array.isArray(prd))
147
+ return [];
148
+ const doc = prd;
149
+ const projectName = typeof doc.name === "string" ? doc.name.trim() : "";
150
+ if (!projectName)
151
+ return [];
152
+ if (!Array.isArray(doc.userStories))
153
+ return [];
154
+ const out = [];
155
+ for (const story of doc.userStories) {
156
+ if (!story || typeof story !== "object" || Array.isArray(story))
157
+ continue;
158
+ const s = story;
159
+ if (s.passes !== true)
160
+ continue;
161
+ const storyId = typeof s.id === "string" ? s.id.trim() : "";
162
+ if (!storyId)
163
+ continue;
164
+ out.push({
165
+ type: "story-completed",
166
+ companyUid,
167
+ projectName,
168
+ storyId,
169
+ dedupeKey: `${companyUid}#${projectName}#${storyId}`,
170
+ });
171
+ }
172
+ return out;
173
+ }
174
+ /**
175
+ * Extract the project-shipped transitions from a parsed board.json. A transition
176
+ * is a project whose `status` is a DONE status (see `isDoneStatus`). The
177
+ * `projectName` is the project's `title` (falling back to `id`); a project with
178
+ * neither is skipped. NOTHING else from the board — description, scope, app,
179
+ * prd_path, timestamps — is read.
180
+ */
181
+ export function extractProjectTransitions(board, companyUid) {
182
+ if (!board || typeof board !== "object" || Array.isArray(board))
183
+ return [];
184
+ const doc = board;
185
+ if (!Array.isArray(doc.projects))
186
+ return [];
187
+ const out = [];
188
+ for (const project of doc.projects) {
189
+ if (!project || typeof project !== "object" || Array.isArray(project))
190
+ continue;
191
+ const p = project;
192
+ if (!isDoneStatus(p.status))
193
+ continue;
194
+ const title = typeof p.title === "string" ? p.title.trim() : "";
195
+ const id = typeof p.id === "string" ? p.id.trim() : "";
196
+ const projectName = title || id;
197
+ if (!projectName)
198
+ continue;
199
+ out.push({
200
+ type: "project-shipped",
201
+ companyUid,
202
+ projectName,
203
+ dedupeKey: `${companyUid}#${projectName}`,
204
+ });
205
+ }
206
+ return out;
207
+ }
208
+ /**
209
+ * Shape a detected transition for the wire — the STRICT allowlist that proves
210
+ * no prd/board content beyond project name, story id, and the status transition
211
+ * is transmitted. Mirrors the server's KEEP_FIELDS + per-type ref rules in
212
+ * `apps/hq-pro/src/vault-service/handlers/outcome-events.ts`. `personUid` is
213
+ * never produced (resolved server-side from the JWT); any other field would be
214
+ * rejected 4xx by the ingest handler.
215
+ */
216
+ export function toWireRow(t, ctx) {
217
+ const row = {
218
+ type: t.type,
219
+ occurredAt: ctx.occurredAt,
220
+ companyUid: t.companyUid,
221
+ repo: ctx.repo,
222
+ branch: ctx.branch,
223
+ dedupeKey: t.dedupeKey,
224
+ projectName: t.projectName,
225
+ };
226
+ if (t.type === "story-completed" && t.storyId !== undefined) {
227
+ row.storyId = t.storyId;
228
+ }
229
+ return row;
230
+ }
231
+ // ── Filesystem scan ────────────────────────────────────────────────────────────
232
+ /**
233
+ * Resolve a company DIRECTORY name (`companies/<slug>`) to its owning company's
234
+ * `cmp_*` uid via the manifest slug→uid map. A slug with no cloud-backed
235
+ * manifest entry resolves to undefined and its projects are skipped.
236
+ */
237
+ function companyUidForSlug(slug, map) {
238
+ return map.bySlug.get(slug);
239
+ }
240
+ /** List the immediate subdirectory names of `dir` (company slugs). */
241
+ async function listCompanySlugs(companiesRoot) {
242
+ try {
243
+ const entries = await fs.readdir(companiesRoot, { withFileTypes: true });
244
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
245
+ }
246
+ catch {
247
+ return [];
248
+ }
249
+ }
250
+ /** Recursively collect every `prd.json` under `root`. Errors are treated as
251
+ * absent (missing dir / EACCES), matching the other collectors' walkers. */
252
+ async function listPrdFiles(root) {
253
+ const out = [];
254
+ async function walk(dir) {
255
+ let entries;
256
+ try {
257
+ entries = await fs.readdir(dir, { withFileTypes: true });
258
+ }
259
+ catch {
260
+ return;
261
+ }
262
+ for (const ent of entries) {
263
+ const full = path.join(dir, ent.name);
264
+ if (ent.isDirectory()) {
265
+ await walk(full);
266
+ }
267
+ else if (ent.isFile() && ent.name === "prd.json") {
268
+ out.push(full);
269
+ }
270
+ }
271
+ }
272
+ await walk(root);
273
+ return out;
274
+ }
275
+ async function readJsonFile(filePath) {
276
+ try {
277
+ const raw = await fs.readFile(filePath, "utf-8");
278
+ return JSON.parse(raw);
279
+ }
280
+ catch {
281
+ return undefined;
282
+ }
283
+ }
284
+ // ── Field / row bounds ───────────────────────────────────────────────────────
285
+ /**
286
+ * Per-string-field character ceiling enforced by the ingest handler (mirrors the
287
+ * 2048-char IAM/field limit). A projectName / storyId longer than this makes the
288
+ * server reject the WHOLE batch, so an over-long field on ONE local project must
289
+ * not be allowed to poison up to 199 other valid outcomes.
290
+ */
291
+ export const MAX_FIELD_CHARS = 2048;
292
+ /**
293
+ * Per-event serialized-byte ceiling (4 KB). The server rejects a whole batch if
294
+ * any single event JSON exceeds this, so an event that would blow the limit is
295
+ * dropped locally rather than sent.
296
+ */
297
+ export const MAX_EVENT_BYTES = 4 * 1024;
298
+ /**
299
+ * Decide whether a shaped wire row is within the ingest limits. The
300
+ * dedupe-relevant fields (companyUid / projectName / storyId, which compose the
301
+ * dedupeKey) CANNOT be truncated without changing the identity of the outcome,
302
+ * so an over-limit transition is SKIPPED wholesale rather than mangled — one
303
+ * malformed local project must never block the valid outcomes in its batch.
304
+ *
305
+ * Returns `null` when the row is acceptable, or a human-readable reason string
306
+ * when it must be skipped (so the caller can log the skip).
307
+ */
308
+ export function wireRowRejectReason(row) {
309
+ for (const [key, value] of Object.entries(row)) {
310
+ if (typeof value === "string" && value.length > MAX_FIELD_CHARS) {
311
+ return `field "${key}" exceeds ${MAX_FIELD_CHARS} chars (${value.length})`;
312
+ }
313
+ }
314
+ const bytes = Buffer.byteLength(JSON.stringify(row), "utf-8");
315
+ if (bytes > MAX_EVENT_BYTES) {
316
+ return `event exceeds ${MAX_EVENT_BYTES} bytes (${bytes})`;
317
+ }
318
+ return null;
319
+ }
320
+ // ── Batching ───────────────────────────────────────────────────────────────────
321
+ // The ingest caps a batch at 500 events; stay comfortably under it. A real sync
322
+ // flushes a small number of transitions, so this bound is rarely reached.
323
+ const MAX_BATCH_EVENTS = 200;
324
+ // ── Main entry point ──────────────────────────────────────────────────────────
325
+ /**
326
+ * Scan HQ project state, detect new story-completed / project-shipped
327
+ * transitions since the last sync, and POST them.
328
+ *
329
+ * Fire-and-forget from the caller's perspective: all errors are caught
330
+ * internally and surfaced only via `log`. The cursor advances ONLY for
331
+ * transitions whose batch the server accepted, so a failed POST re-sends next
332
+ * sync (and the server-side dedupe makes that re-send idempotent).
333
+ */
334
+ export async function collectAndSendOutcomeTelemetry(opts) {
335
+ const home = os.homedir();
336
+ const cursorPath = opts.cursorPath ?? path.join(home, ".hq", "outcome-telemetry-cursor.json");
337
+ const menubarPath = opts.menubarPath ?? path.join(home, ".hq", "menubar.json");
338
+ const repo = opts.repo ?? "hq";
339
+ const branch = opts.branch ?? "main";
340
+ const nowIso = opts.now ?? (() => new Date().toISOString());
341
+ const log = opts.log ?? (() => { });
342
+ // 1. Opt-in gate — same server-authoritative check as usage/skill telemetry,
343
+ // with the local menubar.json fallback.
344
+ let enabled;
345
+ let optInSource;
346
+ try {
347
+ const resp = await opts.client.getTelemetryOptIn();
348
+ enabled = resp.enabled === true;
349
+ optInSource = "server";
350
+ }
351
+ catch (err) {
352
+ log(`[outcome-telemetry] opt-in check failed (${err.message ?? err}) — falling back to local menubar.json`);
353
+ enabled = await readLocalTelemetryEnabled(menubarPath);
354
+ optInSource = "menubar-fallback";
355
+ }
356
+ if (!enabled) {
357
+ return { enabled: false, optInSource, filesScanned: 0, eventsSent: 0, batchesSent: 0 };
358
+ }
359
+ // With no hqRoot there is nothing to scan and no company to attribute.
360
+ if (!opts.hqRoot) {
361
+ return { enabled: true, optInSource, filesScanned: 0, eventsSent: 0, batchesSent: 0 };
362
+ }
363
+ // 2. Resolve slug→companyUid ONCE per run (mirrors ./telemetry.ts).
364
+ const repoCompanyMap = await buildRepoCompanyMap(opts.hqRoot);
365
+ const companiesRoot = path.join(opts.hqRoot, "companies");
366
+ const slugs = await listCompanySlugs(companiesRoot);
367
+ // 3. Detect all current transitions across every cloud-backed company.
368
+ const cursor = await loadCursor(cursorPath);
369
+ const transitions = [];
370
+ let filesScanned = 0;
371
+ for (const slug of slugs) {
372
+ const companyUid = companyUidForSlug(slug, repoCompanyMap);
373
+ // No cloud-backed company for this slug → its projects cannot be attributed
374
+ // (companyUid is a required ingest field), so skip the whole directory.
375
+ if (companyUid === undefined)
376
+ continue;
377
+ const companyDir = path.join(companiesRoot, slug);
378
+ // 3a. Board projects (project-shipped). One board.json per company.
379
+ const boardPath = path.join(companyDir, "board.json");
380
+ const board = await readJsonFile(boardPath);
381
+ if (board !== undefined) {
382
+ filesScanned++;
383
+ for (const transition of extractProjectTransitions(board, companyUid)) {
384
+ transitions.push(transition);
385
+ }
386
+ }
387
+ // 3b. Project stories (story-completed). Many prd.json under projects/.
388
+ const prdFiles = await listPrdFiles(path.join(companyDir, "projects"));
389
+ for (const prdPath of prdFiles) {
390
+ const prd = await readJsonFile(prdPath);
391
+ if (prd === undefined)
392
+ continue;
393
+ filesScanned++;
394
+ for (const transition of extractStoryTransitions(prd, companyUid)) {
395
+ transitions.push(transition);
396
+ }
397
+ }
398
+ }
399
+ // 4. Keep only transitions the cursor has NOT already CONFIRMED emitting.
400
+ // Dedupe within this run too (a dedupeKey seen twice in one scan is one
401
+ // event). A `pending` cursor entry (occurredAt pinned, POST not yet 2xx'd)
402
+ // is still eligible — it re-sends with its ORIGINAL occurredAt.
403
+ const seenThisRun = new Set();
404
+ const pending = [];
405
+ for (const t of transitions) {
406
+ if (seenThisRun.has(t.dedupeKey))
407
+ continue;
408
+ seenThisRun.add(t.dedupeKey);
409
+ const entry = cursor.emitted[t.dedupeKey];
410
+ if (entry && entry.type === t.type && entry.pending !== true)
411
+ continue; // confirmed already
412
+ pending.push(t);
413
+ }
414
+ if (pending.length === 0) {
415
+ return { enabled: true, optInSource, filesScanned, eventsSent: 0, batchesSent: 0 };
416
+ }
417
+ // 5. Assign a STABLE occurredAt per transition and shape the wire row.
418
+ // - If the cursor already pins an occurredAt for this dedupeKey+type
419
+ // (a prior sync detected it), REUSE it verbatim so a replay lands under
420
+ // the same server sort key and collapses to one row.
421
+ // - Otherwise assign a fresh occurredAt now and pin it in the cursor.
422
+ // Rows that would exceed the ingest field/size limits are SKIPPED (their
423
+ // dedupe-identifying fields can't be truncated without changing identity),
424
+ // so one malformed local project cannot block the valid outcomes.
425
+ const now = nowIso();
426
+ const sendable = [];
427
+ for (const t of pending) {
428
+ const existing = cursor.emitted[t.dedupeKey];
429
+ const occurredAt = existing && existing.type === t.type && existing.occurredAt
430
+ ? existing.occurredAt
431
+ : now;
432
+ const row = toWireRow(t, { occurredAt, repo, branch });
433
+ const reject = wireRowRejectReason(row);
434
+ if (reject) {
435
+ log(`[outcome-telemetry] skipping ${t.type} ${t.dedupeKey}: ${reject}`);
436
+ continue;
437
+ }
438
+ // Pin the stable occurredAt BEFORE the POST so a crash between POST-success
439
+ // and cursor-flush still reuses the same timestamp on the next detection.
440
+ cursor.emitted[t.dedupeKey] = { type: t.type, occurredAt, pending: true };
441
+ sendable.push({ transition: t, row });
442
+ }
443
+ // Persist the pinned-but-pending timestamps before sending, so a re-detection
444
+ // after a partial failure reuses the same occurredAt.
445
+ await saveCursor(cursorPath, cursor);
446
+ if (sendable.length === 0) {
447
+ return { enabled: true, optInSource, filesScanned, eventsSent: 0, batchesSent: 0 };
448
+ }
449
+ // 6. Flush in server-sized batches; confirm the cursor per SUCCESSFUL batch.
450
+ let eventsSent = 0;
451
+ let batchesSent = 0;
452
+ for (let i = 0; i < sendable.length; i += MAX_BATCH_EVENTS) {
453
+ const chunk = sendable.slice(i, i + MAX_BATCH_EVENTS);
454
+ const events = chunk.map((s) => s.row);
455
+ try {
456
+ await opts.client.postOutcomeEvents({ events });
457
+ batchesSent++;
458
+ eventsSent += events.length;
459
+ // Confirm these transitions so they never re-send (server-side dedupe also
460
+ // collapses a replay if the cursor is later lost), keeping their pinned
461
+ // occurredAt.
462
+ for (const s of chunk) {
463
+ cursor.emitted[s.transition.dedupeKey] = {
464
+ type: s.transition.type,
465
+ occurredAt: s.row.occurredAt,
466
+ };
467
+ }
468
+ }
469
+ catch (err) {
470
+ log(`[outcome-telemetry] postOutcomeEvents failed (${err.message ?? err}) — ${chunk.length} rows re-send next sync`);
471
+ // Cursor entries stay `pending:true` with their pinned occurredAt — next
472
+ // sync retries with the SAME timestamp.
473
+ }
474
+ }
475
+ // 7. Persist the cursor (confirmed transitions dropped `pending`).
476
+ await saveCursor(cursorPath, cursor);
477
+ return { enabled: true, optInSource, filesScanned, eventsSent, batchesSent };
478
+ }
479
+ //# sourceMappingURL=outcome-telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outcome-telemetry.js","sourceRoot":"","sources":["../src/outcome-telemetry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Tests for the outcome-event collector (outcome-leaderboard US-004).
3
+ *
4
+ * The vault client is stubbed structurally (the production boundary is
5
+ * `OutcomeTelemetryClientSurface`, not the HTTP layer), and a real temp HQ root
6
+ * with `companies/manifest.yaml` + prd.json/board.json fixtures drives the scan.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=outcome-telemetry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outcome-telemetry.test.d.ts","sourceRoot":"","sources":["../src/outcome-telemetry.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}