/** * Ignore-file parser for cloud sync. * * Two modes: * - **Permissive (default)**: everything syncs except what ignore layers * subtract. Two layers stack (later overrides earlier): * 1. Built-in defaults — VCS, node_modules, build artifacts, caches, * AND secrets/credentials (`.env*`, `*.credentials.json`, * `*.secret.*`, `.netrc`, `.mcp.json`). Covers the common stacks so * a first-time sync over a random project folder doesn't push * `target/` or `.next/` — or a credential — to S3. * 2. `.hqignore` (preferred) or `.hqsyncignore` (legacy) — sync-specific * overrides. Use `!pattern` to re-include something earlier layers * excluded. * The repo's `.gitignore` is deliberately NOT consulted: it governs what * the git-mirror commits, which is independent of what sync uploads. * * - **Allowlist**: triggered when `.hqinclude` exists at hqRoot. Nothing * syncs unless its path matches at least one pattern in `.hqinclude`. The * exclusion layers still subtract on top — so even allowlisted subtrees * won't push `node_modules/` or `.env`. Privacy-by-default for HQ trees * that contain mixed personal + shareable data. * * SESSIONS ARE PUSH-ONLY, NOT PUSH-IGNORED (US-006). The company-vault * `sessions/{personUid}/...` prefix (session transcripts) is intentionally NOT * added to `DEFAULT_IGNORES`: push-only means it MUST still upload on the push * walk. Its never-AUTO-pull semantics are enforced ONE layer over, in the pull * scope resolver — `resolvePullScope().excludePrefixes` (src/sync/pull-scope.ts) * subtracts `sessions/` from the effective pull scope in EVERY mode (including * `all`), and `computePullPlan` classifies those keys `skip-out-of-scope`. * Ignoring `sessions/` HERE would wrongly block the upload too, so the split is * deliberate: this module governs the push walk; pull-scope governs never-pull. */ export declare const DEFAULT_IGNORES: string[]; /** Construction-time source for the ignore-configuration snapshot. */ export interface IgnoreMatcherSnapshotReader { existsSync(filePath: string): boolean; readFileSync(filePath: string): string; } /** * Match a path whose basename is the macOS Finder custom-icon file: the * literal `Icon` followed by a carriage return (U+000D). * * This CANNOT be expressed as a `DEFAULT_IGNORES` entry, which is why it is a * predicate instead of a pattern string. Two independent reasons: * * 1. A pattern line ending in a literal CR is inert. The `ignore` package * strips the trailing CR, so the line degrades to `Icon` and never * matches — verified against ignore@5.3.2: * `ignore().add("Icon\r\n").ignores("Icon\r") === false`. Git does the * same thing (it reads the CR as part of the line ending), which is why * the GitHub macOS template has to end that line with TWO CRs. * 2. The usual workaround pattern `Icon?` DOES match, but `?` is a * single-character wildcard in gitignore syntax, so it also swallows any * real file named `Icons`, `Icon1`, `IconX`. Silently dropping user * content is worse than the cruft we are excluding. * * Matching the exact byte sequence keeps the exclusion precise. Finder writes * these into every directory it renders a custom icon for, and a folder-level * cloud-sync agent (iCloud Desktop, backup tooling) propagates them across an * entire tree — including into `.git/`. They carry no user content. * * They are also unsyncable by construction: a CR is a control character, so * the vault key validator rejects the key outright * (INVALID_KEY_CONTROL_CHARS). Excluding them at the walk is what keeps them * from reaching the upload path at all. */ export declare function isMacFinderIconFile(relPath: string): boolean; /** * True when any segment of `relPath` contains a character the vault key * validator rejects (see `KEY_CONTROL_CHARS` in s3.ts — C0 controls plus DEL). * * Kept in lock-step with that validator on purpose: a key this returns `true` * for can never be stored, so planning an upload for it only produces a * guaranteed per-file failure. */ export declare function hasControlCharacters(relPath: string): boolean; export declare function isIndexMarkdownBasename(name: string): boolean; /** * True when `absPath` is an HQ-generated INDEX.md / index.md (the * `> Auto-generated.` marker from index-md-spec). Hand-authored files of the * same name — common in imported knowledge bases — return false so they sync. * * Read is bounded (512 bytes) and only runs for that basename. Unreadable * files fail open (sync) so a permission error cannot silently drop content. */ export declare function isHqGeneratedIndexFile(absPath: string, isDir?: boolean): boolean; export declare const EXPECTED_IGNORE_SEGMENTS: Set; /** * Classify a base-ignore rejection as expected noise vs noteworthy content. * * `true` means the ignored path belongs to an expected by-design exclusion * class: HQ-managed top-level roots, build/VCS/cache/machine-local segments, * or known generated/noise basenames. `false` means the rejected path looks * like real user content and should be surfaced by push observability. * * Top-level `repos/` and `workspace/` are expected, but nested segments named * `repos` or `workspace` are deliberately noteworthy: that is the DEV-1791 * regression signal this classifier exists to preserve. */ export declare function isExpectedIgnore(relPath: string): boolean; /** * Bound on the folder memo. The memo is keyed by relative PARENT DIRECTORY, * never by the probed path: gitignore semantics say nothing under an excluded * directory can be re-included, and the `ignore` package implements exactly * that by testing a path's parent (`dir/`) before the path itself. So the one * verdict worth retaining is "is this folder excluded?" — it settles every * file inside with a Map lookup and costs one entry per directory instead of * one per file. A real HQ root has ~50k in-scope directories against ~1.5M * file probes per walk; the old per-file memo at 50k entries rolled over ~30 * times per walk and so was never warm when it mattered. Entries are small * (a relative directory string plus a boolean), so 200k reserves a few tens * of MB at the very top end while covering any HQ tree seen so far. * * Verdicts for files inside an INCLUDED folder are deliberately not retained: * they still cost a matcher pass on every probe. That is the trade — matcher * microseconds per file for memory proportional to directories — and the * matcher's own per-path cache is bounded separately by * {@link IGNORE_MATCHER_REBUILD_INTERVAL}. * * The memo is per-filter, not process-global: ignore files are read when a * filter is constructed, and a later filter must observe a later * `.hqignore`/`.hqinclude` edit. Correctness never depends on admission. */ export declare const IGNORE_FILTER_MEMO_LIMIT = 200000; /** * `ignore` caches every path it is handed in a private per-instance object, * files included, so a walk would grow that cache without bound no matter how * small our own memo is. The supported way to bound it is to rebuild the * instances from the immutable configuration snapshot after a fixed number of * evaluations. A rebuild costs ~0.3 ms and leaves the folder memo intact: the * snapshot is immutable, so every retained verdict stays exact. */ export declare const IGNORE_MATCHER_REBUILD_INTERVAL = 50000; /** Optional instrumentation seam for the repeated-walk regression tests. */ export interface IgnoreFilterStats { /** Parent-directory memo hits. */ cacheHits: number; /** Parent-directory memo misses: one per newly seen folder. */ cacheMisses: number; /** Calls into the `ignore` package, for folders and for files alike. */ matcherInvocations: number; /** Current retained folder entries; useful for bounded-growth regression probes. */ cacheEntries?: number; /** Matcher instances created for this filter, including interval rebuilds. */ matcherGenerations?: number; } /** Internal tuning/testing options; callers normally pass only `hqRoot`. */ export interface CreateIgnoreFilterOptions { /** Folder memo bound; 0 disables retention entirely. */ memoLimit?: number; /** Matcher evaluations between rebuilds of the `ignore` instances. */ matcherRebuildInterval?: number; stats?: IgnoreFilterStats; /** Test seam for construction-time configuration reads. */ snapshotReader?: IgnoreMatcherSnapshotReader; } /** Callable filter plus a path-free count for the runner heap census. */ export interface IgnoreFilter { (filePath: string, isDir?: boolean): boolean; censusSize(): number; } /** * Pre-memoization implementation retained as a differential-test oracle. * @internal */ export declare function createIgnoreFilterReference(hqRoot: string): (filePath: string, isDir?: boolean) => boolean; export declare function createIgnoreFilter(hqRoot: string, options?: CreateIgnoreFilterOptions): IgnoreFilter; /** * Check if a file exceeds the max sync size (50MB default) */ export declare function isWithinSizeLimit(filePath: string, maxBytes?: number): boolean; //# sourceMappingURL=ignore.d.ts.map