@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.
- package/dist/bin/sync-mutation.d.ts +16 -0
- package/dist/bin/sync-mutation.d.ts.map +1 -0
- package/dist/bin/sync-mutation.js +60 -0
- package/dist/bin/sync-mutation.js.map +1 -0
- package/dist/bin/sync-mutation.test.d.ts +2 -0
- package/dist/bin/sync-mutation.test.d.ts.map +1 -0
- package/dist/bin/sync-mutation.test.js +165 -0
- package/dist/bin/sync-mutation.test.js.map +1 -0
- package/dist/bin/sync-runner-company.d.ts +8 -0
- package/dist/bin/sync-runner-company.d.ts.map +1 -1
- package/dist/bin/sync-runner-company.js +16 -0
- package/dist/bin/sync-runner-company.js.map +1 -1
- package/dist/bin/sync-runner-company.test.d.ts +2 -0
- package/dist/bin/sync-runner-company.test.d.ts.map +1 -0
- package/dist/bin/sync-runner-company.test.js +36 -0
- package/dist/bin/sync-runner-company.test.js.map +1 -0
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +98 -8
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +17 -0
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +109 -0
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/conflict-recovery.test.d.ts +2 -0
- package/dist/cli/conflict-recovery.test.d.ts.map +1 -0
- package/dist/cli/conflict-recovery.test.js +201 -0
- package/dist/cli/conflict-recovery.test.js.map +1 -0
- package/dist/cli/conflict.d.ts +60 -0
- package/dist/cli/conflict.d.ts.map +1 -1
- package/dist/cli/conflict.js +333 -0
- package/dist/cli/conflict.js.map +1 -1
- package/dist/cli/sync.d.ts +35 -0
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +100 -0
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +85 -1
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/skill-telemetry.d.ts +6 -0
- package/dist/skill-telemetry.d.ts.map +1 -1
- package/dist/skill-telemetry.js +14 -2
- package/dist/skill-telemetry.js.map +1 -1
- package/dist/skill-telemetry.test.js +79 -0
- package/dist/skill-telemetry.test.js.map +1 -1
- package/dist/sync/candidate-uploader.d.ts +88 -0
- package/dist/sync/candidate-uploader.d.ts.map +1 -0
- package/dist/sync/candidate-uploader.js +212 -0
- package/dist/sync/candidate-uploader.js.map +1 -0
- package/dist/sync/candidate-uploader.test.d.ts +2 -0
- package/dist/sync/candidate-uploader.test.d.ts.map +1 -0
- package/dist/sync/candidate-uploader.test.js +132 -0
- package/dist/sync/candidate-uploader.test.js.map +1 -0
- package/dist/sync/delta-client.d.ts +73 -0
- package/dist/sync/delta-client.d.ts.map +1 -0
- package/dist/sync/delta-client.js +201 -0
- package/dist/sync/delta-client.js.map +1 -0
- package/dist/sync/delta-client.test.d.ts +2 -0
- package/dist/sync/delta-client.test.d.ts.map +1 -0
- package/dist/sync/delta-client.test.js +97 -0
- package/dist/sync/delta-client.test.js.map +1 -0
- package/dist/sync/durable-apply.d.ts +76 -0
- package/dist/sync/durable-apply.d.ts.map +1 -0
- package/dist/sync/durable-apply.js +530 -0
- package/dist/sync/durable-apply.js.map +1 -0
- package/dist/sync/durable-apply.test.d.ts +2 -0
- package/dist/sync/durable-apply.test.d.ts.map +1 -0
- package/dist/sync/durable-apply.test.js +180 -0
- package/dist/sync/durable-apply.test.js.map +1 -0
- package/dist/sync/event-sync.d.ts +33 -1
- package/dist/sync/event-sync.d.ts.map +1 -1
- package/dist/sync/event-sync.js +149 -1
- package/dist/sync/event-sync.js.map +1 -1
- package/dist/sync/event-sync.test.js +142 -1
- package/dist/sync/event-sync.test.js.map +1 -1
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +1 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/multipart-uploader.d.ts +99 -0
- package/dist/sync/multipart-uploader.d.ts.map +1 -0
- package/dist/sync/multipart-uploader.js +447 -0
- package/dist/sync/multipart-uploader.js.map +1 -0
- package/dist/sync/multipart-uploader.test.d.ts +2 -0
- package/dist/sync/multipart-uploader.test.d.ts.map +1 -0
- package/dist/sync/multipart-uploader.test.js +119 -0
- package/dist/sync/multipart-uploader.test.js.map +1 -0
- package/dist/sync/mutation-client.d.ts +85 -0
- package/dist/sync/mutation-client.d.ts.map +1 -0
- package/dist/sync/mutation-client.js +245 -0
- package/dist/sync/mutation-client.js.map +1 -0
- package/dist/sync/mutation-client.test.d.ts +2 -0
- package/dist/sync/mutation-client.test.d.ts.map +1 -0
- package/dist/sync/mutation-client.test.js +51 -0
- package/dist/sync/mutation-client.test.js.map +1 -0
- package/dist/sync/push-receiver.d.ts +45 -0
- package/dist/sync/push-receiver.d.ts.map +1 -1
- package/dist/sync/push-receiver.js +101 -0
- package/dist/sync/push-receiver.js.map +1 -1
- package/dist/sync/push-receiver.test.js +54 -2
- package/dist/sync/push-receiver.test.js.map +1 -1
- package/dist/sync/scope-inventory-client.d.ts +69 -0
- package/dist/sync/scope-inventory-client.d.ts.map +1 -0
- package/dist/sync/scope-inventory-client.js +210 -0
- package/dist/sync/scope-inventory-client.js.map +1 -0
- package/dist/sync/scope-inventory-client.test.d.ts +2 -0
- package/dist/sync/scope-inventory-client.test.d.ts.map +1 -0
- package/dist/sync/scope-inventory-client.test.js +94 -0
- package/dist/sync/scope-inventory-client.test.js.map +1 -0
- package/dist/sync/snapshot-client.d.ts +98 -0
- package/dist/sync/snapshot-client.d.ts.map +1 -0
- package/dist/sync/snapshot-client.js +402 -0
- package/dist/sync/snapshot-client.js.map +1 -0
- package/dist/sync/snapshot-client.test.d.ts +2 -0
- package/dist/sync/snapshot-client.test.d.ts.map +1 -0
- package/dist/sync/snapshot-client.test.js +169 -0
- package/dist/sync/snapshot-client.test.js.map +1 -0
- package/dist/sync/uploader-finalization.d.ts +97 -0
- package/dist/sync/uploader-finalization.d.ts.map +1 -0
- package/dist/sync/uploader-finalization.js +273 -0
- package/dist/sync/uploader-finalization.js.map +1 -0
- package/dist/sync/uploader-finalization.test.d.ts +2 -0
- package/dist/sync/uploader-finalization.test.d.ts.map +1 -0
- package/dist/sync/uploader-finalization.test.js +92 -0
- package/dist/sync/uploader-finalization.test.js.map +1 -0
- package/dist/telemetry.d.ts +11 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +21 -2
- package/dist/telemetry.js.map +1 -1
- package/dist/telemetry.test.js +80 -0
- package/dist/telemetry.test.js.map +1 -1
- package/package.json +6 -1
- package/.claude/policies/hq-cloud-esm-cannot-spy-fs-builtins.md +0 -30
- package/.claude/policies/hq-cloud-strip-types-no-parameter-properties.md +0 -22
- package/.github/workflows/ci.yml +0 -84
- package/.github/workflows/publish.yml +0 -56
- package/.github/workflows/unreleased-commits-nag.yml +0 -256
- package/eslint.config.js +0 -67
- package/pnpm-workspace.yaml +0 -2
- package/scripts/presign-transport-e2e.mjs +0 -250
- package/scripts/vault-rebaseline.sh +0 -323
- package/scripts/vault-rescue.sh +0 -332
- package/src/active-company.test.ts +0 -188
- package/src/active-company.ts +0 -168
- package/src/agent-codex-instructions.test.ts +0 -332
- package/src/agent-codex-instructions.ts +0 -309
- package/src/auth.ts +0 -146
- package/src/backup-prune.test.ts +0 -98
- package/src/backup-prune.ts +0 -182
- package/src/bin/backup-prune-runner.ts +0 -33
- package/src/bin/rescue-runner.ts +0 -25
- package/src/bin/sync-runner-company.ts +0 -695
- package/src/bin/sync-runner-events.test.ts +0 -143
- package/src/bin/sync-runner-events.ts +0 -55
- package/src/bin/sync-runner-planning.test.ts +0 -311
- package/src/bin/sync-runner-planning.ts +0 -258
- package/src/bin/sync-runner-rollup.test.ts +0 -37
- package/src/bin/sync-runner-rollup.ts +0 -97
- package/src/bin/sync-runner-telemetry.ts +0 -15
- package/src/bin/sync-runner-watch-loop.ts +0 -1235
- package/src/bin/sync-runner-watch-routes.test.ts +0 -71
- package/src/bin/sync-runner-watch-routes.ts +0 -184
- package/src/bin/sync-runner.test.ts +0 -8767
- package/src/bin/sync-runner.ts +0 -2190
- package/src/cli/accept.ts +0 -124
- package/src/cli/conflict.ts +0 -119
- package/src/cli/doctor.test.ts +0 -581
- package/src/cli/doctor.ts +0 -642
- package/src/cli/index.ts +0 -49
- package/src/cli/invite.test.ts +0 -250
- package/src/cli/invite.ts +0 -214
- package/src/cli/promote.ts +0 -157
- package/src/cli/reindex-knowledge.test.ts +0 -307
- package/src/cli/reindex-knowledge.ts +0 -450
- package/src/cli/reindex.test.ts +0 -957
- package/src/cli/reindex.ts +0 -979
- package/src/cli/rescue-classify-ordering.test.ts +0 -548
- package/src/cli/rescue-clone-diagnostics.test.ts +0 -120
- package/src/cli/rescue-core.ts +0 -3011
- package/src/cli/rescue-drift-reconcile.test.ts +0 -179
- package/src/cli/rescue-drop-dir-symlink.test.ts +0 -224
- package/src/cli/rescue-exec-bit-preserve.test.ts +0 -187
- package/src/cli/rescue-hq-root-guard.test.ts +0 -232
- package/src/cli/rescue-journal-reconcile.test.ts +0 -215
- package/src/cli/rescue-mtime-preserve.test.ts +0 -203
- package/src/cli/rescue-settings-reconcile.test.ts +0 -637
- package/src/cli/rescue-snapshot.test.ts +0 -57
- package/src/cli/rescue-snapshot.ts +0 -51
- package/src/cli/rescue.reindex.test.ts +0 -63
- package/src/cli/rescue.test.ts +0 -131
- package/src/cli/rescue.ts +0 -182
- package/src/cli/share.test.ts +0 -7843
- package/src/cli/share.ts +0 -3663
- package/src/cli/sync-scope.test.ts +0 -652
- package/src/cli/sync.test.ts +0 -5207
- package/src/cli/sync.ts +0 -3470
- package/src/cli/tombstones.ts +0 -106
- package/src/cli/watch-event-push-conflict.test.ts +0 -234
- package/src/client-info.test.ts +0 -214
- package/src/client-info.ts +0 -121
- package/src/cognito-auth.test.ts +0 -712
- package/src/cognito-auth.ts +0 -1422
- package/src/company-resolver.test.ts +0 -618
- package/src/company-resolver.ts +0 -521
- package/src/context.test.ts +0 -583
- package/src/context.ts +0 -378
- package/src/daemon-worker.ts +0 -26
- package/src/daemon.ts +0 -99
- package/src/entity-resolver.test.ts +0 -315
- package/src/entity-resolver.ts +0 -180
- package/src/ignore.test.ts +0 -466
- package/src/ignore.ts +0 -469
- package/src/index.ts +0 -439
- package/src/journal.test.ts +0 -968
- package/src/journal.ts +0 -765
- package/src/lib/cloud-authoritative.test.ts +0 -45
- package/src/lib/cloud-authoritative.ts +0 -59
- package/src/lib/conflict-file.ts +0 -86
- package/src/lib/conflict-index.ts +0 -289
- package/src/lib/conflict.test.ts +0 -348
- package/src/lib/describe-error.test.ts +0 -100
- package/src/lib/describe-error.ts +0 -58
- package/src/lib/exit-codes.ts +0 -24
- package/src/lib/machine-id.test.ts +0 -231
- package/src/lib/machine-id.ts +0 -175
- package/src/lib/net-errors.test.ts +0 -65
- package/src/lib/net-errors.ts +0 -86
- package/src/lib/readlink-safe.test.ts +0 -43
- package/src/lib/readlink-safe.ts +0 -29
- package/src/local-path-codec.test.ts +0 -138
- package/src/local-path-codec.ts +0 -161
- package/src/machine-auth.test.ts +0 -1323
- package/src/manifest-reconcile.test.ts +0 -1123
- package/src/manifest-reconcile.ts +0 -518
- package/src/object-io.test.ts +0 -1221
- package/src/object-io.ts +0 -1306
- package/src/operation-lock.test.ts +0 -484
- package/src/operation-lock.ts +0 -680
- package/src/outcome-telemetry.test.ts +0 -498
- package/src/outcome-telemetry.ts +0 -639
- package/src/personal-vault-exclusions.test.ts +0 -308
- package/src/personal-vault-exclusions.ts +0 -354
- package/src/personal-vault.test.ts +0 -756
- package/src/personal-vault.ts +0 -496
- package/src/prefix-coalesce.test.ts +0 -240
- package/src/prefix-coalesce.ts +0 -273
- package/src/public-surface.test.ts +0 -117
- package/src/qmd-reindex.test.ts +0 -877
- package/src/qmd-reindex.ts +0 -842
- package/src/read-only-state-dir.test.ts +0 -188
- package/src/remote-pull.test.ts +0 -1130
- package/src/remote-pull.ts +0 -618
- package/src/s3.symlink-materialize.test.ts +0 -492
- package/src/s3.test.ts +0 -1789
- package/src/s3.ts +0 -1532
- package/src/schemas/signal-types.test.ts +0 -82
- package/src/schemas/signal-types.ts +0 -38
- package/src/schemas/source-channels.test.ts +0 -82
- package/src/schemas/source-channels.ts +0 -53
- package/src/scope-shrink.test.ts +0 -633
- package/src/scope-shrink.ts +0 -481
- package/src/signals/get.test.ts +0 -310
- package/src/signals/get.ts +0 -75
- package/src/signals/internals.ts +0 -195
- package/src/signals/list.test.ts +0 -420
- package/src/signals/list.ts +0 -79
- package/src/signals/parse.ts +0 -8
- package/src/signals/types.ts +0 -91
- package/src/skill-telemetry.test.ts +0 -1825
- package/src/skill-telemetry.ts +0 -1439
- package/src/sources/get.test.ts +0 -293
- package/src/sources/get.ts +0 -66
- package/src/sources/internals.ts +0 -198
- package/src/sources/list.test.ts +0 -402
- package/src/sources/list.ts +0 -84
- package/src/sources/parse.ts +0 -43
- package/src/sources/types.ts +0 -84
- package/src/sync/event-sync.test.ts +0 -594
- package/src/sync/event-sync.ts +0 -545
- package/src/sync/feature-flags.test.ts +0 -378
- package/src/sync/feature-flags.ts +0 -62
- package/src/sync/index.ts +0 -76
- package/src/sync/lease-client.test.ts +0 -128
- package/src/sync/lease-client.ts +0 -207
- package/src/sync/logger.test.ts +0 -242
- package/src/sync/logger.ts +0 -79
- package/src/sync/metrics.test.ts +0 -462
- package/src/sync/metrics.ts +0 -213
- package/src/sync/pull-scope.ts +0 -265
- package/src/sync/push-event.test.ts +0 -266
- package/src/sync/push-event.ts +0 -224
- package/src/sync/push-receiver.test.ts +0 -566
- package/src/sync/push-receiver.ts +0 -1048
- package/src/sync/push-transport.ts +0 -231
- package/src/sync/realtime-rollout.test.ts +0 -86
- package/src/sync/realtime-rollout.ts +0 -262
- package/src/sync/state-store.test.ts +0 -194
- package/src/sync/state-store.ts +0 -727
- package/src/sync-core.ts +0 -58
- package/src/sync-progress.test.ts +0 -94
- package/src/sync-progress.ts +0 -140
- package/src/telemetry-events.test.ts +0 -88
- package/src/telemetry-events.ts +0 -205
- package/src/telemetry.test.ts +0 -1280
- package/src/telemetry.ts +0 -1109
- package/src/types.ts +0 -314
- package/src/vault-client.test.ts +0 -1380
- package/src/vault-client.ts +0 -1694
- package/src/version.ts +0 -24
- package/src/watch-roots.test.ts +0 -278
- package/src/watch-roots.ts +0 -162
- package/src/watcher-event-gate.test.ts +0 -212
- package/src/watcher.test.ts +0 -1079
- package/src/watcher.ts +0 -1741
- package/test/e2e/sync/cross-tenant-isolation.test.ts +0 -630
- package/test/e2e/sync/skill-telemetry-oversized-transcript.test.ts +0 -124
- package/test/e2e/sync/transient-company-leg.test.ts +0 -384
- package/test/e2e/sync/windows-unreadable-link-leg.test.ts +0 -191
- package/test/e2e/watcher-real-chokidar.test.ts +0 -165
- package/test/e2e/watcher-recursive-backend.test.ts +0 -181
- package/test/e2e/watcher-scoped-coverage.test.ts +0 -381
- package/test/invite-flow.integration.test.ts +0 -244
- package/test/joiner-manifest-reconcile.integration.test.ts +0 -322
- package/test/share-sync.integration.test.ts +0 -213
- package/tsconfig.json +0 -19
- 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
|
-
}
|