LearnNewsExamplesServices
Frontmatter
id10435
titlebootstrapWorktree: support independent-clone canonical via --canonical-root
stateClosed
labels
enhancementaiarchitecture
assigneesneo-opus-ada
createdAtApr 27, 2026, 7:57 PM
updatedAtJun 7, 2026, 7:22 PM
githubUrlhttps://github.com/neomjs/neo/issues/10435
authorneo-opus-ada
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtApr 27, 2026, 8:09 PM

bootstrapWorktree: support independent-clone canonical via --canonical-root

Closed v13.0.0/archive-v13-0-0-chunk-6 enhancementaiarchitecture
neo-opus-ada
neo-opus-ada commented on Apr 27, 2026, 7:57 PM

Context

Follow-up to #10433 (granular per-subdir symlinking for git worktrees). Surfaced empirically during the post-#10433 verification turn (2026-04-27), with cross-family agreement from @neo-gemini-pro (Antigravity).

bootstrapWorktree.mjs currently uses git worktree list --porcelain to resolve canonical. This works perfectly for git worktrees off a shared canonical checkout, but skips independent clones (e.g., /Users/Shared/antigravity/neomjs/neo/ is a separate clone, not a worktree of /Users/Shared/github/neomjs/neo/). When the script runs in an independent clone, it correctly identifies that as the "main checkout" and skips the symlink operations — leaving the clone's .neo-ai-data/wake-daemon/ (and other subdirs) as physical isolated dirs.

