Frontmatter
| title | feat(ai): ai/scripts/bootstrapWorktree.mjs + AGENTS_STARTUP note (#10095) |
| author | tobiu |
| state | Merged |
| createdAt | Apr 19, 2026, 3:10 PM |
| updatedAt | Apr 19, 2026, 3:30 PM |
| closedAt | Apr 19, 2026, 3:30 PM |
| mergedAt | Apr 19, 2026, 3:30 PM |
| branches | dev ← agent/10095-worktree-bootstrap |
| url | https://github.com/neomjs/neo/pull/10099 |
5 passed (670ms)

Input from Claude Opus 4.7 (Claude Code):
❋ # PR Review Summary
Status: Comment (ready for human merge; non-blocking polish noted)
Self-Review Opening: Self-review of #10095. Chose
featoverfixbecause the bootstrap command itself IS a new capability — worktrees weren't previously supported for SDK scripts out of the box. Structured as exported function + CLI guard for testability; the spec drives the pure function with tmp fake-main/fake-worktree dirs rather than spawning git, which avoids environment entanglement and runs in 670ms. Course-correction lesson from #10098 applied: the symlink trap was documented with its exact error signature BEFORE filing this PR, so a future agent can't even try the same doomed shortcut.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 93 — Three concerns separated cleanly: git resolution (resolveMainCheckout), pure copy logic (bootstrapWorktree), and CLI glue. The pure function is what the spec exercises; the CLI is thin. MatchesanalyzeNlTelemetry.mjsprecedent forai/scripts/. Lost 7 points for the CLI's inlinemainguard — usingprocess.argv[1] === fileURLToPath(import.meta.url)works but could break if invoked via an unusual launcher; Node 24+ hasimport.meta.mainwhich is cleaner. Not using it because target Node versions aren't stated anywhere I could find.
[CONTENT_COMPLETENESS]: 94 — Top-of-file JSDoc explains both the bug AND the symlink trap inline (future agent reading source doesn't need to follow links).resolveMainCheckoutandbootstrapWorktreeboth have structured@param/@returnsblocks. PR body uses Fat Ticket convention. The AGENTS_STARTUP note is prescriptive ("Before running any SDK-consuming script...") not descriptive, so agents know what to DO, not just what's broken.
[EXECUTION_QUALITY]: 90 — Five Playwright cases cover the realistic failure modes: full copy, partial-seed idempotency, main-checkout refusal, missing-in-source handling, and the export contract. Live CLI round-trip verified end-to-end against the real repo (delete + bootstrap + confirm content). What's missing: (a) no spec forresolveMainCheckoutitself — I treat the git-porcelain parsing as trusted; if git ever changes its output format the script breaks silently; (b) no coverage of the case whereprojectRoot === mainCheckoutbut the paths are expressed differently (trailing slash, symlinked parent, etc.) —path.resolve()should handle it but untested.
[PRODUCTIVITY]: 95 — Identified as highest-ROI of the three remaining tickets, filed, branched, built, tested, verified, and PR'd in one session. Chosen specifically to unblock the fresh-session pivot on #10030.
[IMPACT]: 82 — Every Claude Code worktree session is unblocked for SDK-consuming work forever. Compounds with every #10030-tier session that now starts running instead of debugging ERR_MODULE_NOT_FOUND. Not higher because the blast radius is the Claude Code harness specifically, not the framework.
[COMPLEXITY]: 30 — One new script, one doc edit, one spec. No framework changes.
[EFFORT_PROFILE]:Quick Win— High ROI for every future worktree session at minimal complexity.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #10095
- Related Graph Nodes:
- #10091, #10093, #10098 (three prior PRs in this session that each discovered and manually worked around this bootstrap wall)
feedback_harness_isolation_models.md(the memory note about worktree vs shared-checkout branching models)feedback_distributed_artifacts_invert_local_fs.md(lesson from #10098 about paths in distributed artifacts — conceptually adjacent: this PR is about paths in local-worktree artifacts)
🧠 Graph Ingestion Notes
[KB_GAP]: Two things are now documented in AGENTS_STARTUP that were undocumented before: (1) the ERR_MODULE_NOT_FOUND symptom on fresh worktrees, and (2) the symlink-under-unitTestMode collision. Both were discovered empirically during #10090 and #10092 — had the docs already had them, I would have saved ~30 minutes total across those two PRs. Reinforces the pattern that undocumented environmental pitfalls compound across agent sessions.
[KB_GAP]:ai/services.mjsSDK facade documents its imports via a JSDoc@moduleblock (line 212-229) but doesn't mention thatconfig.mjsfiles are a runtime dependency that may not be present. Adding a@preconditionline pointing atbootstrapWorktree.mjswould make the failure mode discoverable from the consumer's side, not just the session-initialization side. Out of scope here.
[TOOLING_GAP]: Running the Playwright spec initially failed withSyntaxError: Missing semicolon. (3:60)because my JSDoc containedai/mcp/server/*/config.mjs— the*/substring inside a/** ... */comment terminates the block prematurely, turning the rest of the comment into invalid JavaScript. Babel's error message pointed at column 60 of line 3 which was a specific-but-still-cryptic pointer. Worth a repo-wide lint rule: forbid*/substrings inside JSDoc bodies. Out of scope; adding a rule would require choosing a linter direction I don't have context on.
[RETROSPECTIVE]: This PR turns a compounding-cost problem (every new agent session eats 15 min rediscovering the same workaround) into a one-time fix. The asymmetry is the point — friction baked into the initialization path multiplies across the swarm. Any future "agents keep hitting X" observation is a candidate for this treatment.
[RETROSPECTIVE]: The symlink-trap documentation is load-bearing. Without it, the next agent facing the ERR_MODULE_NOT_FOUND is reasonably likely to reach forln -sfirst (it's the "obvious" Unix answer to "I need this file that exists over there") and then get a completely unrelated-lookingNamespace collision in unitTestModeerror. Documenting the trap with its exact error string means a future agent will grep for that string and land on the fix.
📋 Required Actions
No hard blockers. Optional polish items that could fold into a later cleanup PR or drop:
- (Optional) Add a spec for
resolveMainCheckoutusing a stubbed git output, guarding against git's porcelain format ever shifting.- (Optional) Switch the CLI
isMaindetection toimport.meta.mainonce a Node baseline is chosen (Node 24+ supports it natively).- (Optional) Add a
@preconditionJSDoc line onai/services.mjspointing atbootstrapWorktree.mjsso consumers discover the dependency from either direction.- (Optional) Repo-level lint rule forbidding
*/inside JSDoc bodies — would have caught my syntax error at edit time rather than at spec-run time.None of these block merge. Reviewer can merge as-is and fold any that matter into a follow-up.
Summary
Every fresh Claude Code worktree under
.claude/worktrees/hitsERR_MODULE_NOT_FOUNDthe first time any script importsai/services.mjs:Root cause:
ai/mcp/server/<name>/config.mjsis gitignored (copy-from-template files for local overrides), so fresh worktrees inherit the tracked tree without them. Symlinks from the main checkout are NOT a workaround — Node resolves relative imports inside a symlinked module against the canonical target path, soconfig.mjs'simport Base from '../../../../src/core/Base.mjs'loads the main-checkout's Base.mjs. When the worktree-local Base.mjs also gets imported (e.g., by a Playwright spec),Neo.setupClasssees the same namespace registered from two different file paths and throwsNamespace collision in unitTestMode for Neo.core.Base. Copies have their own canonical path inside the worktree and resolve correctly.Impact
Discovered during #10090 (PR #10091) and again during #10092 (PR #10093). Both times the workaround was manually copying four config.mjs files from the main checkout — ~15 minutes of friction per session. Next fresh worktree session on #10030 (Concept Ontology) would hit the same wall. This PR unblocks every future Claude Code session.
Changes
ai/scripts/bootstrapWorktree.mjsexports:resolveMainCheckout(cwd)— parsesgit worktree list --porcelain; the firstworktree <path>line is always the primary working tree regardless of where the command is invoked.bootstrapWorktree({mainCheckout, projectRoot, configs, log})— pure copy logic, idempotent, refuses to run when invoked against the main checkout (no-op). Splits into{copied, skipped, missing}buckets for observable outcomes.CLI entry point resolves
mainCheckoutvia git and logs a one-line summary. Usesprocess.argv[1]-vs-import.meta.urlmain guard so the module is importable by the spec without running the CLI.AGENTS_STARTUP.md— newWorktree Bootstrap (Claude Code)subsection underHarness Memory-File Wiringdocumenting the error, the required bootstrap command, and the symlink trap with its exact error signature. Future agents don't rediscover it.test/playwright/unit/ai/scripts/bootstrapWorktree.spec.mjs— five cases against tmp fake-main-checkout + fake-worktree dirs:BOOTSTRAP_CONFIGSlist.Test Coverage
Live CLI Verification
rm ai/mcp/server/github-workflow/config.mjs node ai/scripts/bootstrapWorktree.mjs <h1 class="neo-h1" data-record-id="7">copied: ai/mcp/server/github-workflow/config.mjs</h1> <h1 class="neo-h1" data-record-id="8">skip (exists): ai/mcp/server/knowledge-base/config.mjs</h1> <h1 class="neo-h1" data-record-id="9">skip (exists): ai/mcp/server/memory-core/config.mjs</h1> <h1 class="neo-h1" data-record-id="10">skip (exists): ai/mcp/server/neural-link/config.mjs</h1> <h1 class="neo-h1" data-record-id="11">✓ Bootstrap complete: 1 copied, 3 skipped, 0 missing (4 total)</h1>Commit Type
Chose
featper pull-request-workflow §3.1 — unlocks a new capability (fresh-worktree-ready in one command) that did not exist before.fixwas considered since the underlying wall is a failure mode, but the fix IS the new capability, and worktrees weren't previously supported out of the box for SDK scripts.Acceptance Criteria
ai/scripts/bootstrapWorktree.mjs— resolves main checkout via git, copies fourconfig.mjsfiles, idempotentAGENTS_STARTUP.mdupdated with the worktree bootstrap note under Harness Memory-File WiringTest plan
npm run test-unit -- test/playwright/unit/ai/scripts/bootstrapWorktree.spec.mjs(5 passed)node ai/scripts/bootstrapWorktree.mjsand confirm any SDK-consuming script now works without manual interventionResolves #10095