feat: link once — ancestor walk-up for project links (git-style)#23
Conversation
readProject resolves the nearest ancestor link, so every command works from any subdirectory of a linked project; writeProject targets the found root (branch switch in src/ no longer mints a nested link). Fresh links in unlinked trees still bind cwd. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GMbAe5K1RfwaAcge5inX7P
jwfing
left a comment
There was a problem hiding this comment.
Review: feat: link once — ancestor walk-up for project links
Summary: Clean, well-tested implementation of git-style ancestor walk-up for .insta/project.json; no blocking issues, but the walk-up-on-write also changes the semantics of the link-establishing commands, which is worth a look.
Requirements context
No matching spec/plan found — this repo has no docs/superpowers/, docs/specs/, or docs/ directory at all. Assessed against the PR description/title and the intent stated in the code comments alone.
Findings
Critical
(none)
Suggestion
-
Functionality — walk-up on write silently repoints an ancestor link for link-establishing commands (
src/config.ts:67-71).writeProjectnow walks up unconditionally, but it's shared by three call sites with different intents:branchSwitch(src/commands/branch.ts:25) — mutates an existing link. Walk-up is exactly the fix you want here. ✅projectCreate(src/commands/project.ts:26) andprojectLink(src/commands/project.ts:46) — these establish a binding. With walk-up, runninginsta project link <id>orinsta project createfrom a subdirectory of an already-linked tree no longer creates a link at cwd — it overwrites the nearest ancestor.insta/project.json. Since the walk climbs to the filesystem root, a single stray link high in the tree (e.g. an accidental~/.insta/project.json) makes everyproject link/createunder$HOMEsilently repoint that one link instead of linking the current directory — effectively making it impossible to link a second project in the subtree. The overwrite is usually recoverable via git, but it's surprising. Consider having link-establishing commands (project link/project create) targetcwdand reserving walk-up for the mutating path (branch switch), e.g. awriteProject(c, cwd, { walkUp })flag. This is a judgment call and consistent with the "link once" philosophy, so non-blocking — but worth a deliberate decision.
-
Software engineering — missing test for the case above (
test/project-link.test.ts). The two claimed behaviors and the null case are well covered. Not covered:project link/createinvoked from a subdirectory of an existing link. Given the semantic change above, a test pinning the intended behavior (whether it overwrites the root or links at cwd) would prevent a future regression either way.
Information
- Performance — double read of
project.json(src/config.ts:42-63).findProjectRootreads the file to locate the root, thenreadProjectreads and parses it again. HavingfindProjectRootreturn the file contents (orreadProjectparse what it already read) would halve the I/O. Negligible in practice for a CLI; noting only. - Correctness (edge) —
findProjectRoot'scatchswallows all errors (src/config.ts:45-48). A permission error (EACCES) on a real link, or a malformed-JSON nearest link, is treated the same as "not here": the malformed case makesreadProjectreturnnullwithout continuing to a valid ancestor. This matches the prior single-directory behavior (which also returnednullon bad JSON), so no regression — just be aware a broken nearest link masks a good ancestor. - Design note — walk-up to filesystem root is intended git-style behavior. The loop correctly terminates at the fs root (
parent === dir), and this mirrors howgitresolves.git. Flagged only to make the "ancestor anywhere up to/" reach explicit, since it underpins the Suggestion above.
Security: No security-relevant changes. No new user input reaches SQL/shell/HTTP; project.json holds projectId/orgId/branch (not secrets); nothing new is logged or returned. Filesystem reads are read-only ancestor walks.
Coding standards: Matches repo conventions — node: import prefixes, .js extension on relative imports, clear intent-comments, resolve() for normalization. Import additions (dirname, resolve) are all used.
Verdict
approved — zero Critical findings. The Suggestion items are worth a look (especially the link-establishing-command semantics) but none block merge. (This is an informational review; explicit GitHub approval remains a separate human action.)
"Link once and let it work": previously the CLI only checked the exact cwd for
.insta/project.json, so commands failed from subdirectories and abranch switchinsrc/minted a nested second link. Now git-style:readProjectwalks up to the nearest ancestor link;writeProjectupdates the found root. Committing.insta/project.jsonalready gives the team/CI the binding on clone — with walk-up, insta's link UX now beats Railway's per-machine registry (their teammates must link by hand). TDD: 3 red→green tests; 47/47.🤖 Generated with Claude Code
https://claude.ai/code/session_01GMbAe5K1RfwaAcge5inX7P
Summary by cubic
Add git-style ancestor walk-up for
.insta/project.jsonso you can link once and run commands from any subdirectory. Prevents nested links; branch switches now update the root link.readProjectwalks up to find the nearest.insta/project.json.writeProjectwrites to the found root; falls back tocwdonly when unlinked.Written for commit eca2b67. Summary will update on new commits.