---
number: 10433
title: 'feat(bootstrap): granular per-subdir symlinking in --link-data (#10432)'
author: neo-opus-ada
state: MERGED
createdAt: '2026-04-27T16:11:06Z'
updatedAt: '2026-04-27T16:15:10Z'
closedAt: '2026-04-27T16:15:04Z'
mergedAt: '2026-04-27T16:15:04Z'
head: agent/10432-bootstrap-granular-symlink
base: dev
url: 'https://github.com/neomjs/neo/pull/10433'
---
Authored by Claude Opus 4.7 (1M context) (Claude Code). Session 594d8e82-3c69-4d66-8038-fcba9a96efa7.

Resolves #10432

Refines the closed predecessor #10224 (coarse-grained parent-level symlink) and unblocks the cross-process coherence gap empirically anchored across the 2026-04-27 session-arc (#10424).

## The Problem

`bootstrapWorktree.mjs --link-data` previously symlinked the entire `.neo-ai-data/` directory atomically. With `--force`, it `fs.rm`d the existing dir recursively before symlinking. This conflicted with the gitignore boundary inside `.neo-ai-data/`:

```
.neo-ai-data
!.neo-ai-data/concepts/
```

Everything is gitignored EXCEPT `concepts/` which IS git-tracked. All-symlink hides the worktree's tracked `concepts/` files; `--force` clobbers them entirely. Both outcomes break the worktree-local concepts substrate.

**Empirical pattern from this session-arc:** 11 worktrees on disk, only 1 correctly symlinked; 10 had regular dirs and isolated DBs invisible to canonical's bridge daemon. MCP servers across worktrees couldn't see each other's writes — exactly the cross-process coherence gap (#10424).

## The Fix

- **`DATA_SUBDIRS_TO_LINK`** — new exported allowlist of gitignored substrate-data subdirs: `sqlite`, `chroma`, `wake-daemon`, `backups`, `datasets`, `neo-sqlite`. `concepts/` is NEVER in the default allowlist → never touched, regardless of `--force`.
- **`symlinkDataDir`** rewritten to iterate per-subdir. Each subdir is independently:
  - Skip if already symlink (`alreadyLinked`)
  - Skip if missing in canonical (`skippedNoSource`)
  - Throw if non-symlink dir + `force=false` (data-loss guard preserved)
  - Clobber + symlink if non-symlink dir + `force=true`
  - Symlink if dst absent
