/** * Personal-vault default exclusions — second-tier scope filter that complements * `PERSONAL_VAULT_EXCLUDED_TOP_LEVEL` (`.git`, `companies`, `repos`, * `workspace`) in `./personal-vault.ts`. * * Where the top-level constant filters the four big buckets at scope root, this * file filters categories of nested files that ride along with an HQ install * but have no business round-tripping to a personal vault — regardless of * where they sit in the tree: * * 1. Secrets at root or nested. `.env`, `.env.local`, `.env.`, * `.mcp.json`. Pre-fix these were being uploaded if the user hadn't * added `.env` to their `.hqignore` (the default starter `.hqignore` * only listed `companies/*\/settings/`). * * 2. Machine-local state. `.beads/` (SQLite issue-tracker + WAL/SHM), * `.obsidian/`, `.vercel/`, `.cache_*`. Per-machine layouts/caches; * multi-device sync either corrupts (SQLite WAL) or causes spurious * churn (caches regenerate on demand). * * 3. Update-flow scratch. `output/`, `_legacy-*` directories. Created by * `/update-hq` / `/promote-hq-core` workflows as checkout scratch; * naming themselves "legacy" / sitting under "output" is a self- * declared "do not preserve" signal. * * 4. Pre-5.24 conflict mirror dir. `.hq-conflicts/` is a directory of * mirror copies from older sync runs (sibling to the now-fixed * `.conflict-YYYY-MM-DDTHH-MM-SSZ-{hash}.{ext}` ephemeral files * handled by `EPHEMERAL_PATH_PATTERN` in share.ts). Same logic * applies: these are local-only safety backups. * * 5. Reindex-generated skill wrappers. `.claude/skills/:/` * (encoded as `%3A` on Windows) is a derived link farm over * canonical `core/`, `personal/`, package, or company skill sources. * Syncing the links duplicates their owners and makes personal pulls * fight reindex over directory links. * * 6. OS / build cruft. `.DS_Store`, `node_modules/`, `dist/`, `.next/`, * `build/`. Universally noise inside HQ (personal scope should not * contain code projects, but defense-in-depth catches anyone who * symlinks a project subtree in). * * 7. The HQ root's own `bin/` (root-anchored, not any nested `bin/`). The * half of a misdirected global install that is neither a `node_modules` * nor a lockfile. `PERSONAL_VAULT_EXCLUDED_TOP_LEVEL` stops it being * pushed; this entry is what stops `computePullPlan` re-downloading the * copies already sitting in buckets from clients that predate the * exclusion — which is the entire population the fix exists for. * * Policy: refuse + warn (5.25 default). The walk filter drops these so they * never upload; the delete-plan walker uses the same filter so already- * journaled entries that match a new exclusion get orphaned in the journal * (no DELETE issued, no churn). A one-shot purge script handles the cleanup * of objects that landed on remote before this version. * * Application scope: personal vault only. Company vaults have separate * first-push protection (settings/, data/, workers/, .git/ exclusion in * `src-tauri/src/util/ignore.rs`) and may legitimately ship `output/` or * `.env*` paths inside their data folders. Wired only when `personalMode` * is true. * * Wire-points (parallel to `EPHEMERAL_PATH_PATTERN`): * - `collectFiles` / `walkDir` (push) — wrap `shouldSync` so excluded * relative paths are rejected before upload. * - `computeDeletePlan` (delete) — same `shouldSync` wrap, so journal * entries matching an exclusion are skipped on the delete pass too. * - `computePullPlan` (pull) — reject legacy remote objects before any * local inspection or download so excluded state cannot rematerialize. */ /** * Each entry is matched against the relative path from the personal-vault * sync root (which IS hq_root in personalMode), using forward-slash * separators. Patterns: * * - `prefix:foo/` — match if the path starts with `foo/` or equals `foo` * (handles both the dir itself and any descendant) * - `basename:.env` — match if any path segment equals `.env` * - `basename:.env-prefix:.env.` — match if the basename starts with `.env.` * - `segment:node_modules` — match if any path segment equals literally * * The literal-segment form catches nested cases (e.g., `repos/foo/node_modules/` * is already excluded by top-level repos/ skip, but `personal/x/node_modules/` * would slip through without segment matching). */ export interface PersonalVaultExclusion { /** Internal id for telemetry / explainability in events. */ id: string; /** Human-readable category for the warning event. */ category: "secret" | "machine-local" | "scratch" | "conflict-mirror" | "generated-mirror" | "os-cruft" | "build-output"; /** * Predicate. Pure: relative path (forward-slash separated, no leading slash) * + optional isDir hint. Returns true to EXCLUDE. */ test: (relPath: string, isDir?: boolean) => boolean; /** Doc-only — shown alongside `id` in the warning event sample. */ pattern: string; } /** * Cheap segment-equality check. Avoids allocating a regex per call. */ declare function hasSegment(relPath: string, segment: string): boolean; declare function basename(relPath: string): string; declare function isGeneratedSkillWrapperPath(relPath: string): boolean; export declare function isGeneratedCommandWrapperPath(relPath: string): boolean; export declare const PERSONAL_VAULT_DEFAULT_EXCLUSIONS: readonly PersonalVaultExclusion[]; /** * First-match result for a path. Returns the matching exclusion (so callers * can surface category/id in events) or undefined for "not excluded". */ export declare function matchPersonalVaultExclusion(relPath: string, isDir?: boolean): PersonalVaultExclusion | undefined; /** * Boolean version — true if any exclusion matches. */ export declare function isPersonalVaultExcluded(relPath: string, isDir?: boolean): boolean; /** * Wrap an existing path filter (typically from `createIgnoreFilter`) with the * personal-vault default exclusions. The wrapper: * * 1. Calls the underlying filter first; if it already rejects, defer * (counter does not fire — we only want to count things the underlying * filter would have allowed but the personal-vault defaults reject). * 2. Computes a relative path from `syncRoot` (the personal-vault sync * root, which is hq_root in personalMode). * 3. Probes `matchPersonalVaultExclusion`; if it matches, returns false * and tags the match via `onExcluded` so the runner can emit a single * `personal-vault-out-of-policy` event with a count + sample at end of * run. * * The wrapper keeps the `(absolutePath, isDir) => boolean` shape the existing * collectFiles / walkDir / computeDeletePlan code already uses, so wiring is * a single line change at the share() filter construction site. */ export declare function wrapFilterWithPersonalVaultDefaults(underlying: (absPath: string, isDir?: boolean) => boolean, syncRoot: string, onExcluded: (relPath: string, match: PersonalVaultExclusion) => void): (absPath: string, isDir?: boolean) => boolean; /** * Test-only export. Mirrors the `_testing` namespace pattern used by * `share.ts` for `EPHEMERAL_PATH_PATTERN` — direct access to the list + * internal helpers for regression-critical pinning without round-tripping * through share(). */ export declare const _testing: { hasSegment: typeof hasSegment; basename: typeof basename; isGeneratedSkillWrapperPath: typeof isGeneratedSkillWrapperPath; }; export {}; //# sourceMappingURL=personal-vault-exclusions.d.ts.map