@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,496 +0,0 @@
1
- /**
2
- * Personal-vault scope helpers — shared between the menubar runner
3
- * (`hq-sync-runner`) and the `hq sync` CLI so every composer of a personal
4
- * push uses the same exclusion list.
5
- *
6
- * The exclusion list mirrors the Rust constant of the same name in
7
- * `hq-sync/src-tauri/src/commands/personal.rs` so the Tauri menubar's
8
- * first-push and this Node engine's steady-state push enforce identical
9
- * scope. Every other top-level entry under hq_root (e.g. `.claude/`,
10
- * `knowledge/`, `modules/`, `README.md`, `.codex/`, `core/`, `data/`,
11
- * `personal/`) is included, subject to the usual `.hqignore` filter.
12
- *
13
- * Excluded entries (and why):
14
- * - `.git`: a git repo's own metadata is hostile to multi-machine
15
- * sync; .gitignore alone doesn't cover `.git/` because it's the repo
16
- * itself, not a tracked path.
17
- * - `companies/`: the top-level `companies/` directory is never enumerated
18
- * wholesale. Team-backed companies are synced by the runner's
19
- * per-membership fanout (one bucket per company). Individual
20
- * `companies/{slug}/` subdirs are added back via
21
- * `computePersonalCompanySubdirs()` for every LOCAL (non-cloud) company
22
- * — i.e. any company that is not in the operator's team-synced membership
23
- * set and is not explicitly marked `cloud: true` in `company.yaml`. Those
24
- * land under the personal bucket as `companies/{slug}/...` keys. (The
25
- * single `companies/manifest.yaml` is also carved in unconditionally as
26
- * the routing source-of-truth.)
27
- * - `repos/`, `workspace/`: per user directive — heavy local-only
28
- * content (cloned remotes, session threads) that has no business in
29
- * the personal vault. Three explicit sub-paths pierce this exclusion and
30
- * DO round-trip: `workspace/threads/handoff.json` (+ its active thread) via
31
- * {@link computeContinuityPointerPaths}, `workspace/agency/` via
32
- * {@link computeAgencySyncPaths}, and `workspace/.session-logs/` (the
33
- * reindex-captured Claude Code transcripts) via
34
- * {@link computeSessionLogsSyncPaths}.
35
- *
36
- * Note: `core/`, `data/`, and `personal/` were previously excluded but are
37
- * INCLUDED as of user directive 2026-05-13. `core/` ships the hq-core
38
- * scaffold — policies/, settings/, skills/, workers/, plus the rules
39
- * manifest at core/core.yaml. `data/` and `personal/` carry per-user data,
40
- * policies, hooks, and skills that follow the user across machines. A
41
- * root-level `core.yaml` (at hq_root, distinct from `core/core.yaml`) is
42
- * filtered layout-aware in `createIgnoreFilter`: excluded only when
43
- * `core/core.yaml` exists (v15+, where root core.yaml is a stale duplicate);
44
- * on v12–v14 roots with no `core/core.yaml`, the root `core.yaml` IS the
45
- * scaffold/version manifest and round-trips like any other top-level file.
46
- */
47
-
48
- import * as fs from "fs";
49
- import * as path from "path";
50
- import { isGeneratedCommandWrapperPath } from "./personal-vault-exclusions.js";
51
-
52
- export const PERSONAL_VAULT_EXCLUDED_TOP_LEVEL: readonly string[] = [
53
- ".git",
54
- "companies",
55
- "repos",
56
- "workspace",
57
- ];
58
-
59
- /**
60
- * Company slugs that are never eligible for the personal-bucket fallback,
61
- * regardless of their `cloud:` marker. `_template` is the scaffolding
62
- * source for `/newcompany` — copying it into the personal vault would
63
- * pollute every machine's vault with the template tree.
64
- */
65
- export const PERSONAL_VAULT_COMPANY_EXCLUDED_SLUGS: readonly string[] = [
66
- "_template",
67
- ];
68
-
69
- export interface PersonalVaultOptions {
70
- /**
71
- * Slugs of companies that already have their own team bucket (i.e. the
72
- * operator has an active Membership row for them). These are excluded
73
- * from the personal-bucket fallback so a single company's content never
74
- * ends up in two buckets. This membership-derived set is the single
75
- * "cloud-backed" signal: when a company becomes cloud-true (gains an
76
- * active membership), its slug lands here and the personal vault both
77
- * stops pushing it and decommissions its stale copy.
78
- */
79
- teamSyncedSlugs?: ReadonlySet<string>;
80
- }
81
-
82
- /**
83
- * Compute absolute paths to share for the personal vault.
84
- *
85
- * Two sources are concatenated:
86
- * 1. Every top-level entry under `hqRoot` whose basename is NOT in
87
- * `PERSONAL_VAULT_EXCLUDED_TOP_LEVEL`.
88
- * 2. Every local (non-cloud) `companies/{slug}/` subdir, after excluding
89
- * (a) `_template`, (b) slugs already in `opts.teamSyncedSlugs`
90
- * (cloud-backed — they sync to their own team bucket), (c) slugs with
91
- * `cloud_uid: cmp_*` in `companies/manifest.yaml`, and (d) any
92
- * subdir explicitly marked `cloud: true` in its `company.yaml`.
93
- * A company with no `company.yaml` (or one without a `cloud:` marker)
94
- * is included — local companies sync to the personal vault by default.
95
- *
96
- * Order is whatever `fs.readdirSync` returns — share() doesn't care, and
97
- * the per-file walk inside share() handles recursion uniformly. Missing
98
- * hqRoot returns []; callers treat that as "no personal content to push"
99
- * rather than a hard error.
100
- */
101
- /**
102
- * S3 key (hq-root-relative, forward-slash) of the companies manifest — the
103
- * routing source-of-truth — carved into the personal vault even though
104
- * `companies/` is otherwise excluded. Exported so the PULL plan applies the
105
- * SAME exemption: skipping it on the pull leaves it unjournaled, which re-fires
106
- * a transient push-side conflict every sync (no journal baseline).
107
- */
108
- export const PERSONAL_VAULT_MANIFEST_KEY = "companies/manifest.yaml";
109
-
110
- /**
111
- * Return true for a path where HQ materializes generated links into the
112
- * release-owned `core/` surface. The caller must additionally establish that
113
- * the entry is a symlink: regular files in these namespaces remain canonical
114
- * core content and must continue to sync.
115
- *
116
- * The generated links point at canonical content in `personal/` or
117
- * `core/packages/`. Reindex / package wiring may remove and recreate them, so
118
- * treating the links themselves as personal-vault data creates a pull →
119
- * reindex → pull oscillation.
120
- */
121
- export function isGeneratedCoreMirrorKey(key: string): boolean {
122
- const parts = key.split("\\").join("/").replace(/^\.\//, "").split("/");
123
- return (
124
- parts.length >= 3 &&
125
- parts[0] === "core" &&
126
- (parts[1] === "knowledge" ||
127
- parts[1] === "policies" ||
128
- parts[1] === "workers" ||
129
- parts[1] === "settings")
130
- );
131
- }
132
-
133
- /**
134
- * Release-owned scaffold and reindex-generated wrappers must never be
135
- * delete-propagated from the personal vault. They are recreated locally by a
136
- * release or `hq reindex`, so an ordinary local absence is not user intent.
137
- */
138
- export function isPersonalVaultDeletionExcluded(key: string): boolean {
139
- const normalized = key.split("\\").join("/").replace(/^\.\//, "");
140
- return (
141
- normalized === "core" ||
142
- normalized.startsWith("core/") ||
143
- normalized === ".claude/skills" ||
144
- normalized.startsWith(".claude/skills/") ||
145
- // reindex surfaces slash commands as `.claude/commands/<ns>:<cmd>.md`
146
- // links. Unlike `.claude/skills/` (entirely generated, so blanket-excluded
147
- // above), `.claude/commands/` may ALSO hold hand-authored native commands
148
- // whose deletion IS user intent — so protect ONLY the reindex-generated
149
- // `<ns>:<cmd>` links from delete-propagation, matching the same shape the
150
- // push-side `generated-command-wrapper` exclusion filters.
151
- isGeneratedCommandWrapperPath(normalized)
152
- );
153
- }
154
-
155
- export function computePersonalVaultPaths(
156
- hqRoot: string,
157
- opts: PersonalVaultOptions = {},
158
- ): string[] {
159
- let entries: string[];
160
- try {
161
- entries = fs.readdirSync(hqRoot);
162
- } catch {
163
- return [];
164
- }
165
- const topLevel = entries
166
- .filter((name) => !PERSONAL_VAULT_EXCLUDED_TOP_LEVEL.includes(name))
167
- .map((name) => path.join(hqRoot, name));
168
- // companies/manifest.yaml is the routing source-of-truth (which slugs
169
- // exist, which are cloud-backed, what their UIDs are). It must travel
170
- // with every machine's personal vault so a fresh install can enumerate
171
- // expected non-cloud companies before its first pull. Special-cased
172
- // because the parent `companies/` is in PERSONAL_VAULT_EXCLUDED_TOP_LEVEL
173
- // (we never enumerate the whole companies tree wholesale).
174
- const manifest: string[] = [];
175
- const manifestPath = path.join(hqRoot, PERSONAL_VAULT_MANIFEST_KEY);
176
- try {
177
- if (fs.statSync(manifestPath).isFile()) {
178
- manifest.push(manifestPath);
179
- }
180
- } catch {
181
- // Missing or unreadable — silently omit. Callers tolerate empty arrays.
182
- }
183
- const companySubdirs = computePersonalCompanySubdirs(
184
- hqRoot,
185
- opts.teamSyncedSlugs,
186
- );
187
- const continuity = computeContinuityPointerPaths(hqRoot);
188
- const agency = computeAgencySyncPaths(hqRoot);
189
- const sessionLogs = computeSessionLogsSyncPaths(hqRoot);
190
- return [
191
- ...topLevel,
192
- ...manifest,
193
- ...companySubdirs,
194
- ...continuity,
195
- ...agency,
196
- ...sessionLogs,
197
- ];
198
- }
199
-
200
- /**
201
- * Fixed relative path (forward-slash, hq-root-relative) of the session
202
- * continuity pointer. The continuity pointer is the ONE file under the
203
- * otherwise machine-local `workspace/` that must travel across machines:
204
- * `/handoff` writes it on machine A so a fresh session on machine B can
205
- * resume via `/startwork`. See {@link computeContinuityPointerPaths}.
206
- */
207
- export const CONTINUITY_POINTER_REL = "workspace/threads/handoff.json";
208
-
209
- /**
210
- * Compute the absolute paths of the session-continuity pointer carve-out:
211
- * `workspace/threads/handoff.json` plus the single thread file it points to.
212
- *
213
- * `workspace/` is in {@link PERSONAL_VAULT_EXCLUDED_TOP_LEVEL} — it is
214
- * machine-local by design (session scratch, locks, reports, the full thread
215
- * history). The continuity pointer is the one exception: without it a
216
- * `/handoff` on one machine never reaches a second machine, so the session
217
- * pointer doesn't follow the user even though the durable output already
218
- * syncs. This carve-out pierces the exclusion for EXACTLY two files and
219
- * nothing else, mirroring the `companies/manifest.yaml` special-case above
220
- * (a single file pushed back in despite its parent dir being excluded).
221
- *
222
- * The "active thread file" is not a fixed name — it is resolved from
223
- * `handoff.json.thread_path` (the pointer the finalize script writes). We
224
- * read + parse `handoff.json`, then include `thread_path` ONLY when it is a
225
- * relative path that resolves to an existing regular file strictly within
226
- * `<hqRoot>/workspace/threads/`. This containment check is a hard security
227
- * boundary: a malformed or tampered `handoff.json` must never be able to
228
- * smuggle an arbitrary file (e.g. `../../.env`, an absolute path, or a
229
- * symlink escaping the threads dir) into the personal vault.
230
- *
231
- * Fail-soft throughout: a missing/unreadable/malformed `handoff.json`, or an
232
- * out-of-bounds / missing `thread_path`, silently degrades to "include
233
- * whatever is valid" (handoff.json alone, or `[]`). Callers tolerate empty
234
- * arrays — same contract as the manifest special-case.
235
- */
236
- export function computeContinuityPointerPaths(hqRoot: string): string[] {
237
- const out: string[] = [];
238
- const threadsDir = path.join(hqRoot, "workspace", "threads");
239
- const handoffPath = path.join(threadsDir, "handoff.json");
240
-
241
- let raw: string;
242
- try {
243
- if (!fs.statSync(handoffPath).isFile()) return out;
244
- raw = fs.readFileSync(handoffPath, "utf8");
245
- } catch {
246
- // No pointer on this machine yet (or unreadable) — nothing to carry.
247
- return out;
248
- }
249
- out.push(handoffPath);
250
-
251
- // Resolve the active thread file from the pointer's `thread_path`. Any
252
- // failure leaves the pointer itself in `out` and skips the thread body —
253
- // the next handoff (or a peer's push) re-converges it.
254
- let threadPath: unknown;
255
- try {
256
- threadPath = (JSON.parse(raw) as { thread_path?: unknown })?.thread_path;
257
- } catch {
258
- return out;
259
- }
260
- if (typeof threadPath !== "string" || threadPath.length === 0) return out;
261
-
262
- // Containment: the resolved file must live strictly inside the threads dir.
263
- // Reject absolute paths, traversal, and symlink escapes. thread_path is
264
- // hq-root-relative as written by handoff-finalize.sh, so resolve against
265
- // hqRoot and re-check the realpath is still under the threads dir.
266
- if (path.isAbsolute(threadPath)) return out;
267
- const resolvedThreads = path.resolve(threadsDir);
268
- const candidate = path.resolve(hqRoot, threadPath);
269
- const withinThreads = (p: string): boolean =>
270
- p === resolvedThreads || p.startsWith(resolvedThreads + path.sep);
271
- if (!withinThreads(candidate)) return out;
272
- try {
273
- const realHqRoot = fs.realpathSync.native(hqRoot);
274
- const expectedRealThreads = path.join(realHqRoot, "workspace", "threads");
275
- const realThreads = fs.realpathSync.native(threadsDir);
276
- if (realThreads !== expectedRealThreads) return out;
277
- const withinRealThreads = (p: string): boolean =>
278
- p === realThreads || p.startsWith(realThreads + path.sep);
279
- const real = fs.realpathSync.native(candidate);
280
- if (!withinRealThreads(real)) return out;
281
- if (!fs.statSync(real).isFile()) return out;
282
- } catch {
283
- // Pointer references a thread file that doesn't exist here — skip it.
284
- return out;
285
- }
286
- out.push(candidate);
287
- return out;
288
- }
289
-
290
- /**
291
- * Fixed relative path (forward-slash, hq-root-relative) of the agency
292
- * workspace subtree. See {@link computeAgencySyncPaths}.
293
- */
294
- export const AGENCY_SYNC_REL = "workspace/agency";
295
-
296
- /**
297
- * Compute the absolute path of the agency-workspace carve-out:
298
- * `workspace/agency/` (the whole subtree).
299
- *
300
- * Like the session-continuity pointer above, this pierces the `workspace/`
301
- * top-level exclusion for ONE specific subtree. hq-pack-agency teams keep
302
- * their cross-session chat.jsonl inboxes and roster/state under
303
- * `workspace/agency/<company>/<team>/`; that state must travel across machines
304
- * so an agency team picked up on machine B sees the same conversation and
305
- * roster it had on machine A. The rest of `workspace/` (session scratch,
306
- * locks, reports, full thread history) stays machine-local.
307
- *
308
- * Returns the directory path when it exists; share()'s per-file walk handles
309
- * recursion, and the nested personal-vault exclusions still apply to files
310
- * inside it (e.g. a stray `node_modules/` or `.env`). Fail-soft: a missing or
311
- * unreadable `workspace/agency/` returns []. Callers tolerate empty arrays —
312
- * same contract as the manifest + continuity special-cases.
313
- */
314
- export function computeAgencySyncPaths(hqRoot: string): string[] {
315
- const agencyDir = path.join(hqRoot, "workspace", "agency");
316
- try {
317
- if (fs.statSync(agencyDir).isDirectory()) return [agencyDir];
318
- } catch {
319
- // No agency workspace on this machine — nothing to carry.
320
- }
321
- return [];
322
- }
323
-
324
- /**
325
- * Fixed relative path (forward-slash, hq-root-relative) of the session-logs
326
- * subtree. See {@link computeSessionLogsSyncPaths}.
327
- */
328
- export const SESSION_LOGS_SYNC_REL = "workspace/.session-logs";
329
-
330
- /**
331
- * Compute the absolute path of the session-logs carve-out:
332
- * `workspace/.session-logs/` (the whole subtree).
333
- *
334
- * `hq reindex` copies this HQ root's Claude Code session transcripts here (see
335
- * reindex's copySessionLogs). Like the agency carve-out above, this pierces the
336
- * `workspace/` top-level exclusion for ONE specific subtree so those raw
337
- * transcripts round-trip to the personal vault / S3 — the operator's explicit
338
- * directive is that the session logs travel with the vault. The rest of
339
- * `workspace/` (session scratch, locks, reports, worktrees) stays machine-local.
340
- *
341
- * Returns the directory path when it exists; share()'s per-file walk handles
342
- * recursion, and the nested personal-vault exclusions still apply to files
343
- * inside it. Fail-soft: a missing or unreadable `workspace/.session-logs/`
344
- * returns []. Callers tolerate empty arrays — same contract as the manifest,
345
- * continuity, and agency special-cases.
346
- */
347
- export function computeSessionLogsSyncPaths(hqRoot: string): string[] {
348
- const sessionLogsDir = path.join(hqRoot, "workspace", ".session-logs");
349
- try {
350
- if (fs.statSync(sessionLogsDir).isDirectory()) return [sessionLogsDir];
351
- } catch {
352
- // No session logs captured on this machine yet — nothing to carry.
353
- }
354
- return [];
355
- }
356
-
357
- /**
358
- * Discover `companies/{slug}/` subdirs that should sync to the personal
359
- * bucket as a fallback for companies the operator has not designated as
360
- * team-backed. Filter rules (all must hold):
361
- *
362
- * 1. The subdir is a directory (skip stray files).
363
- * 2. The slug is NOT in `PERSONAL_VAULT_COMPANY_EXCLUDED_SLUGS`
364
- * (currently just `_template`).
365
- * 3. The slug is NOT in `teamSyncedSlugs` (membership-backed slugs sync
366
- * to their own bucket and must not be double-written).
367
- * 4. The slug is NOT listed with a `cloud_uid: cmp_*` in
368
- * `companies/manifest.yaml`. Full-HQ agent boxes often run
369
- * `--personal` alone (no company fanout targets), so
370
- * `teamSyncedSlugs` is empty even though indigo (etc.) is
371
- * cloud-backed; without this check the multi-GB company tree is
372
- * re-mirrored into the agent personal vault every ritual and
373
- * times out the 180s personal push.
374
- * 5. `companies/{slug}/company.yaml` does NOT contain an explicit
375
- * `cloud: true` line. Local companies sync to the personal vault by
376
- * default, so a missing/markerless `company.yaml` is INCLUDED; only an
377
- * explicit `cloud: true` marker opts the directory OUT (belt-and-braces
378
- * with `teamSyncedSlugs` / manifest `cloud_uid` for a company designated
379
- * cloud-backed before its membership has resolved).
380
- *
381
- * Returns absolute paths.
382
- */
383
- export function computePersonalCompanySubdirs(
384
- hqRoot: string,
385
- teamSyncedSlugs: ReadonlySet<string> = new Set(),
386
- ): string[] {
387
- const companiesRoot = path.join(hqRoot, "companies");
388
- let slugs: string[];
389
- try {
390
- slugs = fs.readdirSync(companiesRoot);
391
- } catch {
392
- return [];
393
- }
394
- // Manifest cloud_uid is the durable routing signal for cloud-backed
395
- // companies. Prefer it over relying solely on runner membership fanout
396
- // (empty for --personal-only agent ritual) or company.yaml (often missing
397
- // on full-HQ agent boxes that never ran /designate-team locally).
398
- const cloudManifestSlugs = readCloudBackedSlugsFromManifest(hqRoot);
399
- const eligible: string[] = [];
400
- for (const slug of slugs) {
401
- if (PERSONAL_VAULT_COMPANY_EXCLUDED_SLUGS.includes(slug)) continue;
402
- if (teamSyncedSlugs.has(slug)) continue;
403
- if (cloudManifestSlugs.has(slug)) continue;
404
- const subdir = path.join(companiesRoot, slug);
405
- let isDir = false;
406
- try {
407
- isDir = fs.statSync(subdir).isDirectory();
408
- } catch {
409
- continue;
410
- }
411
- if (!isDir) continue;
412
- if (companyHasCloudTrueMarker(subdir)) continue;
413
- eligible.push(subdir);
414
- }
415
- return eligible;
416
- }
417
-
418
- /**
419
- * Slugs whose `companies/manifest.yaml` entry carries a `cloud_uid: cmp_*`.
420
- * These are cloud-backed team companies and must not personal-vault mirror.
421
- *
422
- * Lightweight line parse (no YAML dep): fail-open on missing/unreadable
423
- * manifest → empty set → companies stay eligible for personal fallback.
424
- * Only `cmp_*` uids count (same rule as company-resolver / telemetry).
425
- */
426
- export function readCloudBackedSlugsFromManifest(hqRoot: string): Set<string> {
427
- const manifestPath = path.join(hqRoot, "companies", "manifest.yaml");
428
- let text: string;
429
- try {
430
- text = fs.readFileSync(manifestPath, "utf8");
431
- } catch {
432
- return new Set();
433
- }
434
- const out = new Set<string>();
435
- let currentSlug: string | null = null;
436
- let inCompanies = false;
437
- for (const line of text.split(/\r?\n/)) {
438
- // Top-level key (no indent). Only track entries under `companies:`.
439
- if (/^[A-Za-z0-9_.-]+:\s*(?:#.*)?$/.test(line)) {
440
- inCompanies = /^companies:\s*(?:#.*)?$/.test(line);
441
- currentSlug = null;
442
- continue;
443
- }
444
- if (!inCompanies) continue;
445
- // Two-space indent: company slug under companies:
446
- const slugMatch = /^ {2}([A-Za-z0-9][A-Za-z0-9_-]*):\s*(?:#.*)?$/.exec(line);
447
- if (slugMatch) {
448
- currentSlug = slugMatch[1];
449
- continue;
450
- }
451
- if (
452
- currentSlug &&
453
- /^\s{2,}cloud_uid:\s*cmp_[A-Za-z0-9]+\b/.test(line)
454
- ) {
455
- out.add(currentSlug);
456
- // Keep currentSlug so a later key on the same company is fine; next
457
- // sibling slug line will replace it.
458
- }
459
- }
460
- return out;
461
- }
462
-
463
- /**
464
- * True iff `{subdir}/company.yaml` exists and contains an explicit
465
- * top-level `cloud: true` line — the marker `/designate-team` writes when a
466
- * company is promoted to cloud-backed. That command's awk is the only
467
- * producer of this key, and it always emits `cloud: <bool>` at column zero
468
- * with no indentation, no quoting, lowercase boolean, optional trailing
469
- * comment. A `true` marker excludes the directory from the personal-vault
470
- * fallback; everything else (missing file, no marker, `cloud: false`)
471
- * leaves it eligible — local companies sync to the personal vault by default.
472
- *
473
- * Anchored at column 0 deliberately:
474
- * - rejects `meta:\n cloud: true` (nested key, would be `meta.cloud`
475
- * in a real YAML parser — false positive for us if we allowed
476
- * arbitrary leading whitespace)
477
- * - rejects `- cloud: true` (list item, also a nested key)
478
- * - rejects flow mappings like `{ cloud: true }`
479
- *
480
- * Other intentional rejections (lowercase `true` only, no YAML-alt
481
- * boolean spellings): `cloud: True`, `cloud: TRUE`, `cloud: yes`,
482
- * `cloud: on`, `cloud: "true"`, `cloud: 'true'`. None of these are
483
- * produced by `/designate-team`; matching them risks confusing
484
- * round-trips with hand-edited YAML. (A misspelled marker fails OPEN —
485
- * the company stays in the personal vault — which is the safe default.)
486
- */
487
- function companyHasCloudTrueMarker(subdir: string): boolean {
488
- const yamlPath = path.join(subdir, "company.yaml");
489
- let text: string;
490
- try {
491
- text = fs.readFileSync(yamlPath, "utf8");
492
- } catch {
493
- return false;
494
- }
495
- return /^cloud:\s*true\b/m.test(text);
496
- }