@indigoai-us/hq-cloud 6.14.50 → 6.15.1

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 (340) hide show
  1. package/dist/bin/sync-mutation.d.ts +14 -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 +64 -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/reindex-knowledge.d.ts +7 -0
  34. package/dist/cli/reindex-knowledge.d.ts.map +1 -0
  35. package/dist/cli/reindex-knowledge.js +389 -0
  36. package/dist/cli/reindex-knowledge.js.map +1 -0
  37. package/dist/cli/reindex-knowledge.test.d.ts +12 -0
  38. package/dist/cli/reindex-knowledge.test.d.ts.map +1 -0
  39. package/dist/cli/reindex-knowledge.test.js +260 -0
  40. package/dist/cli/reindex-knowledge.test.js.map +1 -0
  41. package/dist/cli/reindex.d.ts.map +1 -1
  42. package/dist/cli/reindex.js +8 -0
  43. package/dist/cli/reindex.js.map +1 -1
  44. package/dist/cli/rescue-settings-reconcile.test.js +47 -0
  45. package/dist/cli/rescue-settings-reconcile.test.js.map +1 -1
  46. package/dist/cli/sync.d.ts +27 -0
  47. package/dist/cli/sync.d.ts.map +1 -1
  48. package/dist/cli/sync.js +52 -0
  49. package/dist/cli/sync.js.map +1 -1
  50. package/dist/cli/sync.test.js +31 -1
  51. package/dist/cli/sync.test.js.map +1 -1
  52. package/dist/index.d.ts +2 -0
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +1 -0
  55. package/dist/index.js.map +1 -1
  56. package/dist/skill-telemetry.d.ts +6 -0
  57. package/dist/skill-telemetry.d.ts.map +1 -1
  58. package/dist/skill-telemetry.js +14 -2
  59. package/dist/skill-telemetry.js.map +1 -1
  60. package/dist/skill-telemetry.test.js +79 -0
  61. package/dist/skill-telemetry.test.js.map +1 -1
  62. package/dist/sync/candidate-uploader.d.ts +88 -0
  63. package/dist/sync/candidate-uploader.d.ts.map +1 -0
  64. package/dist/sync/candidate-uploader.js +212 -0
  65. package/dist/sync/candidate-uploader.js.map +1 -0
  66. package/dist/sync/candidate-uploader.test.d.ts +2 -0
  67. package/dist/sync/candidate-uploader.test.d.ts.map +1 -0
  68. package/dist/sync/candidate-uploader.test.js +132 -0
  69. package/dist/sync/candidate-uploader.test.js.map +1 -0
  70. package/dist/sync/delta-client.d.ts +73 -0
  71. package/dist/sync/delta-client.d.ts.map +1 -0
  72. package/dist/sync/delta-client.js +201 -0
  73. package/dist/sync/delta-client.js.map +1 -0
  74. package/dist/sync/delta-client.test.d.ts +2 -0
  75. package/dist/sync/delta-client.test.d.ts.map +1 -0
  76. package/dist/sync/delta-client.test.js +97 -0
  77. package/dist/sync/delta-client.test.js.map +1 -0
  78. package/dist/sync/durable-apply.d.ts +76 -0
  79. package/dist/sync/durable-apply.d.ts.map +1 -0
  80. package/dist/sync/durable-apply.js +530 -0
  81. package/dist/sync/durable-apply.js.map +1 -0
  82. package/dist/sync/durable-apply.test.d.ts +2 -0
  83. package/dist/sync/durable-apply.test.d.ts.map +1 -0
  84. package/dist/sync/durable-apply.test.js +180 -0
  85. package/dist/sync/durable-apply.test.js.map +1 -0
  86. package/dist/sync/event-sync.d.ts +33 -1
  87. package/dist/sync/event-sync.d.ts.map +1 -1
  88. package/dist/sync/event-sync.js +149 -1
  89. package/dist/sync/event-sync.js.map +1 -1
  90. package/dist/sync/event-sync.test.js +142 -1
  91. package/dist/sync/event-sync.test.js.map +1 -1
  92. package/dist/sync/index.d.ts +2 -0
  93. package/dist/sync/index.d.ts.map +1 -1
  94. package/dist/sync/index.js +1 -0
  95. package/dist/sync/index.js.map +1 -1
  96. package/dist/sync/multipart-uploader.d.ts +99 -0
  97. package/dist/sync/multipart-uploader.d.ts.map +1 -0
  98. package/dist/sync/multipart-uploader.js +447 -0
  99. package/dist/sync/multipart-uploader.js.map +1 -0
  100. package/dist/sync/multipart-uploader.test.d.ts +2 -0
  101. package/dist/sync/multipart-uploader.test.d.ts.map +1 -0
  102. package/dist/sync/multipart-uploader.test.js +119 -0
  103. package/dist/sync/multipart-uploader.test.js.map +1 -0
  104. package/dist/sync/mutation-client.d.ts +82 -0
  105. package/dist/sync/mutation-client.d.ts.map +1 -0
  106. package/dist/sync/mutation-client.js +221 -0
  107. package/dist/sync/mutation-client.js.map +1 -0
  108. package/dist/sync/mutation-client.test.d.ts +2 -0
  109. package/dist/sync/mutation-client.test.d.ts.map +1 -0
  110. package/dist/sync/mutation-client.test.js +51 -0
  111. package/dist/sync/mutation-client.test.js.map +1 -0
  112. package/dist/sync/push-receiver.d.ts +45 -0
  113. package/dist/sync/push-receiver.d.ts.map +1 -1
  114. package/dist/sync/push-receiver.js +101 -0
  115. package/dist/sync/push-receiver.js.map +1 -1
  116. package/dist/sync/push-receiver.test.js +54 -2
  117. package/dist/sync/push-receiver.test.js.map +1 -1
  118. package/dist/sync/scope-inventory-client.d.ts +69 -0
  119. package/dist/sync/scope-inventory-client.d.ts.map +1 -0
  120. package/dist/sync/scope-inventory-client.js +210 -0
  121. package/dist/sync/scope-inventory-client.js.map +1 -0
  122. package/dist/sync/scope-inventory-client.test.d.ts +2 -0
  123. package/dist/sync/scope-inventory-client.test.d.ts.map +1 -0
  124. package/dist/sync/scope-inventory-client.test.js +94 -0
  125. package/dist/sync/scope-inventory-client.test.js.map +1 -0
  126. package/dist/sync/snapshot-client.d.ts +98 -0
  127. package/dist/sync/snapshot-client.d.ts.map +1 -0
  128. package/dist/sync/snapshot-client.js +402 -0
  129. package/dist/sync/snapshot-client.js.map +1 -0
  130. package/dist/sync/snapshot-client.test.d.ts +2 -0
  131. package/dist/sync/snapshot-client.test.d.ts.map +1 -0
  132. package/dist/sync/snapshot-client.test.js +169 -0
  133. package/dist/sync/snapshot-client.test.js.map +1 -0
  134. package/dist/sync/uploader-finalization.d.ts +97 -0
  135. package/dist/sync/uploader-finalization.d.ts.map +1 -0
  136. package/dist/sync/uploader-finalization.js +273 -0
  137. package/dist/sync/uploader-finalization.js.map +1 -0
  138. package/dist/sync/uploader-finalization.test.d.ts +2 -0
  139. package/dist/sync/uploader-finalization.test.d.ts.map +1 -0
  140. package/dist/sync/uploader-finalization.test.js +92 -0
  141. package/dist/sync/uploader-finalization.test.js.map +1 -0
  142. package/dist/telemetry.d.ts +11 -1
  143. package/dist/telemetry.d.ts.map +1 -1
  144. package/dist/telemetry.js +21 -2
  145. package/dist/telemetry.js.map +1 -1
  146. package/dist/telemetry.test.js +80 -0
  147. package/dist/telemetry.test.js.map +1 -1
  148. package/package.json +6 -1
  149. package/.claude/policies/hq-cloud-esm-cannot-spy-fs-builtins.md +0 -30
  150. package/.claude/policies/hq-cloud-strip-types-no-parameter-properties.md +0 -22
  151. package/.github/workflows/ci.yml +0 -84
  152. package/.github/workflows/publish.yml +0 -56
  153. package/.github/workflows/unreleased-commits-nag.yml +0 -256
  154. package/eslint.config.js +0 -67
  155. package/pnpm-workspace.yaml +0 -2
  156. package/scripts/presign-transport-e2e.mjs +0 -250
  157. package/scripts/vault-rebaseline.sh +0 -323
  158. package/scripts/vault-rescue.sh +0 -332
  159. package/src/active-company.test.ts +0 -188
  160. package/src/active-company.ts +0 -168
  161. package/src/agent-codex-instructions.test.ts +0 -332
  162. package/src/agent-codex-instructions.ts +0 -309
  163. package/src/auth.ts +0 -146
  164. package/src/backup-prune.test.ts +0 -98
  165. package/src/backup-prune.ts +0 -182
  166. package/src/bin/backup-prune-runner.ts +0 -33
  167. package/src/bin/rescue-runner.ts +0 -25
  168. package/src/bin/sync-runner-company.ts +0 -695
  169. package/src/bin/sync-runner-events.test.ts +0 -143
  170. package/src/bin/sync-runner-events.ts +0 -55
  171. package/src/bin/sync-runner-planning.test.ts +0 -311
  172. package/src/bin/sync-runner-planning.ts +0 -258
  173. package/src/bin/sync-runner-rollup.test.ts +0 -37
  174. package/src/bin/sync-runner-rollup.ts +0 -97
  175. package/src/bin/sync-runner-telemetry.ts +0 -15
  176. package/src/bin/sync-runner-watch-loop.ts +0 -1235
  177. package/src/bin/sync-runner-watch-routes.test.ts +0 -71
  178. package/src/bin/sync-runner-watch-routes.ts +0 -184
  179. package/src/bin/sync-runner.test.ts +0 -8767
  180. package/src/bin/sync-runner.ts +0 -2190
  181. package/src/cli/accept.ts +0 -124
  182. package/src/cli/conflict.ts +0 -119
  183. package/src/cli/doctor.test.ts +0 -581
  184. package/src/cli/doctor.ts +0 -642
  185. package/src/cli/index.ts +0 -49
  186. package/src/cli/invite.test.ts +0 -250
  187. package/src/cli/invite.ts +0 -214
  188. package/src/cli/promote.ts +0 -157
  189. package/src/cli/reindex.test.ts +0 -957
  190. package/src/cli/reindex.ts +0 -970
  191. package/src/cli/rescue-classify-ordering.test.ts +0 -548
  192. package/src/cli/rescue-clone-diagnostics.test.ts +0 -120
  193. package/src/cli/rescue-core.ts +0 -3011
  194. package/src/cli/rescue-drift-reconcile.test.ts +0 -179
  195. package/src/cli/rescue-drop-dir-symlink.test.ts +0 -224
  196. package/src/cli/rescue-exec-bit-preserve.test.ts +0 -187
  197. package/src/cli/rescue-hq-root-guard.test.ts +0 -232
  198. package/src/cli/rescue-journal-reconcile.test.ts +0 -215
  199. package/src/cli/rescue-mtime-preserve.test.ts +0 -203
  200. package/src/cli/rescue-settings-reconcile.test.ts +0 -583
  201. package/src/cli/rescue-snapshot.test.ts +0 -57
  202. package/src/cli/rescue-snapshot.ts +0 -51
  203. package/src/cli/rescue.reindex.test.ts +0 -63
  204. package/src/cli/rescue.test.ts +0 -131
  205. package/src/cli/rescue.ts +0 -182
  206. package/src/cli/share.test.ts +0 -7843
  207. package/src/cli/share.ts +0 -3663
  208. package/src/cli/sync-scope.test.ts +0 -652
  209. package/src/cli/sync.test.ts +0 -5207
  210. package/src/cli/sync.ts +0 -3470
  211. package/src/cli/tombstones.ts +0 -106
  212. package/src/cli/watch-event-push-conflict.test.ts +0 -234
  213. package/src/client-info.test.ts +0 -214
  214. package/src/client-info.ts +0 -121
  215. package/src/cognito-auth.test.ts +0 -712
  216. package/src/cognito-auth.ts +0 -1422
  217. package/src/company-resolver.test.ts +0 -618
  218. package/src/company-resolver.ts +0 -521
  219. package/src/context.test.ts +0 -583
  220. package/src/context.ts +0 -378
  221. package/src/daemon-worker.ts +0 -26
  222. package/src/daemon.ts +0 -99
  223. package/src/entity-resolver.test.ts +0 -315
  224. package/src/entity-resolver.ts +0 -180
  225. package/src/ignore.test.ts +0 -466
  226. package/src/ignore.ts +0 -469
  227. package/src/index.ts +0 -439
  228. package/src/journal.test.ts +0 -968
  229. package/src/journal.ts +0 -765
  230. package/src/lib/cloud-authoritative.test.ts +0 -45
  231. package/src/lib/cloud-authoritative.ts +0 -59
  232. package/src/lib/conflict-file.ts +0 -86
  233. package/src/lib/conflict-index.ts +0 -289
  234. package/src/lib/conflict.test.ts +0 -348
  235. package/src/lib/describe-error.test.ts +0 -100
  236. package/src/lib/describe-error.ts +0 -58
  237. package/src/lib/exit-codes.ts +0 -24
  238. package/src/lib/machine-id.test.ts +0 -231
  239. package/src/lib/machine-id.ts +0 -175
  240. package/src/lib/net-errors.test.ts +0 -65
  241. package/src/lib/net-errors.ts +0 -86
  242. package/src/lib/readlink-safe.test.ts +0 -43
  243. package/src/lib/readlink-safe.ts +0 -29
  244. package/src/local-path-codec.test.ts +0 -138
  245. package/src/local-path-codec.ts +0 -161
  246. package/src/machine-auth.test.ts +0 -1323
  247. package/src/manifest-reconcile.test.ts +0 -1123
  248. package/src/manifest-reconcile.ts +0 -518
  249. package/src/object-io.test.ts +0 -1221
  250. package/src/object-io.ts +0 -1306
  251. package/src/operation-lock.test.ts +0 -484
  252. package/src/operation-lock.ts +0 -680
  253. package/src/outcome-telemetry.test.ts +0 -498
  254. package/src/outcome-telemetry.ts +0 -639
  255. package/src/personal-vault-exclusions.test.ts +0 -308
  256. package/src/personal-vault-exclusions.ts +0 -354
  257. package/src/personal-vault.test.ts +0 -756
  258. package/src/personal-vault.ts +0 -496
  259. package/src/prefix-coalesce.test.ts +0 -240
  260. package/src/prefix-coalesce.ts +0 -273
  261. package/src/public-surface.test.ts +0 -117
  262. package/src/qmd-reindex.test.ts +0 -877
  263. package/src/qmd-reindex.ts +0 -842
  264. package/src/read-only-state-dir.test.ts +0 -188
  265. package/src/remote-pull.test.ts +0 -1130
  266. package/src/remote-pull.ts +0 -618
  267. package/src/s3.symlink-materialize.test.ts +0 -492
  268. package/src/s3.test.ts +0 -1789
  269. package/src/s3.ts +0 -1532
  270. package/src/schemas/signal-types.test.ts +0 -82
  271. package/src/schemas/signal-types.ts +0 -38
  272. package/src/schemas/source-channels.test.ts +0 -82
  273. package/src/schemas/source-channels.ts +0 -53
  274. package/src/scope-shrink.test.ts +0 -633
  275. package/src/scope-shrink.ts +0 -481
  276. package/src/signals/get.test.ts +0 -310
  277. package/src/signals/get.ts +0 -75
  278. package/src/signals/internals.ts +0 -195
  279. package/src/signals/list.test.ts +0 -420
  280. package/src/signals/list.ts +0 -79
  281. package/src/signals/parse.ts +0 -8
  282. package/src/signals/types.ts +0 -91
  283. package/src/skill-telemetry.test.ts +0 -1825
  284. package/src/skill-telemetry.ts +0 -1439
  285. package/src/sources/get.test.ts +0 -293
  286. package/src/sources/get.ts +0 -66
  287. package/src/sources/internals.ts +0 -198
  288. package/src/sources/list.test.ts +0 -402
  289. package/src/sources/list.ts +0 -84
  290. package/src/sources/parse.ts +0 -43
  291. package/src/sources/types.ts +0 -84
  292. package/src/sync/event-sync.test.ts +0 -594
  293. package/src/sync/event-sync.ts +0 -545
  294. package/src/sync/feature-flags.test.ts +0 -378
  295. package/src/sync/feature-flags.ts +0 -62
  296. package/src/sync/index.ts +0 -76
  297. package/src/sync/lease-client.test.ts +0 -128
  298. package/src/sync/lease-client.ts +0 -207
  299. package/src/sync/logger.test.ts +0 -242
  300. package/src/sync/logger.ts +0 -79
  301. package/src/sync/metrics.test.ts +0 -462
  302. package/src/sync/metrics.ts +0 -213
  303. package/src/sync/pull-scope.ts +0 -265
  304. package/src/sync/push-event.test.ts +0 -266
  305. package/src/sync/push-event.ts +0 -224
  306. package/src/sync/push-receiver.test.ts +0 -566
  307. package/src/sync/push-receiver.ts +0 -1048
  308. package/src/sync/push-transport.ts +0 -231
  309. package/src/sync/realtime-rollout.test.ts +0 -86
  310. package/src/sync/realtime-rollout.ts +0 -262
  311. package/src/sync/state-store.test.ts +0 -194
  312. package/src/sync/state-store.ts +0 -727
  313. package/src/sync-core.ts +0 -58
  314. package/src/sync-progress.test.ts +0 -94
  315. package/src/sync-progress.ts +0 -140
  316. package/src/telemetry-events.test.ts +0 -88
  317. package/src/telemetry-events.ts +0 -205
  318. package/src/telemetry.test.ts +0 -1280
  319. package/src/telemetry.ts +0 -1109
  320. package/src/types.ts +0 -314
  321. package/src/vault-client.test.ts +0 -1380
  322. package/src/vault-client.ts +0 -1694
  323. package/src/version.ts +0 -24
  324. package/src/watch-roots.test.ts +0 -278
  325. package/src/watch-roots.ts +0 -162
  326. package/src/watcher-event-gate.test.ts +0 -212
  327. package/src/watcher.test.ts +0 -1079
  328. package/src/watcher.ts +0 -1741
  329. package/test/e2e/sync/cross-tenant-isolation.test.ts +0 -630
  330. package/test/e2e/sync/skill-telemetry-oversized-transcript.test.ts +0 -124
  331. package/test/e2e/sync/transient-company-leg.test.ts +0 -384
  332. package/test/e2e/sync/windows-unreadable-link-leg.test.ts +0 -191
  333. package/test/e2e/watcher-real-chokidar.test.ts +0 -165
  334. package/test/e2e/watcher-recursive-backend.test.ts +0 -181
  335. package/test/e2e/watcher-scoped-coverage.test.ts +0 -381
  336. package/test/invite-flow.integration.test.ts +0 -244
  337. package/test/joiner-manifest-reconcile.integration.test.ts +0 -322
  338. package/test/share-sync.integration.test.ts +0 -213
  339. package/tsconfig.json +0 -19
  340. 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
- }