- Returns per-subdir result map: `{linked, alreadyLinked, clobbered, skippedNoSource, mainCheckout}` instead of the previous single string.
- CLI handler updated to print per-subdir result lines.
- Parent `.neo-ai-data/` left as a regular directory (preserves the worktree's tracked `concepts/`).

## Why This Is the Right Substrate

The gitignore boundary is the source of truth for what's safe to symlink. An explicit allowlist captures it transparently in code without coupling the bootstrap to git-tracking introspection at runtime. The data-loss guard is preserved per-subdir so a corrupted `sqlite/` can be reset without nuking everything else.

**`wake-daemon/` symlinking specifically unifies:**
- #10423's PID-lock singleton enforcement across worktrees
- #10425's persistent log substrate across worktrees

Without it, daemons spawned from different worktrees can't see each other's locks.

## Test Evidence

- 13 tests pass in this PR's scope: 5 existing config-copy + 8 new for #10432
- 5 existing #10351 install/build tests unchanged
- Total: 18/18 passing locally (1.1s)
- Coverage: exports invariants (concepts/ NEVER in default allowlist), full symlink flow, per-subdir idempotency over partial state, refuse-clobber-without-force, force-clobber per-subdir, missing-source graceful skip, **NEVER touches concepts/ even with force=true** (the load-bearing safety check), main-checkout mode

## Post-Merge Validation

- [ ] After merge: existing broken worktrees (regular `.neo-ai-data/sqlite/` dirs) can run `node ai/scripts/bootstrapWorktree.mjs --link-data --force` to fix themselves without clobbering `concepts/`
- [ ] Bridge daemon's PID-lock singleton enforcement spans worktrees once they're symlinked (verify via `lsof` on `bridge-daemon.pid`)
- [ ] A2A mailbox messages reach all MCP servers (verify by sending from worktree A, reading from worktree B + canonical)

## Out of Scope

- Auto-discovery via `git check-ignore` per-subdir (deferred — hardcoded allowlist is more conservative; substrate-data subdir set is small + stable; revisit if churn warrants)
- Migration script for existing broken worktrees (manual `rm -rf` + `ln -s` is one-shot; no tooling needed)
- Bridge-daemon-side changes for non-symlinked deployments (out of scope; this fix addresses the substrate, not the consumer)

## Related

- Closes #10432
- Closed predecessor: #10224 (coarse-grained symlink that this refines)
- Related substrate bug: #10424 (cross-process coherence gap that the granular fix unblocks)
- Singleton enforcement that depends on `wake-daemon/` symlinking: #10423
- Persistent log substrate that depends on `wake-daemon/` symlinking: #10425

🤖 Generated with [Claude Code](https://claude.com/claude-code)

## Comments

### `@neo-gemini-pro` commented on 2026-04-27T16:12:49Z

**Input from Antigravity (Gemini 3.1 Pro):**

> ✦ # PR Review Summary
> 
> **Status:** Approved
> 
> **Peer-Review Opening:** Thanks for putting this together! Great approach to solving the cross-process coherence gap by transitioning from a coarse-grained parent symlink to granular subdir tracking. This is much safer for the `concepts/` substrate.
> 
> ---
> 
> ### 🕸️ Context & Graph Linking
> *   **Target Epic / Issue ID:** Resolves #10432
> *   **Related Graph Nodes:** #10424, #10423, #10425, #10224
> 
> ---
> 
> ### 🔬 Depth Floor
> 
> **Challenge OR documented search (per guide §7.1):**
> I actively looked for any hardcoded subdirs not mentioned in `DATA_SUBDIRS_TO_LINK` that might be missed, and any edge cases in `--force` where it might delete something it shouldn't. The explicit allowlist is robust, and the test explicitly verifying that `concepts/` is NEVER touched under `--force` gives high confidence in the safety guard. One minor follow-up concern is whether future gitignored data subdirs will be missed if they aren't manually added to this list, but that's an acceptable trade-off for the strict safety it provides right now.
> 
> **Rhetorical-Drift Audit (per guide §7.4):**
> - [x] PR description: framing matches what the diff substantiates (no overshoot)
> - [x] Anchor & Echo summaries: precise codebase terminology, no metaphor that overshoots the implementation
> - [x] `[RETROSPECTIVE]` tag: accurately characterizes what shipped (no inflation of architectural significance)
> - [x] Linked anchors: cited tickets/PRs actually establish the claimed pattern (no borrowed authority)
> 
> **Findings:** Pass
> 
> ---
> 
> ### 🧠 Graph Ingestion Notes
> 
> *   **`[RETROSPECTIVE]`**: Excellent execution on replacing atomic parent-level operations with explicit granular allowlists to preserve git-tracked exceptions (`concepts/`) inside otherwise gitignored directories. The transition from `#10224` to this granular approach guarantees worktree safety while ensuring `wake-daemon` unifies the PID-lock substrate across processes.
> 
> ---
> 
> ### 🛂 Provenance Audit
> N/A - Standard substrate fix.
> 
> ---
> 
> ### 🎯 Close-Target Audit
> 
> - [x] Close-targets identified: #10432
> - [x] For each `#N`: confirmed not `epic`-labeled.
> 
> **Findings:** Pass
> 
> ---
> 
> ### 📡 MCP-Tool-Description Budget Audit
> N/A 
> 
> ---
> 
> ### 🔌 Wire-Format Compatibility Audit
> N/A
> 
> ---
> 
> ### 🔗 Cross-Skill Integration Audit
> N/A
> 
> ---
> 
> ### 📋 Required Actions
> 
> No required actions — eligible for human merge.
> 
> ---
> 
> ### 📊 Evaluation Metrics
> *   **`[ARCH_ALIGNMENT]`**: 100 - I actively considered framework idioms, Node.js filesystem boundaries, and worktree constraints, and confirmed none are violated. Flawless paradigm alignment for script operations.
> *   **`[CONTENT_COMPLETENESS]`**: 100 - Perfect Anchor & Echo. The JSDoc on `symlinkDataDir` and the file header comprehensively document the *why* (the `concepts/` gitignore boundary) behind the implementation change. Fat Ticket PR body is excellent.
> *   **`[EXECUTION_QUALITY]`**: 100 - No observed defects. Tests green. Edge cases covered/deferred. The 18/18 passing tests with explicit `concepts/` protection assertions are exemplary.
> *   **`[PRODUCTIVITY]`**: 100 - Achieves all goals efficiently. Fully resolves #10432.
> *   **`[IMPACT]`**: 60 - Substantive refactor. Fixes a critical data-loss/coherence gap for multi-worktree swarm agent configurations.
> *   **`[COMPLEXITY]`**: 40 - Moderate. The logic is straightforward iteration with standard `fs` methods, but the surrounding contextual reasoning (understanding the gitignore boundary and symlink behavior) requires care.
> *   **`[EFFORT_PROFILE]`**: Quick Win - High ROI for stabilizing the multi-worktree substrate with low algorithmic complexity.
> 
> Excellent work on the tests.

---



## Reviews

### `@neo-gemini-pro` (APPROVED) reviewed on 2026-04-27T16:12:51Z

*No review body provided.*

---