Empirical anchor: 2026-04-27, the bridge daemon ran in canonical (PID 12039), logging to canonical's bridge.log. The Antigravity clone's IDE was tailing its OWN local .neo-ai-data/wake-daemon/bridge.log (silent, since the daemon wasn't writing there). @neo-gemini-pro manually rm -rf + ln -s'd wake-daemon/ to bridge the gap. The other 6 substrate-data subdirs in the Antigravity clone had been hand-curated to the same pattern back on April 22.

The Problem

The Antigravity clone's .neo-ai-data/ was already manually configured to identical structure as a post-#10433 worktree:

Subdir Antigravity clone Worktree (post-#10433)
backups, chroma, datasets, neo-sqlite, sqlite symlinks → canonical symlinks → canonical
concepts/ regular dir ✓ regular dir ✓
wake-daemon symlink → canonical (manually added 2026-04-27) symlink → canonical

Same architectural pattern, same gitignore-boundary discipline (concepts/ untouched), same per-subdir granularity. The only difference is canonical-resolution mechanism: git worktrees use git worktree list, independent clones need an explicit override.

The Architectural Reality

ai/scripts/bootstrapWorktree.mjs:resolveMainCheckout is the single chokepoint that distinguishes worktree-mode from independent-clone-mode. Currently:

export async function resolveMainCheckout(cwd) {
    const {stdout} = await execFileAsync('git', ['worktree', 'list', '--porcelain'], {cwd});
    const match    = stdout.match(/^worktree (.+)$/m);
    return match ? match[1] : null;
}

For independent clones, git worktree list --porcelain returns the clone's OWN root as the first entry. The script then sees path.resolve(projectRoot) === path.resolve(mainCheckout) and correctly enters main-checkout mode (no-op). The fix: layer an explicit canonical-root override on top.

The per-subdir symlink logic (#10433's DATA_SUBDIRS_TO_LINK + symlinkDataDir) is already topology-agnostic. Once canonical is resolved correctly, the existing logic does the right thing. No changes needed beyond the resolution layer.

The Fix

Extend resolveMainCheckout with an explicit-override option:

export async function resolveMainCheckout(cwd, {explicitRoot} = {}) {
    if (explicitRoot) return path.resolve(explicitRoot);
    const {stdout} = await execFileAsync('git', ['worktree', 'list', '--porcelain'], {cwd});
    const match    = stdout.match(/^worktree (.+)$/m);
    return match ? match[1] : null;
}

CLI surface (additive — worktree case unchanged):

<h1 class="neo-h1" data-record-id="6">Worktree case (existing behavior)</h1>

node ai/scripts/bootstrapWorktree.mjs --link-data

<h1 class="neo-h1" data-record-id="7">Independent-clone case (new)</h1>

node ai/scripts/bootstrapWorktree.mjs --link-data --canonical-root /Users/Shared/github/neomjs/neo

<h1 class="neo-h1" data-record-id="8">Or via env var (composable in CI / shell aliases)</h1>

NEO_AI_CANONICAL_ROOT=/Users/Shared/github/neomjs/neo \
    node ai/scripts/bootstrapWorktree.mjs --link-data

CLI handler resolves --canonical-root flag → falls back to NEO_AI_CANONICAL_ROOT env var → falls back to git-worktree-list (the current behavior, unchanged).

Decision: Keep the filename bootstrapWorktree.mjs

Cross-family-validated by @neo-gemini-pro: option 1 (keep filename, broaden file-head JSDoc) over rename. Rationale:

  • Renames break muscle memory and external documentation references
  • Scope change is small (resolution-layer extension)
  • File-head JSDoc already discusses "data symlinking across worktree+main checkouts" — broadening to include "sibling independent clones" is a documentation tweak

Decision: NO DATA_FILES_TO_LINK allowlist (top-level cruft cleanup instead)

Empirical investigation of the bonus question (whether to add file-symlink support for .neo-ai-data/memory-core.sqlite):

  • Zero references in any source file. The only 'memory-core.sqlite' literal is in ai/mcp/server/neural-link/config.template.mjs:36, which resolves to path.join(os.homedir(), '.neo-ai-data', 'memory-core.sqlite') — a different filesystem location entirely (~/.neo-ai-data/...).
  • File is 0 bytes since 2026-04-13 (14 days, never written to)
  • Not git-tracked
  • Substrate has moved into sqlite/ and neo-sqlite/ subdirs

Verdict: pure tech debt from an old code path. Adding DATA_FILES_TO_LINK would propagate cruft. Better to clean it up: delete the file from canonical (it's gitignored and unreferenced; broken symlinks in clones are harmless because nothing reads the path).

This may belong as a separate cleanup ticket if the broken-symlink-in-clones cosmetic concern warrants it; for the scope of this ticket, the recommendation is "no file-symlink support, plus a note that the existing top-level file can be safely deleted from canonical."

Acceptance Criteria

  • resolveMainCheckout(cwd, {explicitRoot}) accepts an optional explicitRoot parameter; returns it directly when set
  • CLI mode supports --canonical-root <path> flag
  • CLI mode supports NEO_AI_CANONICAL_ROOT env var (flag takes precedence)
  • Running with explicit canonical-root from an independent clone applies the existing per-subdir symlink logic correctly
  • concepts/ is still NEVER symlinked (#10433 invariant preserved)
  • Worktree behavior unchanged (existing tests pass)
  • Test coverage: explicit-root resolution, env-var fallback, flag-precedence-over-env-var, full per-subdir flow from independent-clone perspective

Out of Scope

  • Renaming the script or its exports (cross-family validated to skip)
  • Adding DATA_FILES_TO_LINK for .neo-ai-data/memory-core.sqlite (confirmed cruft; cleanup vs propagation is a separate concern)
  • Sibling-clone heuristic auto-discovery (e.g., walking parent dirs to find clones with matching origin URL) — out of scope, too much magic
  • Migration tooling for existing manually-curated independent clones (one-time --canonical-root invocation handles them; no migration needed)
  • Cleanup of the unreferenced top-level memory-core.sqlite cruft file (warrants its own tiny ticket if pursued)

Avoided Traps

  • Trap: Auto-discover sibling clones via git config remote.origin.url matching. Avoided: too much filesystem-walk + URL-comparison magic; explicit override is more transparent and predictable.
  • Trap: Rename the script to reflect the broader scope. Avoided: rename cost > clarity value at this scope; cross-family validated to keep the filename.
  • Trap: Add DATA_FILES_TO_LINK to mirror the antigravity clone's memory-core.sqlite symlink. Avoided: empirical evidence shows the top-level file is unreferenced 0-byte cruft; symlinking it would propagate the tech debt across substrates.

Related

  • Predecessor: #10433 (granular per-subdir symlinking that this extends to independent clones)
  • Closed earlier predecessor: #10224 (coarse-grained parent-level symlink)
  • Substrate-level coherence the granular fix unblocks: #10424
  • Singleton enforcement that depends on wake-daemon/ symlinking: #10423, #10425

Origin Session ID: b3c0bfb8-44e1-4646-9c62-110ef16b0fad

Retrieval Hint: "bootstrapWorktree independent clone canonical-root antigravity sibling checkout"

tobiu closed this issue on Apr 27, 2026, 8:09 PM