@indigoai-us/hq-cloud 6.15.0 → 6.15.2

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 (329) hide show
  1. package/dist/bin/sync-mutation.d.ts +16 -0
  2. package/dist/bin/sync-mutation.d.ts.map +1 -0
  3. package/dist/bin/sync-mutation.js +60 -0
  4. package/dist/bin/sync-mutation.js.map +1 -0
  5. package/dist/bin/sync-mutation.test.d.ts +2 -0
  6. package/dist/bin/sync-mutation.test.d.ts.map +1 -0
  7. package/dist/bin/sync-mutation.test.js +165 -0
  8. package/dist/bin/sync-mutation.test.js.map +1 -0
  9. package/dist/bin/sync-runner-company.d.ts +8 -0
  10. package/dist/bin/sync-runner-company.d.ts.map +1 -1
  11. package/dist/bin/sync-runner-company.js +16 -0
  12. package/dist/bin/sync-runner-company.js.map +1 -1
  13. package/dist/bin/sync-runner-company.test.d.ts +2 -0
  14. package/dist/bin/sync-runner-company.test.d.ts.map +1 -0
  15. package/dist/bin/sync-runner-company.test.js +36 -0
  16. package/dist/bin/sync-runner-company.test.js.map +1 -0
  17. package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
  18. package/dist/bin/sync-runner-watch-loop.js +98 -8
  19. package/dist/bin/sync-runner-watch-loop.js.map +1 -1
  20. package/dist/bin/sync-runner.d.ts +17 -0
  21. package/dist/bin/sync-runner.d.ts.map +1 -1
  22. package/dist/bin/sync-runner.js.map +1 -1
  23. package/dist/bin/sync-runner.test.js +109 -0
  24. package/dist/bin/sync-runner.test.js.map +1 -1
  25. package/dist/cli/conflict-recovery.test.d.ts +2 -0
  26. package/dist/cli/conflict-recovery.test.d.ts.map +1 -0
  27. package/dist/cli/conflict-recovery.test.js +201 -0
  28. package/dist/cli/conflict-recovery.test.js.map +1 -0
  29. package/dist/cli/conflict.d.ts +60 -0
  30. package/dist/cli/conflict.d.ts.map +1 -1
  31. package/dist/cli/conflict.js +333 -0
  32. package/dist/cli/conflict.js.map +1 -1
  33. package/dist/cli/sync.d.ts +35 -0
  34. package/dist/cli/sync.d.ts.map +1 -1
  35. package/dist/cli/sync.js +100 -0
  36. package/dist/cli/sync.js.map +1 -1
  37. package/dist/cli/sync.test.js +85 -1
  38. package/dist/cli/sync.test.js.map +1 -1
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +1 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/skill-telemetry.d.ts +6 -0
  44. package/dist/skill-telemetry.d.ts.map +1 -1
  45. package/dist/skill-telemetry.js +14 -2
  46. package/dist/skill-telemetry.js.map +1 -1
  47. package/dist/skill-telemetry.test.js +79 -0
  48. package/dist/skill-telemetry.test.js.map +1 -1
  49. package/dist/sync/candidate-uploader.d.ts +88 -0
  50. package/dist/sync/candidate-uploader.d.ts.map +1 -0
  51. package/dist/sync/candidate-uploader.js +212 -0
  52. package/dist/sync/candidate-uploader.js.map +1 -0
  53. package/dist/sync/candidate-uploader.test.d.ts +2 -0
  54. package/dist/sync/candidate-uploader.test.d.ts.map +1 -0
  55. package/dist/sync/candidate-uploader.test.js +132 -0
  56. package/dist/sync/candidate-uploader.test.js.map +1 -0
  57. package/dist/sync/delta-client.d.ts +73 -0
  58. package/dist/sync/delta-client.d.ts.map +1 -0
  59. package/dist/sync/delta-client.js +201 -0
  60. package/dist/sync/delta-client.js.map +1 -0
  61. package/dist/sync/delta-client.test.d.ts +2 -0
  62. package/dist/sync/delta-client.test.d.ts.map +1 -0
  63. package/dist/sync/delta-client.test.js +97 -0
  64. package/dist/sync/delta-client.test.js.map +1 -0
  65. package/dist/sync/durable-apply.d.ts +76 -0
  66. package/dist/sync/durable-apply.d.ts.map +1 -0
  67. package/dist/sync/durable-apply.js +530 -0
  68. package/dist/sync/durable-apply.js.map +1 -0
  69. package/dist/sync/durable-apply.test.d.ts +2 -0
  70. package/dist/sync/durable-apply.test.d.ts.map +1 -0
  71. package/dist/sync/durable-apply.test.js +180 -0
  72. package/dist/sync/durable-apply.test.js.map +1 -0
  73. package/dist/sync/event-sync.d.ts +33 -1
  74. package/dist/sync/event-sync.d.ts.map +1 -1
  75. package/dist/sync/event-sync.js +149 -1
  76. package/dist/sync/event-sync.js.map +1 -1
  77. package/dist/sync/event-sync.test.js +142 -1
  78. package/dist/sync/event-sync.test.js.map +1 -1
  79. package/dist/sync/index.d.ts +2 -0
  80. package/dist/sync/index.d.ts.map +1 -1
  81. package/dist/sync/index.js +1 -0
  82. package/dist/sync/index.js.map +1 -1
  83. package/dist/sync/multipart-uploader.d.ts +99 -0
  84. package/dist/sync/multipart-uploader.d.ts.map +1 -0
  85. package/dist/sync/multipart-uploader.js +447 -0
  86. package/dist/sync/multipart-uploader.js.map +1 -0
  87. package/dist/sync/multipart-uploader.test.d.ts +2 -0
  88. package/dist/sync/multipart-uploader.test.d.ts.map +1 -0
  89. package/dist/sync/multipart-uploader.test.js +119 -0
  90. package/dist/sync/multipart-uploader.test.js.map +1 -0
  91. package/dist/sync/mutation-client.d.ts +85 -0
  92. package/dist/sync/mutation-client.d.ts.map +1 -0
  93. package/dist/sync/mutation-client.js +245 -0
  94. package/dist/sync/mutation-client.js.map +1 -0
  95. package/dist/sync/mutation-client.test.d.ts +2 -0
  96. package/dist/sync/mutation-client.test.d.ts.map +1 -0
  97. package/dist/sync/mutation-client.test.js +51 -0
  98. package/dist/sync/mutation-client.test.js.map +1 -0
  99. package/dist/sync/push-receiver.d.ts +45 -0
  100. package/dist/sync/push-receiver.d.ts.map +1 -1
  101. package/dist/sync/push-receiver.js +101 -0
  102. package/dist/sync/push-receiver.js.map +1 -1
  103. package/dist/sync/push-receiver.test.js +54 -2
  104. package/dist/sync/push-receiver.test.js.map +1 -1
  105. package/dist/sync/scope-inventory-client.d.ts +69 -0
  106. package/dist/sync/scope-inventory-client.d.ts.map +1 -0
  107. package/dist/sync/scope-inventory-client.js +210 -0
  108. package/dist/sync/scope-inventory-client.js.map +1 -0
  109. package/dist/sync/scope-inventory-client.test.d.ts +2 -0
  110. package/dist/sync/scope-inventory-client.test.d.ts.map +1 -0
  111. package/dist/sync/scope-inventory-client.test.js +94 -0
  112. package/dist/sync/scope-inventory-client.test.js.map +1 -0
  113. package/dist/sync/snapshot-client.d.ts +98 -0
  114. package/dist/sync/snapshot-client.d.ts.map +1 -0
  115. package/dist/sync/snapshot-client.js +402 -0
  116. package/dist/sync/snapshot-client.js.map +1 -0
  117. package/dist/sync/snapshot-client.test.d.ts +2 -0
  118. package/dist/sync/snapshot-client.test.d.ts.map +1 -0
  119. package/dist/sync/snapshot-client.test.js +169 -0
  120. package/dist/sync/snapshot-client.test.js.map +1 -0
  121. package/dist/sync/uploader-finalization.d.ts +97 -0
  122. package/dist/sync/uploader-finalization.d.ts.map +1 -0
  123. package/dist/sync/uploader-finalization.js +273 -0
  124. package/dist/sync/uploader-finalization.js.map +1 -0
  125. package/dist/sync/uploader-finalization.test.d.ts +2 -0
  126. package/dist/sync/uploader-finalization.test.d.ts.map +1 -0
  127. package/dist/sync/uploader-finalization.test.js +92 -0
  128. package/dist/sync/uploader-finalization.test.js.map +1 -0
  129. package/dist/telemetry.d.ts +11 -1
  130. package/dist/telemetry.d.ts.map +1 -1
  131. package/dist/telemetry.js +21 -2
  132. package/dist/telemetry.js.map +1 -1
  133. package/dist/telemetry.test.js +80 -0
  134. package/dist/telemetry.test.js.map +1 -1
  135. package/package.json +6 -1
  136. package/.claude/policies/hq-cloud-esm-cannot-spy-fs-builtins.md +0 -30
  137. package/.claude/policies/hq-cloud-strip-types-no-parameter-properties.md +0 -22
  138. package/.github/workflows/ci.yml +0 -84
  139. package/.github/workflows/publish.yml +0 -56
  140. package/.github/workflows/unreleased-commits-nag.yml +0 -256
  141. package/eslint.config.js +0 -67
  142. package/pnpm-workspace.yaml +0 -2
  143. package/scripts/presign-transport-e2e.mjs +0 -250
  144. package/scripts/vault-rebaseline.sh +0 -323
  145. package/scripts/vault-rescue.sh +0 -332
  146. package/src/active-company.test.ts +0 -188
  147. package/src/active-company.ts +0 -168
  148. package/src/agent-codex-instructions.test.ts +0 -332
  149. package/src/agent-codex-instructions.ts +0 -309
  150. package/src/auth.ts +0 -146
  151. package/src/backup-prune.test.ts +0 -98
  152. package/src/backup-prune.ts +0 -182
  153. package/src/bin/backup-prune-runner.ts +0 -33
  154. package/src/bin/rescue-runner.ts +0 -25
  155. package/src/bin/sync-runner-company.ts +0 -695
  156. package/src/bin/sync-runner-events.test.ts +0 -143
  157. package/src/bin/sync-runner-events.ts +0 -55
  158. package/src/bin/sync-runner-planning.test.ts +0 -311
  159. package/src/bin/sync-runner-planning.ts +0 -258
  160. package/src/bin/sync-runner-rollup.test.ts +0 -37
  161. package/src/bin/sync-runner-rollup.ts +0 -97
  162. package/src/bin/sync-runner-telemetry.ts +0 -15
  163. package/src/bin/sync-runner-watch-loop.ts +0 -1235
  164. package/src/bin/sync-runner-watch-routes.test.ts +0 -71
  165. package/src/bin/sync-runner-watch-routes.ts +0 -184
  166. package/src/bin/sync-runner.test.ts +0 -8767
  167. package/src/bin/sync-runner.ts +0 -2190
  168. package/src/cli/accept.ts +0 -124
  169. package/src/cli/conflict.ts +0 -119
  170. package/src/cli/doctor.test.ts +0 -581
  171. package/src/cli/doctor.ts +0 -642
  172. package/src/cli/index.ts +0 -49
  173. package/src/cli/invite.test.ts +0 -250
  174. package/src/cli/invite.ts +0 -214
  175. package/src/cli/promote.ts +0 -157
  176. package/src/cli/reindex-knowledge.test.ts +0 -307
  177. package/src/cli/reindex-knowledge.ts +0 -450
  178. package/src/cli/reindex.test.ts +0 -957
  179. package/src/cli/reindex.ts +0 -979
  180. package/src/cli/rescue-classify-ordering.test.ts +0 -548
  181. package/src/cli/rescue-clone-diagnostics.test.ts +0 -120
  182. package/src/cli/rescue-core.ts +0 -3011
  183. package/src/cli/rescue-drift-reconcile.test.ts +0 -179
  184. package/src/cli/rescue-drop-dir-symlink.test.ts +0 -224
  185. package/src/cli/rescue-exec-bit-preserve.test.ts +0 -187
  186. package/src/cli/rescue-hq-root-guard.test.ts +0 -232
  187. package/src/cli/rescue-journal-reconcile.test.ts +0 -215
  188. package/src/cli/rescue-mtime-preserve.test.ts +0 -203
  189. package/src/cli/rescue-settings-reconcile.test.ts +0 -637
  190. package/src/cli/rescue-snapshot.test.ts +0 -57
  191. package/src/cli/rescue-snapshot.ts +0 -51
  192. package/src/cli/rescue.reindex.test.ts +0 -63
  193. package/src/cli/rescue.test.ts +0 -131
  194. package/src/cli/rescue.ts +0 -182
  195. package/src/cli/share.test.ts +0 -7843
  196. package/src/cli/share.ts +0 -3663
  197. package/src/cli/sync-scope.test.ts +0 -652
  198. package/src/cli/sync.test.ts +0 -5207
  199. package/src/cli/sync.ts +0 -3470
  200. package/src/cli/tombstones.ts +0 -106
  201. package/src/cli/watch-event-push-conflict.test.ts +0 -234
  202. package/src/client-info.test.ts +0 -214
  203. package/src/client-info.ts +0 -121
  204. package/src/cognito-auth.test.ts +0 -712
  205. package/src/cognito-auth.ts +0 -1422
  206. package/src/company-resolver.test.ts +0 -618
  207. package/src/company-resolver.ts +0 -521
  208. package/src/context.test.ts +0 -583
  209. package/src/context.ts +0 -378
  210. package/src/daemon-worker.ts +0 -26
  211. package/src/daemon.ts +0 -99
  212. package/src/entity-resolver.test.ts +0 -315
  213. package/src/entity-resolver.ts +0 -180
  214. package/src/ignore.test.ts +0 -466
  215. package/src/ignore.ts +0 -469
  216. package/src/index.ts +0 -439
  217. package/src/journal.test.ts +0 -968
  218. package/src/journal.ts +0 -765
  219. package/src/lib/cloud-authoritative.test.ts +0 -45
  220. package/src/lib/cloud-authoritative.ts +0 -59
  221. package/src/lib/conflict-file.ts +0 -86
  222. package/src/lib/conflict-index.ts +0 -289
  223. package/src/lib/conflict.test.ts +0 -348
  224. package/src/lib/describe-error.test.ts +0 -100
  225. package/src/lib/describe-error.ts +0 -58
  226. package/src/lib/exit-codes.ts +0 -24
  227. package/src/lib/machine-id.test.ts +0 -231
  228. package/src/lib/machine-id.ts +0 -175
  229. package/src/lib/net-errors.test.ts +0 -65
  230. package/src/lib/net-errors.ts +0 -86
  231. package/src/lib/readlink-safe.test.ts +0 -43
  232. package/src/lib/readlink-safe.ts +0 -29
  233. package/src/local-path-codec.test.ts +0 -138
  234. package/src/local-path-codec.ts +0 -161
  235. package/src/machine-auth.test.ts +0 -1323
  236. package/src/manifest-reconcile.test.ts +0 -1123
  237. package/src/manifest-reconcile.ts +0 -518
  238. package/src/object-io.test.ts +0 -1221
  239. package/src/object-io.ts +0 -1306
  240. package/src/operation-lock.test.ts +0 -484
  241. package/src/operation-lock.ts +0 -680
  242. package/src/outcome-telemetry.test.ts +0 -498
  243. package/src/outcome-telemetry.ts +0 -639
  244. package/src/personal-vault-exclusions.test.ts +0 -308
  245. package/src/personal-vault-exclusions.ts +0 -354
  246. package/src/personal-vault.test.ts +0 -756
  247. package/src/personal-vault.ts +0 -496
  248. package/src/prefix-coalesce.test.ts +0 -240
  249. package/src/prefix-coalesce.ts +0 -273
  250. package/src/public-surface.test.ts +0 -117
  251. package/src/qmd-reindex.test.ts +0 -877
  252. package/src/qmd-reindex.ts +0 -842
  253. package/src/read-only-state-dir.test.ts +0 -188
  254. package/src/remote-pull.test.ts +0 -1130
  255. package/src/remote-pull.ts +0 -618
  256. package/src/s3.symlink-materialize.test.ts +0 -492
  257. package/src/s3.test.ts +0 -1789
  258. package/src/s3.ts +0 -1532
  259. package/src/schemas/signal-types.test.ts +0 -82
  260. package/src/schemas/signal-types.ts +0 -38
  261. package/src/schemas/source-channels.test.ts +0 -82
  262. package/src/schemas/source-channels.ts +0 -53
  263. package/src/scope-shrink.test.ts +0 -633
  264. package/src/scope-shrink.ts +0 -481
  265. package/src/signals/get.test.ts +0 -310
  266. package/src/signals/get.ts +0 -75
  267. package/src/signals/internals.ts +0 -195
  268. package/src/signals/list.test.ts +0 -420
  269. package/src/signals/list.ts +0 -79
  270. package/src/signals/parse.ts +0 -8
  271. package/src/signals/types.ts +0 -91
  272. package/src/skill-telemetry.test.ts +0 -1825
  273. package/src/skill-telemetry.ts +0 -1439
  274. package/src/sources/get.test.ts +0 -293
  275. package/src/sources/get.ts +0 -66
  276. package/src/sources/internals.ts +0 -198
  277. package/src/sources/list.test.ts +0 -402
  278. package/src/sources/list.ts +0 -84
  279. package/src/sources/parse.ts +0 -43
  280. package/src/sources/types.ts +0 -84
  281. package/src/sync/event-sync.test.ts +0 -594
  282. package/src/sync/event-sync.ts +0 -545
  283. package/src/sync/feature-flags.test.ts +0 -378
  284. package/src/sync/feature-flags.ts +0 -62
  285. package/src/sync/index.ts +0 -76
  286. package/src/sync/lease-client.test.ts +0 -128
  287. package/src/sync/lease-client.ts +0 -207
  288. package/src/sync/logger.test.ts +0 -242
  289. package/src/sync/logger.ts +0 -79
  290. package/src/sync/metrics.test.ts +0 -462
  291. package/src/sync/metrics.ts +0 -213
  292. package/src/sync/pull-scope.ts +0 -265
  293. package/src/sync/push-event.test.ts +0 -266
  294. package/src/sync/push-event.ts +0 -224
  295. package/src/sync/push-receiver.test.ts +0 -566
  296. package/src/sync/push-receiver.ts +0 -1048
  297. package/src/sync/push-transport.ts +0 -231
  298. package/src/sync/realtime-rollout.test.ts +0 -86
  299. package/src/sync/realtime-rollout.ts +0 -262
  300. package/src/sync/state-store.test.ts +0 -194
  301. package/src/sync/state-store.ts +0 -727
  302. package/src/sync-core.ts +0 -58
  303. package/src/sync-progress.test.ts +0 -94
  304. package/src/sync-progress.ts +0 -140
  305. package/src/telemetry-events.test.ts +0 -88
  306. package/src/telemetry-events.ts +0 -205
  307. package/src/telemetry.test.ts +0 -1280
  308. package/src/telemetry.ts +0 -1109
  309. package/src/types.ts +0 -314
  310. package/src/vault-client.test.ts +0 -1380
  311. package/src/vault-client.ts +0 -1694
  312. package/src/version.ts +0 -24
  313. package/src/watch-roots.test.ts +0 -278
  314. package/src/watch-roots.ts +0 -162
  315. package/src/watcher-event-gate.test.ts +0 -212
  316. package/src/watcher.test.ts +0 -1079
  317. package/src/watcher.ts +0 -1741
  318. package/test/e2e/sync/cross-tenant-isolation.test.ts +0 -630
  319. package/test/e2e/sync/skill-telemetry-oversized-transcript.test.ts +0 -124
  320. package/test/e2e/sync/transient-company-leg.test.ts +0 -384
  321. package/test/e2e/sync/windows-unreadable-link-leg.test.ts +0 -191
  322. package/test/e2e/watcher-real-chokidar.test.ts +0 -165
  323. package/test/e2e/watcher-recursive-backend.test.ts +0 -181
  324. package/test/e2e/watcher-scoped-coverage.test.ts +0 -381
  325. package/test/invite-flow.integration.test.ts +0 -244
  326. package/test/joiner-manifest-reconcile.integration.test.ts +0 -322
  327. package/test/share-sync.integration.test.ts +0 -213
  328. package/tsconfig.json +0 -19
  329. package/vitest.config.ts +0 -22
