@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/personal-vault.ts
DELETED
|
@@ -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
|
-
}
|