Gate relocation noise on a universally seeded .gitignore

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-31 19:06:14 -04:00
parent 11981a2a99
commit cb87ea79c1
2 changed files with 2 additions and 2 deletions
+1 -1
View File
@@ -31,7 +31,7 @@ MyBoard.kanban/ ← board = the document
- `index.md` = YAML frontmatter between `---` delimiters, then Markdown body.
- **Reserved child names** at card level: `attachments/` (specified below) and `comments/` (enhanced schema — see below). Never levels themselves, never identity (identity-bearing siblings are UUIDs).
- **Name shape gates level detection** (settled): only folders passing the identity predicate (the shape-only rule above — 8-4-4-4-12 hex, any case, any version) are lane/card candidates. Anything else — even with an `index.md` inside — is a stray: skipped with a log warning, preserved verbatim, never rendered. A hand-made `notes/` folder or a board nested inside another can never brick a load; the cost — a hand-authored lane named `todo/` doesn't render — is what the warning and the agent guide exist for.
- A UUID-named folder below the root **lacking `index.md` is skipped with a log warning**, not an error — an interrupted two-step create must not brick the board. **Stray tolerance is uniform at every level**: non-reserved files and folders at board, lane, or card level are ignored by the loader, preserved verbatim, never rendered (inside a card, only `index.md` and the reserved child names mean anything). **One carve-out — loose *files* in a card folder are relocated, not tolerated** (settled 2026-07-28, Lanework-owns-the-board): a regular file sitting beside a card's `index.md` (not `attachments/`, not a reserved name) belongs in `attachments/`, and the app moves it there — Finder-style rename on collision — surfacing a graceful warning-tone notice naming the card and files ("Moved 'notes.txt' into attachments — 'Fix login'"; plurals fold). Detection stays read-only in the loader (the Repair precedent); the relocation runs through the Writer as an app-mediated write, commits normally on git boards, and waits out any read-only lock (strays stay tolerated until it clears). The carve-out is exactly that narrow: stray *folders* in a card (a nested clone, a hand-made subfolder), symlinks (never touched, above), and everything at board or lane level keep the verbatim posture — relocating a directory into the flat attachment model would be wrong, and board/lane-level strays (`CLAUDE.user.md`, a `notes/` folder) are legitimate residents. The agent guide teaches the rule (08-agent-integration.md): card files belong in `attachments/`. **Three board-root names are app-claimed, not strays** — the one scope on the verbatim promise: `CLAUDE.md` is app-owned and rewritten on guide version bumps, with a markerless user-authored `CLAUDE.md` relocated to `CLAUDE.user.md` (08-agent-integration.md ▸ The agent guide — that name is claimed too), and adding git seeds `.gitignore` (06-history-undo.md ▸ Repository hygiene; seeded once, then the user's to edit). Agents and hand-editors should treat these names as the app's at board root; everything else is untouched. **A claimed name held by the wrong kind of node is Lanework's to heal — by displacement** (ruled 2026-07-29 — Lanework owns the board, so an invalid artifact on a claimed name is a defect, not a resident): a regular file or symlink squatting `.trash` (a directory name), or a directory or symlink squatting `CLAUDE.md` (a file name), is moved aside by a scheduled heal (Validation and healing below) via the Finder-style rename ladder (`.trash``.trash 2`), **preserved verbatim, never destroyed**, with the relocation-style warning-tone notice naming old and new; the freed name then serves the app (the next delete mints the real `.trash/`; the guide write proceeds). **The rule is level-uniform** (extended 2026-07-29): a card's reserved child names are claimed the same way — a regular file or symlink squatting `attachments` (a directory name) displaces by the same ladder (`attachments``attachments 2`), so imports, Finder drops, and the sidebar listing never fail one gesture at a time against a squatted name; the displaced file, now an ordinary loose file, rides the next relocation into the real `attachments/` — the heals compose. The reserved-but-unconsumed `comments` is the timing principle's own illustration: nothing reads that name until the tracker era, so a wrong-kind holder is a tolerated stray today and joins the scheduled class the day the name becomes load-bearing. This upgrades the agent guide's former untouchable-skip to a displacement — the invariant that survives is displacement-never-destruction. One standing exception: the settled `CLAUDE.user.md`-taken skip stands — that displacement has a designated *destination*, and freeing a destination by a second displacement would cascade renames; the guide write skips with a log as settled (08-agent-integration.md).
- A UUID-named folder below the root **lacking `index.md` is skipped with a log warning**, not an error — an interrupted two-step create must not brick the board. **Stray tolerance is uniform at every level**: non-reserved files and folders at board, lane, or card level are ignored by the loader, preserved verbatim, never rendered (inside a card, only `index.md` and the reserved child names mean anything). **One carve-out — loose *files* in a card folder are relocated, not tolerated** (settled 2026-07-28, Lanework-owns-the-board): a regular file sitting beside a card's `index.md` (not `attachments/`, not a reserved name) belongs in `attachments/`, and the app moves it there — Finder-style rename on collision — surfacing a graceful warning-tone notice naming the card and files ("Moved 'notes.txt' into attachments — 'Fix login'"; plurals fold). Detection stays read-only in the loader (the Repair precedent); the relocation runs through the Writer as an app-mediated write, commits normally on git boards, and waits out any read-only lock (strays stay tolerated until it clears). **`.gitignore` is the noise gate** (ruled 2026-07-31): a file matching the board-root `.gitignore` — standard gitignore pattern semantics against the board-relative path; nested `.gitignore` files are ordinary strays the heal never consults — keeps the ordinary stray posture: skipped, preserved verbatim, logged, never relocated, never announced. The exclusion list is exactly that file, nothing hardcoded — the app seeds `.gitignore` on every board (06-history-undo.md ▸ Repository hygiene: `.DS_Store` plus the writer's own temp pattern `.*.lanework-*`), and fine-tuning what counts as noise over time means fine-tuning the seed or editing the board's copy. On Pro boards the same file governs the committer, so ignored noise neither relocates nor commits — one definition of noise, two consumers. The carve-out is exactly that narrow: stray *folders* in a card (a nested clone, a hand-made subfolder), symlinks (never touched, above), and everything at board or lane level keep the verbatim posture — relocating a directory into the flat attachment model would be wrong, and board/lane-level strays (`CLAUDE.user.md`, a `notes/` folder) are legitimate residents. The agent guide teaches the rule (08-agent-integration.md): card files belong in `attachments/`. **Three board-root names are app-claimed, not strays** — the one scope on the verbatim promise: `CLAUDE.md` is app-owned and rewritten on guide version bumps, with a markerless user-authored `CLAUDE.md` relocated to `CLAUDE.user.md` (08-agent-integration.md ▸ The agent guide — that name is claimed too), and the app seeds `.gitignore` on every board (re-ruled 2026-07-31 — at creation, healed in when missing at open, git or not; 06-history-undo.md ▸ Repository hygiene; seeded, then the user's to edit). Agents and hand-editors should treat these names as the app's at board root; everything else is untouched. **A claimed name held by the wrong kind of node is Lanework's to heal — by displacement** (ruled 2026-07-29 — Lanework owns the board, so an invalid artifact on a claimed name is a defect, not a resident): a regular file or symlink squatting `.trash` (a directory name), or a directory or symlink squatting `CLAUDE.md` (a file name), is moved aside by a scheduled heal (Validation and healing below) via the Finder-style rename ladder (`.trash``.trash 2`), **preserved verbatim, never destroyed**, with the relocation-style warning-tone notice naming old and new; the freed name then serves the app (the next delete mints the real `.trash/`; the guide write proceeds). **The rule is level-uniform** (extended 2026-07-29): a card's reserved child names are claimed the same way — a regular file or symlink squatting `attachments` (a directory name) displaces by the same ladder (`attachments``attachments 2`), so imports, Finder drops, and the sidebar listing never fail one gesture at a time against a squatted name; the displaced file, now an ordinary loose file, rides the next relocation into the real `attachments/` — the heals compose. The reserved-but-unconsumed `comments` is the timing principle's own illustration: nothing reads that name until the tracker era, so a wrong-kind holder is a tolerated stray today and joins the scheduled class the day the name becomes load-bearing. This upgrades the agent guide's former untouchable-skip to a displacement — the invariant that survives is displacement-never-destruction. One standing exception: the settled `CLAUDE.user.md`-taken skip stands — that displacement has a designated *destination*, and freeing a destination by a second displacement would cascade renames; the guide write skips with a log as settled (08-agent-integration.md).
- **Symlinks are never traversed** (settled): the loader and the watcher treat any symbolic link in the board tree — identity-shaped name or not — as a stray: skipped with a log warning, preserved verbatim, never rendered, never followed. A link passing the identity predicate would otherwise render bytes living outside the board that FSEvents never reports — the silently-stale board 02-architecture.md structurally excludes — and a link cycle would make the tree walk non-terminating. Copy flows copy the link itself, never its target: Duplicate, Save as Template, instantiation, cross-board copies, and clipboard staging preserve the link verbatim as they do any stray.
- **Atomic writes**: temp file, rename over `index.md`.
- **Unknown frontmatter keys and their order are preserved verbatim** on every rewrite. This is load-bearing for agent overlays (`project:`, `sphere:`, custom tags) — see 08-agent-integration.md.
+1 -1
View File
@@ -71,7 +71,7 @@ Agent and hand edits arrive through the watcher like any change and get auto-com
## Repository hygiene
- **`.gitignore` seeded at init, never touched after.** Adding git to a board writes a minimal `.gitignore` (`.DS_Store`) if none exists; the app never edits an existing one and never manages the file afterward — it's the user's from then on. (Repo-nested boards have no app-managed git, so no app `.gitignore` either.)
- **`.gitignore` seeded on every board, never touched after** (re-ruled 2026-07-31 — the file outgrew git: it is the one noise definition the loose-file relocation heal obeys, 01-storage-format.md ▸ Rules, so every board carries it, git or not). Board creation writes the minimal seed — `.DS_Store` plus the writer's temp pattern (`.*.lanework-*`) — and a board missing the file gains it by scheduled heal at open (the guide-refresh cadence; deletion is answered by re-seeding, and the escape hatch for wanting no exclusions is an *empty* file, which the app honors and never rewrites). The app never edits an existing `.gitignore` — it's the user's from the seed on, and fine-tuning what counts as noise over time means fine-tuning the seed. Repo-nested boards are seeded too (re-ruling the old no-app-`.gitignore` posture): the file serves the heal there, not any app-managed git; the visible untracked file in the user's repository is accepted on the agent-guide precedent, with the honest side effect that the enclosing repo's git reads it for paths under the board.
- **Repo growth is accepted.** Unbounded history is the price of never-rewrite, and every attachment version lives in the repo forever. The app may run safe libgit2 housekeeping (repacking loose objects) periodically — it rewrites nothing. **"Periodically" has numbers** (blessed 2026-07-31, all injectable): the pack runs when the loose-object count crosses **6,700** — git's own `gc.auto` default, borrowed for the identical reason — **8 s after session activation** (outlasting the committer's launch catch-up), at background priority, skipped under a pause, a lock, or an in-flight commit, and **attempted at most once per session** — hygiene must never become a hot loop; a skipped or failed pack waits for the next open. **Packs accumulate — the pass never consolidates, rewrites, or deletes existing packs** (blessed 2026-07-31): it deletes only loose files it proved redundant against the pack it just wrote, so a long-lived board accrues roughly one pack per threshold's worth of objects, forever — the never-rewrite posture's accepted cost (git tolerates many packs gracefully; a terminal `git gc` consolidates at the user's discretion). **SHA-256 repositories are unsupported, safely** (blessed 2026-07-31): an adopted SHA-256 repo the engine cannot open takes the corrupt-repo loud-failure path — never a silent fall to mode-none; should one ever open, hygiene abstains by construction — its loose-object filter matches the SHA-1 filename shape only, so its work list is empty and the repo is left untouched (recognizing nothing and touching nothing is the fail-safe direction; a looser match that deleted half-recognized files would be the dangerous one). Content-removing compaction is explicitly out (it would rewrite history); size tooling joins the wishlist if growth ever bites in practice.
- **Deleting never forgets.** On a git board, deleting a card removes it from the board but never from history — every version of its content and attachments stays reachable in any git client, and even a trash purge (Empty Trash, or the future age-based auto-purge — 01-storage-format.md) only cleans the working tree. This is part of the design; users should learn it here, not from a repo browser.