@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
package/src/ignore.ts DELETED
@@ -1,469 +0,0 @@
1
- /**
2
- * Ignore-file parser for cloud sync.
3
- *
4
- * Two modes:
5
- * - **Permissive (default)**: everything syncs except what ignore layers
6
- * subtract. Three layers stack (later overrides earlier):
7
- * 1. Built-in defaults — VCS, node_modules, build artifacts, caches,
8
- * env files. Covers the common stacks so a first-time sync over a
9
- * random project folder doesn't push `target/` or `.next/` to S3.
10
- * 2. Repo `.gitignore` at hqRoot — reuses existing exclusions so we
11
- * don't re-list every build directory. Root-level only.
12
- * 3. `.hqignore` (preferred) or `.hqsyncignore` (legacy) — sync-specific
13
- * overrides. Use `!pattern` to re-include something earlier layers
14
- * excluded.
15
- *
16
- * - **Allowlist**: triggered when `.hqinclude` exists at hqRoot. Nothing
17
- * syncs unless its path matches at least one pattern in `.hqinclude`. The
18
- * three exclusion layers still subtract on top — so even allowlisted
19
- * subtrees won't push `node_modules/` or `.env`. Privacy-by-default for
20
- * HQ trees that contain mixed personal + shareable data.
21
- *
22
- * SESSIONS ARE PUSH-ONLY, NOT PUSH-IGNORED (US-006). The company-vault
23
- * `sessions/{personUid}/...` prefix (session transcripts) is intentionally NOT
24
- * added to `DEFAULT_IGNORES`: push-only means it MUST still upload on the push
25
- * walk. Its never-AUTO-pull semantics are enforced ONE layer over, in the pull
26
- * scope resolver — `resolvePullScope().excludePrefixes` (src/sync/pull-scope.ts)
27
- * subtracts `sessions/` from the effective pull scope in EVERY mode (including
28
- * `all`), and `computePullPlan` classifies those keys `skip-out-of-scope`.
29
- * Ignoring `sessions/` HERE would wrongly block the upload too, so the split is
30
- * deliberate: this module governs the push walk; pull-scope governs never-pull.
31
- */
32
-
33
- import * as fs from "fs";
34
- import * as path from "path";
35
- import ignore from "ignore";
36
-
37
- // Patterns that must never sync regardless of project type.
38
- // Grouped by ecosystem so new stacks are easy to add.
39
- export const DEFAULT_IGNORES = [
40
- // VCS + OS
41
- ".git/",
42
- ".git",
43
- ".DS_Store",
44
- "Thumbs.db",
45
-
46
- // Node / JS
47
- "node_modules/",
48
- // pnpm's content-addressed package store. Large and machine-local like
49
- // node_modules/; a synced store writes a `.conflict-` copy of every blob
50
- // each cycle, which then re-syncs and amplifies into tens of thousands of
51
- // files. Never round-trip it.
52
- ".pnpm-store/",
53
- "dist/",
54
- "build/",
55
- ".next/",
56
- ".nuxt/",
57
- ".svelte-kit/",
58
- ".turbo/",
59
- ".parcel-cache/",
60
- ".vite/",
61
- "coverage/",
62
-
63
- // Rust / Tauri
64
- "target/",
65
-
66
- // Python
67
- "__pycache__/",
68
- "*.pyc",
69
- ".pytest_cache/",
70
- ".mypy_cache/",
71
- ".ruff_cache/",
72
- ".venv/",
73
- "venv/",
74
-
75
- // Go / JVM / other
76
- "vendor/",
77
- "out/",
78
- "*.class",
79
-
80
- // Generic caches / temp
81
- ".cache/",
82
- // Project-local qmd index (qmd init). Must never round-trip through the vault —
83
- // sqlite-vec vec0 tables corrupt when copied across machines (feedback_332c7ccc /
84
- // sqlite-vec#297). Default global index at ~/.cache/qmd is already outside hqRoot.
85
- ".qmd/",
86
- "tmp/",
87
- ".tmp/",
88
-
89
- // HQ sync internal state (never round-trip these). The `.hq-*` wildcard
90
- // covers `.hq-sync.pid`, `.hq-sync-journal.json`, `.hq-sync-state.json`,
91
- // `.hq-embeddings-pending.json`, and any future internal-state file. The
92
- // `.hqignore` / `.hqsyncignore` / `.hqinclude` config files don't match
93
- // (no hyphen). The `.hq/` directory holds per-host source-of-truth files
94
- // (`install-manifest.json`, `machine-id`, `machine.json`, `config.json`)
95
- // that MUST NEVER round-trip through the bucket — see Bug #1/#6/#8 in
96
- // the 5.33.0 deep-test report. Leaking these between machines makes two
97
- // hosts share an installer id / machine-id and breaks the contract the
98
- // 5.33.0 machine-id fix relies on.
99
- "*.pid",
100
- ".hq-*",
101
- ".hq/",
102
- "modules.lock",
103
- // NOTE: the root-anchored `/core.yaml` exclusion is intentionally NOT a
104
- // static default — it is layout-dependent and added conditionally in
105
- // `createIgnoreFilter` (see the `core/core.yaml` probe there). On v15+ the
106
- // scaffold/version manifest lives at `core/core.yaml` and a root-level
107
- // `core.yaml` is a stale per-machine duplicate that must not round-trip; on
108
- // v12–v14 roots there is NO `core/core.yaml` and the ROOT `core.yaml` IS the
109
- // scaffold/version manifest, which MUST sync (else the vault has no version
110
- // marker and the file is undetectable across user installs).
111
- // hq modules manifest — local module-resolution state, never synced.
112
- "modules/modules.yaml",
113
- // per-company identity file — written locally on first sync, never round-tripped.
114
- "company.yaml",
115
- // auto-generated tool index and policy digest — regenerated locally per-machine.
116
- "INDEX.md",
117
- "policies/_digest.md",
118
-
119
- // Claude Code worktrees — local-only working copies, never synced.
120
- "**/.claude/worktrees/",
121
-
122
- // Claude Code per-machine session state — every active session writes
123
- // to `.claude/state/active-session-project` and
124
- // `.claude/state/auto-session-project-<sessionUuid>` independently on
125
- // each host. Syncing them produces a guaranteed conflict per machine
126
- // per session (~25 of the 30 mirrors written during the 5.34.0 live-
127
- // sync test traced here). The `.claude/audit/` siblings are also
128
- // per-host (audit log of hook decisions, suppressions, instructions).
129
- // Both classes are session-/host-scoped by design and must never
130
- // round-trip. Anchored under `**/.claude/` so the same rule covers
131
- // hqRoot, company sub-dirs, and embedded knowledge repos uniformly —
132
- // mirrors the `**/.claude/worktrees/` pattern just above.
133
- "**/.claude/state/",
134
- "**/.claude/audit/",
135
-
136
- // Claude Code settings are machine-local: `env.PATH` is a snapshot of the
137
- // host's shell PATH taken at install time, and model/permission prefs are
138
- // per-operator-per-machine. Syncing them propagates one machine's PATH
139
- // onto every other machine (and overwrites the installer's day-one PATH
140
- // fix ~40s after a fresh install pulls existing cloud state) — the root
141
- // cause of the recurring "qmd: command not found" reports (see
142
- // workspace/e2e-mac/reports/20260701-qmd-path in HQ). Never round-trip
143
- // them, in either direction.
144
- "**/.claude/settings.json",
145
- "**/.claude/settings.local.json",
146
-
147
- // Skill hero images (skills-first-class US-014) — cloud-only artifacts stored
148
- // under a skill's `.skill-media/` dir (e.g. `skills/<name>/.skill-media/hero.png`).
149
- // They live in the vault/DB and are surfaced by the console, but must NEVER
150
- // round-trip through the file sync onto agent machines: agents run the skill
151
- // from SKILL.md and have no use for the image, and pulling it would litter
152
- // every skill dir with binary blobs. Anchored `**/` so it matches at any depth
153
- // (hqRoot skills, company skills, embedded knowledge repos) — excludes them
154
- // from BOTH push and pull. Mirrors the `**/.claude/worktrees/` anchor shape.
155
- "**/.skill-media/",
156
-
157
- // HQ repos directory (managed separately, not synced). Root-anchored so
158
- // only hqRoot/repos/ is excluded; bare `repos/` would match any nested
159
- // directory named repos (for example companies/<co>/knowledge/repos/),
160
- // silently dropping discover-generated knowledge (DEV-1791).
161
- "/repos/",
162
-
163
- // Secrets / env
164
- ".env",
165
- ".env.*",
166
- ];
167
-
168
- function readIgnoreFile(filePath: string): string | null {
169
- if (!fs.existsSync(filePath)) return null;
170
- try {
171
- return fs.readFileSync(filePath, "utf-8");
172
- } catch {
173
- return null;
174
- }
175
- }
176
-
177
- /**
178
- * Compile a depth-anchored ancestor matcher from .hqinclude content.
179
- *
180
- * Git itself never asks "should I descend into this dir?" — it walks its
181
- * index and checks each file directly. Our walkers/watchers don't have an
182
- * index, so they have to make a descent decision per directory. To preserve
183
- * full gitignore semantics in allowlist mode, we must allow descent into
184
- * every ancestor of any include pattern. We can't reuse the `ignore` lib
185
- * for this because gitignore's `foo/*\/` would also match `foo/x/y/z/` —
186
- * recursing into the dir — which would defeat the privacy invariant.
187
- *
188
- * The matcher therefore checks an ancestor candidate against each include
189
- * pattern's prefix segments at the EXACT same depth, with `*` and `?`
190
- * resolved per-segment. `**` segments are treated as wildcards that match
191
- * any single segment here — sufficient for descent decisions, since the
192
- * include matcher itself still gates files.
193
- */
194
- function compileAncestorMatcher(
195
- includeContent: string,
196
- ): ((relDir: string) => boolean) | null {
197
- const prefixes: RegExp[][] = [];
198
- for (const raw of includeContent.split(/\r?\n/)) {
199
- const line = raw.trim();
200
- if (!line || line.startsWith("#") || line.startsWith("!")) continue;
201
- const stripped = line.replace(/^\//, "").replace(/\/$/, "");
202
- if (!stripped.includes("/")) continue;
203
- const segs = stripped.split("/").map(segmentToRegex);
204
- for (let i = 1; i < segs.length; i++) {
205
- prefixes.push(segs.slice(0, i));
206
- }
207
- }
208
- if (!prefixes.length) return null;
209
- return (relDir: string): boolean => {
210
- const parts = relDir.split("/");
211
- for (const pat of prefixes) {
212
- if (pat.length !== parts.length) continue;
213
- let ok = true;
214
- for (let i = 0; i < pat.length; i++) {
215
- if (!pat[i].test(parts[i])) {
216
- ok = false;
217
- break;
218
- }
219
- }
220
- if (ok) return true;
221
- }
222
- return false;
223
- };
224
- }
225
-
226
- function segmentToRegex(seg: string): RegExp {
227
- // Translate a single gitignore path segment to an anchored regex. `*` and
228
- // `**` both match any single segment here (segments never contain `/`),
229
- // `?` matches one char. Everything else is escaped literal.
230
- let body = "";
231
- for (let i = 0; i < seg.length; i++) {
232
- const ch = seg[i];
233
- if (ch === "*") {
234
- if (seg[i + 1] === "*") i++;
235
- body += "[^/]*";
236
- } else if (ch === "?") {
237
- body += "[^/]";
238
- } else if (/[.+^${}()|[\]\\]/.test(ch)) {
239
- body += "\\" + ch;
240
- } else {
241
- body += ch;
242
- }
243
- }
244
- return new RegExp(`^${body}$`);
245
- }
246
-
247
- /**
248
- * Match a path whose basename is the macOS Finder custom-icon file: the
249
- * literal `Icon` followed by a carriage return (U+000D).
250
- *
251
- * This CANNOT be expressed as a `DEFAULT_IGNORES` entry, which is why it is a
252
- * predicate instead of a pattern string. Two independent reasons:
253
- *
254
- * 1. A pattern line ending in a literal CR is inert. The `ignore` package
255
- * strips the trailing CR, so the line degrades to `Icon` and never
256
- * matches — verified against ignore@5.3.2:
257
- * `ignore().add("Icon\r\n").ignores("Icon\r") === false`. Git does the
258
- * same thing (it reads the CR as part of the line ending), which is why
259
- * the GitHub macOS template has to end that line with TWO CRs.
260
- * 2. The usual workaround pattern `Icon?` DOES match, but `?` is a
261
- * single-character wildcard in gitignore syntax, so it also swallows any
262
- * real file named `Icons`, `Icon1`, `IconX`. Silently dropping user
263
- * content is worse than the cruft we are excluding.
264
- *
265
- * Matching the exact byte sequence keeps the exclusion precise. Finder writes
266
- * these into every directory it renders a custom icon for, and a folder-level
267
- * cloud-sync agent (iCloud Desktop, backup tooling) propagates them across an
268
- * entire tree — including into `.git/`. They carry no user content.
269
- *
270
- * They are also unsyncable by construction: a CR is a control character, so
271
- * the vault key validator rejects the key outright
272
- * (INVALID_KEY_CONTROL_CHARS). Excluding them at the walk is what keeps them
273
- * from reaching the upload path at all.
274
- */
275
- export function isMacFinderIconFile(relPath: string): boolean {
276
- const basename = relPath.replace(/\/$/, "").split("/").pop() ?? "";
277
- return basename === "Icon\r";
278
- }
279
-
280
- /**
281
- * True when any segment of `relPath` contains a character the vault key
282
- * validator rejects (see `KEY_CONTROL_CHARS` in s3.ts — C0 controls plus DEL).
283
- *
284
- * Kept in lock-step with that validator on purpose: a key this returns `true`
285
- * for can never be stored, so planning an upload for it only produces a
286
- * guaranteed per-file failure.
287
- */
288
- export function hasControlCharacters(relPath: string): boolean {
289
- // eslint-disable-next-line no-control-regex
290
- return /[\x00-\x1F\x7F]/.test(relPath);
291
- }
292
-
293
- export const EXPECTED_IGNORE_SEGMENTS = new Set([
294
- ".git",
295
- "node_modules",
296
- ".pnpm-store",
297
- "dist",
298
- "build",
299
- ".next",
300
- ".nuxt",
301
- ".svelte-kit",
302
- ".turbo",
303
- ".parcel-cache",
304
- ".vite",
305
- "coverage",
306
- "target",
307
- "__pycache__",
308
- ".pytest_cache",
309
- ".mypy_cache",
310
- ".ruff_cache",
311
- ".venv",
312
- "venv",
313
- "vendor",
314
- "out",
315
- ".cache",
316
- ".qmd",
317
- "tmp",
318
- ".tmp",
319
- ".hq",
320
- ".claude",
321
- // Cloud-only skill hero images (US-014) — never synced onto agent machines.
322
- ".skill-media",
323
- ]);
324
-
325
- /**
326
- * Classify a base-ignore rejection as expected noise vs noteworthy content.
327
- *
328
- * `true` means the ignored path belongs to an expected by-design exclusion
329
- * class: HQ-managed top-level roots, build/VCS/cache/machine-local segments,
330
- * or known generated/noise basenames. `false` means the rejected path looks
331
- * like real user content and should be surfaced by push observability.
332
- *
333
- * Top-level `repos/` and `workspace/` are expected, but nested segments named
334
- * `repos` or `workspace` are deliberately noteworthy: that is the DEV-1791
335
- * regression signal this classifier exists to preserve.
336
- */
337
- export function isExpectedIgnore(relPath: string): boolean {
338
- if (
339
- relPath === "repos" ||
340
- relPath.startsWith("repos/") ||
341
- relPath === "workspace" ||
342
- relPath.startsWith("workspace/")
343
- ) {
344
- return true;
345
- }
346
-
347
- const segments = relPath.split("/");
348
- for (const segment of segments) {
349
- if (EXPECTED_IGNORE_SEGMENTS.has(segment)) return true;
350
- }
351
-
352
- const basename = segments[segments.length - 1] ?? "";
353
- return (
354
- basename === ".DS_Store" ||
355
- basename === "Thumbs.db" ||
356
- // Same class as .DS_Store: OS-generated, zero user content. Classified
357
- // expected so a tree full of them stays silent instead of firing the
358
- // "EXCLUDED from sync — review in case this is unintended" warning once
359
- // per file (Finder scatters one into every directory it touches).
360
- basename === "Icon\r" ||
361
- basename === "company.yaml" ||
362
- basename === "INDEX.md" ||
363
- basename === "modules.lock" ||
364
- basename.endsWith(".pyc") ||
365
- basename.endsWith(".class") ||
366
- basename.endsWith(".pid") ||
367
- basename.startsWith(".hq-") ||
368
- basename === ".env" ||
369
- basename.startsWith(".env.") ||
370
- basename === ".mcp.json"
371
- );
372
- }
373
-
374
- export function createIgnoreFilter(
375
- hqRoot: string,
376
- ): (filePath: string, isDir?: boolean) => boolean {
377
- const ig = ignore();
378
-
379
- // Layer 1: baseline defaults
380
- ig.add(DEFAULT_IGNORES);
381
-
382
- // Layer 1b: layout-aware root `core.yaml` exclusion. The hq-core scaffold/
383
- // version manifest moved location across releases: v15+ keeps it at
384
- // `core/core.yaml` (which always syncs), v12–v14 keep it at the ROOT as
385
- // `core.yaml`. We must sync whichever one is the real manifest, on every
386
- // layout, so the vault always carries a version marker.
387
- // - v15+ (core/core.yaml present): exclude the root `/core.yaml` — it is a
388
- // stale/duplicate per-machine marker, never the authoritative manifest.
389
- // - v12–v14 (core/core.yaml absent): do NOT exclude root `core.yaml` — it
390
- // IS the manifest and must round-trip, else the file is undetectable
391
- // across user installs (the ~98 "no core.yaml in vault" users were all old-layout).
392
- // Root-anchored (`/core.yaml`) so this never affects `core/core.yaml`.
393
- if (fs.existsSync(path.join(hqRoot, "core", "core.yaml"))) {
394
- ig.add("/core.yaml");
395
- }
396
-
397
- // Layer 2: repo's .gitignore (common case — covers most build dirs already)
398
- const gitignore = readIgnoreFile(path.join(hqRoot, ".gitignore"));
399
- if (gitignore) ig.add(gitignore);
400
-
401
- // Layer 3: sync-specific overrides. .hqignore is the documented name;
402
- // .hqsyncignore is the legacy name we still honor.
403
- const hqignore =
404
- readIgnoreFile(path.join(hqRoot, ".hqignore")) ??
405
- readIgnoreFile(path.join(hqRoot, ".hqsyncignore"));
406
- if (hqignore) ig.add(hqignore);
407
-
408
- // Allowlist mode: when `.hqinclude` exists, sync is opt-in. The matcher
409
- // here treats include patterns as ignore patterns and inverts the verdict —
410
- // a path is "allowed" iff its relative path matches at least one entry.
411
- // Exclusion layers above still subtract, so build artifacts inside an
412
- // allowlisted subtree (e.g. node_modules/ inside companies/x/repos/y/) are
413
- // still skipped.
414
- const hqinclude = readIgnoreFile(path.join(hqRoot, ".hqinclude"));
415
- const includeMatcher = hqinclude ? ignore().add(hqinclude) : null;
416
- // Ancestor matcher: matches every directory that lies on the path to an
417
- // include pattern. Consulted ONLY for `isDir=true` queries so a walker can
418
- // descend through `companies/` and `companies/indigo/` to reach the leaf
419
- // `companies/*/knowledge/`. Files directly inside those intermediate dirs
420
- // remain excluded — this is the privacy invariant of allowlist mode.
421
- const ancestorMatcher = hqinclude ? compileAncestorMatcher(hqinclude) : null;
422
-
423
- return (filePath: string, isDir = false): boolean => {
424
- const relative = path.relative(hqRoot, filePath);
425
- if (!relative || relative.startsWith("..")) return true; // outside HQ root
426
-
427
- // macOS Finder custom-icon files. Excluded here rather than as a
428
- // DEFAULT_IGNORES pattern because neither available spelling works —
429
- // see isMacFinderIconFile for the ignore@5 CR-stripping proof and the
430
- // `Icon?` over-match hazard.
431
- if (isMacFinderIconFile(relative)) return false;
432
-
433
- // Gitignore dir-only patterns (`foo/`) only match candidate paths that
434
- // end with `/`. The `ignore` lib has no stat awareness, so when the
435
- // caller knows the entry is a directory we hand the matcher the
436
- // canonical trailing-slash form. This mirrors how git itself decides:
437
- // it knows from the index whether each entry is a tree or a blob.
438
- // Applied symmetrically to BOTH layers — exclude and include — to
439
- // preserve full gitignore semantics on both sides.
440
- const probe = isDir && !relative.endsWith("/") ? relative + "/" : relative;
441
-
442
- if (ig.ignores(probe)) return false;
443
- if (!includeMatcher) return true;
444
- if (includeMatcher.ignores(probe)) return true;
445
- // Directory query that didn't match the include pattern itself — allow
446
- // if it's an ancestor of one (so the walker can descend to the leaf).
447
- if (isDir && ancestorMatcher) {
448
- // Ancestor matching is depth-anchored, so feed it the slashless form.
449
- const relDir = relative.replace(/\/$/, "");
450
- if (relDir && ancestorMatcher(relDir)) return true;
451
- }
452
- return false;
453
- };
454
- }
455
-
456
- /**
457
- * Check if a file exceeds the max sync size (50MB default)
458
- */
459
- export function isWithinSizeLimit(
460
- filePath: string,
461
- maxBytes = 50 * 1024 * 1024
462
- ): boolean {
463
- try {
464
- const stat = fs.statSync(filePath);
465
- return stat.size <= maxBytes;
466
- } catch {
467
- return false;
468
- }
469
- }