@@ -1,618 +0,0 @@
1
- /**
2
- * Tests for the cwd → owning-company resolver (surface-hq-console-telemetry
3
- * US-002). Covers manifest parsing, cmp_* extraction, longest-prefix matching,
4
- * the trailing-slash sibling boundary, and the unattributed (no-match) path.
5
- */
6
-
7
- import { describe, it, expect, afterEach } from "vitest";
8
- import * as fs from "fs";
9
- import * as os from "os";
10
- import * as path from "path";
11
- import {
12
- buildRepoCompanyMap,
13
- resolveCompanyForCwd,
14
- resolveCompanyForSkill,
15
- } from "./company-resolver.js";
16
-
17
- const INDIGO = "cmp_01INDIGO";
18
- const HPO = "cmp_01HPO";
19
-
20
- const MANIFEST = `companies:
21
- personal:
22
- name: Personal
23
- repos: []
24
- indigo:
25
- name: Indigo
26
- repos:
27
- - repos/private/hq-cloud
28
- - repos/public/hq-core
29
- cloud_uid: ${INDIGO}
30
- hpo:
31
- name: HPO
32
- repos:
33
- - repos/private/hpo-jobs
34
- cloud_uid: ${HPO}
35
- liverecover:
36
- name: LiveRecover
37
- repos:
38
- - repos/private/liverecover-site
39
- `;
40
-
41
- function setupHqRoot(manifest: string = MANIFEST): string {
42
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "hq-resolver-test-"));
43
- fs.mkdirSync(path.join(root, "companies"), { recursive: true });
44
- fs.writeFileSync(path.join(root, "companies", "manifest.yaml"), manifest);
45
- return root;
46
- }
47
-
48
- describe("buildRepoCompanyMap + resolveCompanyForCwd", () => {
49
- let hqRoot: string;
50
- afterEach(() => {
51
- if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
52
- });
53
-
54
- it("resolves a cwd inside a company repo to that company's cmp_ uid", async () => {
55
- hqRoot = setupHqRoot();
56
- const map = await buildRepoCompanyMap(hqRoot);
57
- const cwd = path.join(hqRoot, "repos/private/hq-cloud/src");
58
- expect(resolveCompanyForCwd(cwd, map)).toBe(INDIGO);
59
- });
60
-
61
- it("resolves the repo root itself (exact match, no trailing slash)", async () => {
62
- hqRoot = setupHqRoot();
63
- const map = await buildRepoCompanyMap(hqRoot);
64
- const cwd = path.join(hqRoot, "repos/public/hq-core");
65
- expect(resolveCompanyForCwd(cwd, map)).toBe(INDIGO);
66
- });
67
-
68
- it("maps each repo to its own company", async () => {
69
- hqRoot = setupHqRoot();
70
- const map = await buildRepoCompanyMap(hqRoot);
71
- expect(resolveCompanyForCwd(path.join(hqRoot, "repos/private/hpo-jobs/x"), map)).toBe(HPO);
72
- });
73
-
74
- it("returns undefined for a cwd outside any company repo (unattributed)", async () => {
75
- hqRoot = setupHqRoot();
76
- const map = await buildRepoCompanyMap(hqRoot);
77
- expect(resolveCompanyForCwd("/Users/x/random-repo", map)).toBeUndefined();
78
- // The HQ root itself is company-agnostic — it belongs to no single tenant.
79
- expect(resolveCompanyForCwd(hqRoot, map)).toBeUndefined();
80
- });
81
-
82
- /**
83
- * Behaviour change (was: `companies/<slug>` resolved to undefined).
84
- *
85
- * A company's own HQ directory holds its skills, knowledge, and project
86
- * files, so work done there is unambiguously that company's work. The prior
87
- * repos-only scope dropped it, and in production a large share of sessions
88
- * run from inside the HQ root rather than a repo checkout — leaving that work
89
- * unattributed and the owner's Projects view empty. Attribution is scoped to
90
- * the single tenant that owns the directory, so this widens coverage without
91
- * crossing a tenant boundary.
92
- */
93
- it("attributes a cwd inside the company's own HQ directory", async () => {
94
- hqRoot = setupHqRoot();
95
- const map = await buildRepoCompanyMap(hqRoot);
96
- expect(resolveCompanyForCwd(path.join(hqRoot, "companies/indigo"), map)).toBe(INDIGO);
97
- expect(
98
- resolveCompanyForCwd(path.join(hqRoot, "companies/hpo/knowledge"), map),
99
- ).toBe(HPO);
100
- });
101
-
102
- it("does NOT attribute a company that lacks a cloud_uid (not cloud-backed)", async () => {
103
- hqRoot = setupHqRoot();
104
- const map = await buildRepoCompanyMap(hqRoot);
105
- // liverecover-site has no cloud_uid → unattributed.
106
- expect(resolveCompanyForCwd(path.join(hqRoot, "repos/private/liverecover-site"), map)).toBeUndefined();
107
- });
108
-
109
- it("does not match a sibling that merely shares a path prefix", async () => {
110
- hqRoot = setupHqRoot();
111
- const map = await buildRepoCompanyMap(hqRoot);
112
- // `<repo>-other` shares the string prefix but must NOT match (slash boundary).
113
- expect(resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-cloud-other"), map)).toBeUndefined();
114
- });
115
-
116
- it("handles a trailing slash on the cwd", async () => {
117
- hqRoot = setupHqRoot();
118
- const map = await buildRepoCompanyMap(hqRoot);
119
- expect(resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-cloud") + "/", map)).toBe(INDIGO);
120
- });
121
-
122
- it("returns undefined for an undefined/empty cwd", async () => {
123
- hqRoot = setupHqRoot();
124
- const map = await buildRepoCompanyMap(hqRoot);
125
- expect(resolveCompanyForCwd(undefined, map)).toBeUndefined();
126
- expect(resolveCompanyForCwd("", map)).toBeUndefined();
127
- });
128
-
129
- it("never returns the reserved 'unattributed' sentinel", async () => {
130
- hqRoot = setupHqRoot(`companies:
131
- bad:
132
- repos:
133
- - repos/x
134
- cloud_uid: unattributed
135
- `);
136
- const map = await buildRepoCompanyMap(hqRoot);
137
- // `unattributed` is not a cmp_* uid → not added to the map → no match.
138
- expect(map.entries).toHaveLength(0);
139
- expect(resolveCompanyForCwd(path.join(hqRoot, "repos/x"), map)).toBeUndefined();
140
- });
141
-
142
- it("yields an empty map for a missing manifest", async () => {
143
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "hq-resolver-empty-"));
144
- try {
145
- const map = await buildRepoCompanyMap(root);
146
- expect(map.entries).toHaveLength(0);
147
- expect(resolveCompanyForCwd(path.join(root, "repos/private/hq-cloud"), map)).toBeUndefined();
148
- } finally {
149
- fs.rmSync(root, { recursive: true, force: true });
150
- }
151
- });
152
-
153
- it("yields an empty map for an unparseable manifest", async () => {
154
- hqRoot = setupHqRoot(":\n not: [valid: yaml");
155
- const map = await buildRepoCompanyMap(hqRoot);
156
- expect(map.entries).toHaveLength(0);
157
- expect(map.bySlug.size).toBe(0);
158
- });
159
- });
160
-
161
- describe("resolveCompanyForSkill", () => {
162
- let hqRoot: string;
163
- afterEach(() => {
164
- if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
165
- });
166
-
167
- it("attributes a company-namespaced skill to its company, regardless of cwd", async () => {
168
- hqRoot = setupHqRoot();
169
- const map = await buildRepoCompanyMap(hqRoot);
170
- // The slug map is populated from the manifest keys with a cloud_uid.
171
- expect(resolveCompanyForSkill("indigo:signals", map)).toBe(INDIGO);
172
- expect(resolveCompanyForSkill("hpo:some-skill", map)).toBe(HPO);
173
- // Multi-segment skill name: only the leading slug is the company.
174
- expect(resolveCompanyForSkill("indigo:hq-dev-thread", map)).toBe(INDIGO);
175
- });
176
-
177
- it("returns undefined for a core (un-namespaced) skill", async () => {
178
- hqRoot = setupHqRoot();
179
- const map = await buildRepoCompanyMap(hqRoot);
180
- expect(resolveCompanyForSkill("startwork", map)).toBeUndefined();
181
- expect(resolveCompanyForSkill("review", map)).toBeUndefined();
182
- });
183
-
184
- it("returns undefined for a non-company namespace (personal/pack) and a leading colon", async () => {
185
- hqRoot = setupHqRoot();
186
- const map = await buildRepoCompanyMap(hqRoot);
187
- // `personal` has no cloud_uid → not in the slug map.
188
- expect(resolveCompanyForSkill("personal:startwork", map)).toBeUndefined();
189
- expect(
190
- resolveCompanyForSkill("hq-pack-engineering:review", map),
191
- ).toBeUndefined();
192
- expect(resolveCompanyForSkill(":oops", map)).toBeUndefined();
193
- });
194
-
195
- it("returns undefined for a slug whose company is not cloud-backed", async () => {
196
- hqRoot = setupHqRoot();
197
- const map = await buildRepoCompanyMap(hqRoot);
198
- // `liverecover` is in the manifest but carries no cloud_uid.
199
- expect(resolveCompanyForSkill("liverecover:foo", map)).toBeUndefined();
200
- });
201
-
202
- it("handles a missing/undefined skill safely", async () => {
203
- hqRoot = setupHqRoot();
204
- const map = await buildRepoCompanyMap(hqRoot);
205
- expect(resolveCompanyForSkill(undefined, map)).toBeUndefined();
206
- expect(resolveCompanyForSkill("", map)).toBeUndefined();
207
- });
208
- });
209
-
210
- /**
211
- * Regression: the real `companies/manifest.yaml` records `repos` as BARE NAMES
212
- * (`hq-cloud`), not HQ-root-relative paths (`repos/private/hq-cloud`). The
213
- * original resolver did `path.resolve(hqRoot, repo)` and produced
214
- * `<hqRoot>/hq-cloud` — a path no checkout lives at — so every entry matched
215
- * nothing and NO telemetry event was ever attributed. Verified in prod at the
216
- * time of the fix: 0 of 3200 sampled usage events across 9 people carried a
217
- * `companyUid`, which left the Team Activity "Projects" tab permanently empty.
218
- */
219
- describe("real-manifest shape: bare repo names + company directories", () => {
220
- const ACME = "cmp_01ACME";
221
- const BETA = "cmp_01BETA";
222
-
223
- // Mirrors the live manifest: bare repo names, an explicit `path`, and a
224
- // company with no cloud_uid (not cloud-backed).
225
- const REAL_SHAPE = `companies:
226
- acme:
227
- name: Acme
228
- path: companies/acme
229
- repos:
230
- - hq-cloud
231
- - hq-console
232
- cloud_uid: ${ACME}
233
- beta:
234
- name: Beta
235
- path: companies/beta
236
- repos:
237
- - beta-site
238
- cloud_uid: ${BETA}
239
- nocloud:
240
- name: NoCloud
241
- path: companies/nocloud
242
- repos:
243
- - orphan-repo
244
- `;
245
-
246
- let hqRoot: string;
247
- afterEach(() => {
248
- if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
249
- });
250
-
251
- it("resolves a bare repo name to its repos/private checkout", async () => {
252
- hqRoot = setupHqRoot(REAL_SHAPE);
253
- const map = await buildRepoCompanyMap(hqRoot);
254
- const cwd = path.join(hqRoot, "repos/private/hq-console/src/app");
255
- expect(resolveCompanyForCwd(cwd, map)).toBe(ACME);
256
- });
257
-
258
- it("resolves a bare repo name to its repos/public checkout", async () => {
259
- hqRoot = setupHqRoot(REAL_SHAPE);
260
- const map = await buildRepoCompanyMap(hqRoot);
261
- const cwd = path.join(hqRoot, "repos/public/hq-cloud");
262
- expect(resolveCompanyForCwd(cwd, map)).toBe(ACME);
263
- });
264
-
265
- it("attributes work inside the company's own HQ directory", async () => {
266
- hqRoot = setupHqRoot(REAL_SHAPE);
267
- const map = await buildRepoCompanyMap(hqRoot);
268
- expect(resolveCompanyForCwd(path.join(hqRoot, "companies/acme"), map)).toBe(ACME);
269
- expect(
270
- resolveCompanyForCwd(path.join(hqRoot, "companies/beta/projects/x"), map),
271
- ).toBe(BETA);
272
- });
273
-
274
- it("still honours an explicit HQ-root-relative repo path (back-compat)", async () => {
275
- hqRoot = setupHqRoot();
276
- const map = await buildRepoCompanyMap(hqRoot);
277
- expect(
278
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-cloud/src"), map),
279
- ).toBe("cmp_01INDIGO");
280
- });
281
-
282
- it("leaves a non-cloud-backed company's repos and directory unattributed", async () => {
283
- hqRoot = setupHqRoot(REAL_SHAPE);
284
- const map = await buildRepoCompanyMap(hqRoot);
285
- expect(
286
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/orphan-repo"), map),
287
- ).toBeUndefined();
288
- expect(resolveCompanyForCwd(path.join(hqRoot, "companies/nocloud"), map)).toBeUndefined();
289
- });
290
-
291
- /**
292
- * Case folding follows the FILESYSTEM, not the OS.
293
- *
294
- * Where the filesystem folds case, `.../HQ` and `.../hq` are literally the
295
- * same directory and a differently-cased cwd must still attribute. Where it
296
- * does not, they are different directories and an unattributed event is the
297
- * correct (safe) outcome.
298
- *
299
- * The expectation is derived from the resolver's own probe (`map.foldsCase`),
300
- * NOT from `process.platform` — case-sensitive APFS and case-insensitive
301
- * volumes mounted on Linux both make the platform a liar, and this test would
302
- * fail on them despite the resolver behaving correctly.
303
- */
304
- it("follows the probed filesystem for a differently-cased path", async () => {
305
- hqRoot = setupHqRoot(REAL_SHAPE);
306
- const map = await buildRepoCompanyMap(hqRoot);
307
- const cwd = path.join(hqRoot, "repos/private/hq-console").toUpperCase();
308
- expect(resolveCompanyForCwd(cwd, map)).toBe(map.foldsCase ? ACME : undefined);
309
- });
310
-
311
- it("always matches an exactly-cased path", async () => {
312
- hqRoot = setupHqRoot(REAL_SHAPE);
313
- const map = await buildRepoCompanyMap(hqRoot);
314
- expect(
315
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-console/src"), map),
316
- ).toBe(ACME);
317
- });
318
-
319
- it("treats backslash-separated paths as equivalent to forward slashes", async () => {
320
- hqRoot = setupHqRoot(REAL_SHAPE);
321
- const map = await buildRepoCompanyMap(hqRoot);
322
- const cwd = path.join(hqRoot, "repos/private/hq-console/src").replace(/\//g, "\\");
323
- expect(resolveCompanyForCwd(cwd, map)).toBe(ACME);
324
- });
325
-
326
- it("does not let a sibling sharing a string prefix match", async () => {
327
- hqRoot = setupHqRoot(REAL_SHAPE);
328
- const map = await buildRepoCompanyMap(hqRoot);
329
- expect(
330
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-console-other"), map),
331
- ).toBeUndefined();
332
- });
333
-
334
- it("emits no duplicate match keys", async () => {
335
- hqRoot = setupHqRoot(REAL_SHAPE);
336
- const map = await buildRepoCompanyMap(hqRoot);
337
- const keys = map.entries.map((e) => e.matchKey);
338
- expect(new Set(keys).size).toBe(keys.length);
339
- });
340
- });
341
-
342
- /**
343
- * Codex-review follow-ups. Each of these is a real HQ layout that pure lexical
344
- * matching against the primary checkout silently misses, leaving the work
345
- * unattributed — the same class of gap that left the Projects tab empty.
346
- */
347
- describe("real HQ layouts: worktrees, symlinks, and ambiguity", () => {
348
- const ACME = "cmp_01ACME";
349
- const BETA = "cmp_01BETA";
350
-
351
- const SHAPE = `companies:
352
- acme:
353
- name: Acme
354
- path: companies/acme
355
- repos:
356
- - hq-cloud
357
- cloud_uid: ${ACME}
358
- beta:
359
- name: Beta
360
- path: companies/beta
361
- repos:
362
- - beta-site
363
- cloud_uid: ${BETA}
364
- `;
365
-
366
- let hqRoot: string;
367
- afterEach(() => {
368
- if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
369
- });
370
-
371
- function mkdirp(...segments: string[]): string {
372
- const p = path.join(hqRoot, ...segments);
373
- fs.mkdirSync(p, { recursive: true });
374
- return p;
375
- }
376
-
377
- /**
378
- * HQ does most feature work in a sibling git worktree named
379
- * `<repo>-wt-<branch>`. The resolver's slash boundary rejects that as a mere
380
- * string-prefix sibling of `<repo>`, so without explicit expansion every
381
- * worktree session is unattributed.
382
- */
383
- it("attributes work inside a `<repo>-wt-*` git worktree", async () => {
384
- hqRoot = setupHqRoot(SHAPE);
385
- mkdirp("repos/private/hq-cloud");
386
- const wt = mkdirp("repos/private/hq-cloud-wt-feature-x");
387
-
388
- const map = await buildRepoCompanyMap(hqRoot);
389
- expect(resolveCompanyForCwd(wt, map)).toBe(ACME);
390
- expect(resolveCompanyForCwd(path.join(wt, "src/lib"), map)).toBe(ACME);
391
- });
392
-
393
- it("still rejects an unrelated sibling that merely shares the prefix", async () => {
394
- hqRoot = setupHqRoot(SHAPE);
395
- mkdirp("repos/private/hq-cloud");
396
- const other = mkdirp("repos/private/hq-cloud-other");
397
-
398
- const map = await buildRepoCompanyMap(hqRoot);
399
- expect(resolveCompanyForCwd(other, map)).toBeUndefined();
400
- });
401
-
402
- /**
403
- * A symlinked checkout reports its PHYSICAL path as cwd, which lives outside
404
- * the HQ tree entirely and can never match the lexical root.
405
- */
406
- it("attributes a symlinked checkout reached by its physical path", async () => {
407
- hqRoot = setupHqRoot(SHAPE);
408
- const physical = fs.mkdtempSync(path.join(os.tmpdir(), "hq-physical-repo-"));
409
- fs.mkdirSync(path.join(hqRoot, "repos/private"), { recursive: true });
410
- try {
411
- fs.symlinkSync(physical, path.join(hqRoot, "repos/private/hq-cloud"), "dir");
412
- } catch {
413
- return; // symlinks unavailable (e.g. unprivileged Windows) — skip
414
- }
415
-
416
- const map = await buildRepoCompanyMap(hqRoot);
417
- try {
418
- expect(resolveCompanyForCwd(fs.realpathSync(physical), map)).toBe(ACME);
419
- // The lexical path must keep working too.
420
- expect(
421
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-cloud"), map),
422
- ).toBe(ACME);
423
- } finally {
424
- fs.rmSync(physical, { recursive: true, force: true });
425
- }
426
- });
427
-
428
- /**
429
- * Fail CLOSED: when two companies claim the same path, attributing to
430
- * whichever the manifest listed first would be a cross-tenant
431
- * misattribution. Unattributed is the correct answer.
432
- */
433
- it("leaves a path claimed by two companies unattributed", async () => {
434
- const AMBIGUOUS = `companies:
435
- acme:
436
- name: Acme
437
- repos:
438
- - shared-repo
439
- cloud_uid: ${ACME}
440
- beta:
441
- name: Beta
442
- repos:
443
- - shared-repo
444
- cloud_uid: ${BETA}
445
- `;
446
- hqRoot = setupHqRoot(AMBIGUOUS);
447
- mkdirp("repos/private/shared-repo");
448
-
449
- const map = await buildRepoCompanyMap(hqRoot);
450
- expect(
451
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/shared-repo"), map),
452
- ).toBeUndefined();
453
- });
454
-
455
- it("an ambiguous repo does not poison that company's other repos", async () => {
456
- const AMBIGUOUS = `companies:
457
- acme:
458
- name: Acme
459
- repos:
460
- - shared-repo
461
- - acme-only
462
- cloud_uid: ${ACME}
463
- beta:
464
- name: Beta
465
- repos:
466
- - shared-repo
467
- cloud_uid: ${BETA}
468
- `;
469
- hqRoot = setupHqRoot(AMBIGUOUS);
470
- mkdirp("repos/private/shared-repo");
471
- mkdirp("repos/private/acme-only");
472
-
473
- const map = await buildRepoCompanyMap(hqRoot);
474
- expect(
475
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/acme-only"), map),
476
- ).toBe(ACME);
477
- });
478
-
479
- it("reports the probed case-folding behaviour of the real filesystem", async () => {
480
- hqRoot = setupHqRoot(SHAPE);
481
- const map = await buildRepoCompanyMap(hqRoot);
482
- expect(typeof map.foldsCase).toBe("boolean");
483
- // Whatever the probe found, an exactly-cased path always resolves.
484
- mkdirp("repos/private/hq-cloud");
485
- const fresh = await buildRepoCompanyMap(hqRoot);
486
- expect(
487
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-cloud"), fresh),
488
- ).toBe(ACME);
489
- });
490
- });
491
-
492
- /**
493
- * A worktree that no longer exists on disk.
494
- *
495
- * `buildRepoCompanyMap` discovers worktrees by reading the directory, which
496
- * only sees what exists at build time. Telemetry, though, is read from a
497
- * cursor — and a branch worktree is routinely deleted before the next pass,
498
- * while pending JSONL rows still carry its cwd. Since a successful POST
499
- * advances the cursor permanently, failing to resolve those rows loses the
500
- * attribution for good.
501
- */
502
- describe("worktree paths that no longer exist on disk", () => {
503
- const ACME = "cmp_01ACME";
504
- const SHAPE = `companies:
505
- acme:
506
- name: Acme
507
- path: companies/acme
508
- repos:
509
- - hq-cloud
510
- cloud_uid: ${ACME}
511
- `;
512
-
513
- let hqRoot: string;
514
- afterEach(() => {
515
- if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
516
- });
517
-
518
- it("resolves a deleted worktree's cwd by its documented naming convention", async () => {
519
- hqRoot = setupHqRoot(SHAPE);
520
- fs.mkdirSync(path.join(hqRoot, "repos/private/hq-cloud"), { recursive: true });
521
- // NOTE: the worktree is deliberately NOT created — it was cleaned up before
522
- // this telemetry pass, exactly as in the real failure.
523
- const map = await buildRepoCompanyMap(hqRoot);
524
-
525
- const gone = path.join(hqRoot, "repos/private/hq-cloud-wt-feature-gone");
526
- expect(resolveCompanyForCwd(gone, map)).toBe(ACME);
527
- expect(resolveCompanyForCwd(path.join(gone, "src/lib"), map)).toBe(ACME);
528
- });
529
-
530
- it("does not let the worktree fallback rescue an unrelated sibling", async () => {
531
- hqRoot = setupHqRoot(SHAPE);
532
- fs.mkdirSync(path.join(hqRoot, "repos/private/hq-cloud"), { recursive: true });
533
- const map = await buildRepoCompanyMap(hqRoot);
534
-
535
- // `-other` is not the `-wt-` convention, so it stays a foreign sibling...
536
- expect(
537
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-cloud-other"), map),
538
- ).toBeUndefined();
539
- // ...and a `-wt-` suffix on a repo nobody owns resolves to nothing.
540
- expect(
541
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/stranger-wt-x"), map),
542
- ).toBeUndefined();
543
- });
544
- });
545
-
546
- /**
547
- * Fail-closed must survive the worktree fallback.
548
- *
549
- * The fallback collapses `<repo>-wt-<branch>` to `<repo>`. If the worktree-
550
- * shaped path was itself dropped for ambiguity, collapsing it would hand the
551
- * event to whoever owns the base repo — quietly undoing the protection.
552
- */
553
- describe("ambiguity is not escapable via the worktree fallback", () => {
554
- const ACME = "cmp_01ACME";
555
- const BETA = "cmp_01BETA";
556
-
557
- let hqRoot: string;
558
- afterEach(() => {
559
- if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
560
- });
561
-
562
- it("keeps an ambiguous `<repo>-wt-*` path unattributed instead of collapsing it", async () => {
563
- // Acme owns `foo`; Beta owns a real repo that happens to be named
564
- // `foo-wt-team`, which also looks like a worktree of Acme's `foo`.
565
- hqRoot = setupHqRoot(`companies:
566
- acme:
567
- name: Acme
568
- repos:
569
- - foo
570
- cloud_uid: ${ACME}
571
- beta:
572
- name: Beta
573
- repos:
574
- - foo-wt-team
575
- cloud_uid: ${BETA}
576
- `);
577
- fs.mkdirSync(path.join(hqRoot, "repos/private/foo"), { recursive: true });
578
- fs.mkdirSync(path.join(hqRoot, "repos/private/foo-wt-team"), { recursive: true });
579
-
580
- const map = await buildRepoCompanyMap(hqRoot);
581
- const contested = path.join(hqRoot, "repos/private/foo-wt-team");
582
-
583
- expect(resolveCompanyForCwd(contested, map)).toBeUndefined();
584
- expect(resolveCompanyForCwd(path.join(contested, "src"), map)).toBeUndefined();
585
- // The unambiguous base repo still resolves.
586
- expect(resolveCompanyForCwd(path.join(hqRoot, "repos/private/foo"), map)).toBe(ACME);
587
- });
588
- });
589
-
590
- /**
591
- * `path` is documented as HQ-root-relative, but the manifest is synced and
592
- * hand-editable. A path that escapes the root would claim a directory spanning
593
- * other tenants and stamp this company's uid on everything beneath it.
594
- */
595
- describe("company paths that escape the HQ root are rejected", () => {
596
- const ACME = "cmp_01ACME";
597
-
598
- let hqRoot: string;
599
- afterEach(() => {
600
- if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
601
- });
602
-
603
- it.each([[" .."], ["."], ["/"]])("ignores path: %s", async (badPath) => {
604
- hqRoot = setupHqRoot(`companies:
605
- acme:
606
- name: Acme
607
- path: "${badPath.trim()}"
608
- repos: []
609
- cloud_uid: ${ACME}
610
- `);
611
- const map = await buildRepoCompanyMap(hqRoot);
612
-
613
- // Neither the HQ root nor its parent may be claimed.
614
- expect(resolveCompanyForCwd(hqRoot, map)).toBeUndefined();
615
- expect(resolveCompanyForCwd(path.dirname(hqRoot), map)).toBeUndefined();
616
- expect(resolveCompanyForCwd(path.join(hqRoot, "companies/other"), map)).toBeUndefined();
617
- });
618
- });