feat(cli): task-setup.sh + per-worktree context registration#891
Conversation
Adds scripts/task-setup.sh and extends `lobu context add` so paired worktrees register themselves as managed contexts the menubar can spawn. Fixes the "we keep losing changes" submodule pain: 1. Detached-HEAD trap — script switches packages/owletto onto a real feat/<name> branch before any commits can happen. 2. bun.lock prune — submodule init runs before `bun install`. 3. Missing .env in fresh worktrees — copies from the main repo. 4. Port collisions — picks next free PORT / WORKER_PROXY_PORT. CLI changes: - LobuServerConfig.cwd: directory the lifecycle owner cd's into before spawning `lobu run`. No reader yet; menubar follow-up in owletto. - `lobu context add` now accepts --port, --host, --database-url, --data-dir, --cwd, --lifecycle. - addContext() takes an optional server config. Companion shell functions documented in the script header; not auto-installed (user adds to ~/.zshrc).
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughExtended CLI context commands to accept and persist optional per-context server configuration (including cwd) and added Makefile targets plus scripts to create/clean per-task git worktrees with port allocation, ChangesServer Configuration in CLI Context
Per-Task Worktree and Environment Setup Script
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/index.ts`:
- Around line 546-550: The CLI option parser for "--port <port>" currently uses
Number.parseInt(value, 10) which accepts partial/invalid inputs; change the
parser function for the "--port <port>" option to validate that the entire input
is a decimal integer (e.g., /^\d+$/) and that the numeric value falls in the
valid TCP port range (1–65535); on invalid input throw a descriptive Error (so
the CLI fails at parse time) and on valid input return the parsed Number; update
the option's anonymous parser where "--port <port>" is defined to replace
Number.parseInt with this strict-validate-and-parse logic.
In `@scripts/task-setup.sh`:
- Around line 105-111: The loop that scans for PORT/WORKER_PROXY_PORT only
iterates worktree .env.local files (for env_local in
"$repo"/.claude/worktrees/*/.env.local) and therefore misses the repository root
.env.local; update the scan used by the function that computes highest_port and
highest_proxy so it also reads "$repo/.env.local" (either prepend a check for
"$repo/.env.local" before the for-loop or extend the glob to include it), parse
PORT and WORKER_PROXY_PORT the same way using the existing awk/tr -d logic, and
ensure you still skip the current worktree_dir/.env.local and update
highest_port and highest_proxy accordingly.
- Around line 71-75: The worktree creation currently only checks local
refs/heads/$branch before creating a new branch from origin/main, which discards
remote-only branch history; change the logic around the git show-ref and git
worktree add calls to also detect a remote branch (refs/remotes/origin/$branch)
and, if present, add the worktree from that remote ref (use git worktree add
"$worktree_dir" "origin/$branch") instead of creating a new branch from
origin/main; keep using the existing variables repo, branch and worktree_dir and
follow the same pattern used in the submodule logic (check remote ref first,
then fallback to creating from origin/main).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9a586b45-37d4-486e-95f3-1b6c9f232d02
📒 Files selected for processing (5)
packages/cli/src/commands/context.tspackages/cli/src/index.tspackages/cli/src/internal/__tests__/context.test.tspackages/cli/src/internal/context.tsscripts/task-setup.sh
| if (cd "$repo" && git show-ref --verify --quiet "refs/heads/$branch"); then | ||
| (cd "$repo" && git worktree add "$worktree_dir" "$branch") | ||
| else | ||
| (cd "$repo" && git worktree add "$worktree_dir" -b "$branch" origin/main) | ||
| fi |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Verify whether remote feat/* branches exist without local counterparts.
# If output is non-empty, current logic can create divergent branches.
git for-each-ref --format='%(refname:short)' refs/remotes/origin/feat/ \
| sed 's#^origin/##' \
| while read -r b; do
git show-ref --verify --quiet "refs/heads/$b" || echo "$b"
doneRepository: lobu-ai/lobu
Length of output: 38
🏁 Script executed:
# Check if there are ANY remote branches
git branch -r | head -20Repository: lobu-ai/lobu
Length of output: 99
🏁 Script executed:
# Check the context where this script is called and what branches are used
cd scripts && head -100 task-setup.sh && echo "---" && grep -n "branch=" task-setup.sh || trueRepository: lobu-ai/lobu
Length of output: 3561
🏁 Script executed:
# Verify the git worktree syntax for the proposed fix - test the command structure
git help worktree | grep -A 20 "worktree add" | head -30 || echo "Testing command syntax only"Repository: lobu-ai/lobu
Length of output: 154
🏁 Script executed:
# Check if there are task branches or any naming conventions in use
git branch -a | grep -E "(task|feat)" | head -20 || echo "No matching branches found"Repository: lobu-ai/lobu
Length of output: 38
Handle remote-only task branches when creating the worktree.
Line 71 checks only local refs/heads/$branch. If origin/$branch exists but local does not, line 74 creates a new branch from origin/main, diverging from existing task history. This occurs when a worktree is created on a fresh clone or after local branch deletion, while the remote branch already exists.
Note: The submodule setup logic (lines 79–88) correctly implements the intended pattern by checking remote branches before fallback, confirming this should apply to the main worktree as well.
Proposed fix
if (cd "$repo" && git show-ref --verify --quiet "refs/heads/$branch"); then
(cd "$repo" && git worktree add "$worktree_dir" "$branch")
+ elif (cd "$repo" && git show-ref --verify --quiet "refs/remotes/origin/$branch"); then
+ (cd "$repo" && git worktree add --track -b "$branch" "$worktree_dir" "origin/$branch")
else
(cd "$repo" && git worktree add "$worktree_dir" -b "$branch" origin/main)
fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (cd "$repo" && git show-ref --verify --quiet "refs/heads/$branch"); then | |
| (cd "$repo" && git worktree add "$worktree_dir" "$branch") | |
| else | |
| (cd "$repo" && git worktree add "$worktree_dir" -b "$branch" origin/main) | |
| fi | |
| if (cd "$repo" && git show-ref --verify --quiet "refs/heads/$branch"); then | |
| (cd "$repo" && git worktree add "$worktree_dir" "$branch") | |
| elif (cd "$repo" && git show-ref --verify --quiet "refs/remotes/origin/$branch"); then | |
| (cd "$repo" && git worktree add --track -b "$branch" "$worktree_dir" "origin/$branch") | |
| else | |
| (cd "$repo" && git worktree add "$worktree_dir" -b "$branch" origin/main) | |
| fi |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/task-setup.sh` around lines 71 - 75, The worktree creation currently
only checks local refs/heads/$branch before creating a new branch from
origin/main, which discards remote-only branch history; change the logic around
the git show-ref and git worktree add calls to also detect a remote branch
(refs/remotes/origin/$branch) and, if present, add the worktree from that remote
ref (use git worktree add "$worktree_dir" "origin/$branch") instead of creating
a new branch from origin/main; keep using the existing variables repo, branch
and worktree_dir and follow the same pattern used in the submodule logic (check
remote ref first, then fallback to creating from origin/main).
| for env_local in "$repo"/.claude/worktrees/*/.env.local; do | ||
| [[ "$env_local" == "$worktree_dir/.env.local" ]] && continue | ||
| p="$(awk -F= '/^PORT=/{print $2; exit}' "$env_local" | tr -d '[:space:]')" | ||
| q="$(awk -F= '/^WORKER_PROXY_PORT=/{print $2; exit}' "$env_local" | tr -d '[:space:]')" | ||
| [[ -n "$p" && "$p" =~ ^[0-9]+$ && "$p" -gt "$highest_port" ]] && highest_port="$p" | ||
| [[ -n "$q" && "$q" =~ ^[0-9]+$ && "$q" -gt "$highest_proxy" ]] && highest_proxy="$q" | ||
| done |
There was a problem hiding this comment.
Include root .env.local in the collision scan.
Line 105 scans only worktree .env.local files. If $repo/.env.local uses non-default ports, this allocator can still assign conflicting values.
Proposed fix
-shopt -s nullglob
-for env_local in "$repo"/.claude/worktrees/*/.env.local; do
+shopt -s nullglob
+for env_local in "$repo"/.env.local "$repo"/.claude/worktrees/*/.env.local; do
+ [[ -f "$env_local" ]] || continue
[[ "$env_local" == "$worktree_dir/.env.local" ]] && continue
p="$(awk -F= '/^PORT=/{print $2; exit}' "$env_local" | tr -d '[:space:]')"
q="$(awk -F= '/^WORKER_PROXY_PORT=/{print $2; exit}' "$env_local" | tr -d '[:space:]')"
[[ -n "$p" && "$p" =~ ^[0-9]+$ && "$p" -gt "$highest_port" ]] && highest_port="$p"
[[ -n "$q" && "$q" =~ ^[0-9]+$ && "$q" -gt "$highest_proxy" ]] && highest_proxy="$q"
done📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for env_local in "$repo"/.claude/worktrees/*/.env.local; do | |
| [[ "$env_local" == "$worktree_dir/.env.local" ]] && continue | |
| p="$(awk -F= '/^PORT=/{print $2; exit}' "$env_local" | tr -d '[:space:]')" | |
| q="$(awk -F= '/^WORKER_PROXY_PORT=/{print $2; exit}' "$env_local" | tr -d '[:space:]')" | |
| [[ -n "$p" && "$p" =~ ^[0-9]+$ && "$p" -gt "$highest_port" ]] && highest_port="$p" | |
| [[ -n "$q" && "$q" =~ ^[0-9]+$ && "$q" -gt "$highest_proxy" ]] && highest_proxy="$q" | |
| done | |
| for env_local in "$repo"/.env.local "$repo"/.claude/worktrees/*/.env.local; do | |
| [[ -f "$env_local" ]] || continue | |
| [[ "$env_local" == "$worktree_dir/.env.local" ]] && continue | |
| p="$(awk -F= '/^PORT=/{print $2; exit}' "$env_local" | tr -d '[:space:]')" | |
| q="$(awk -F= '/^WORKER_PROXY_PORT=/{print $2; exit}' "$env_local" | tr -d '[:space:]')" | |
| [[ -n "$p" && "$p" =~ ^[0-9]+$ && "$p" -gt "$highest_port" ]] && highest_port="$p" | |
| [[ -n "$q" && "$q" =~ ^[0-9]+$ && "$q" -gt "$highest_proxy" ]] && highest_proxy="$q" | |
| done |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/task-setup.sh` around lines 105 - 111, The loop that scans for
PORT/WORKER_PROXY_PORT only iterates worktree .env.local files (for env_local in
"$repo"/.claude/worktrees/*/.env.local) and therefore misses the repository root
.env.local; update the scan used by the function that computes highest_port and
highest_proxy so it also reads "$repo/.env.local" (either prepend a check for
"$repo/.env.local" before the for-loop or extend the glob to include it), parse
PORT and WORKER_PROXY_PORT the same way using the existing awk/tr -d logic, and
ensure you still skip the current worktree_dir/.env.local and update
highest_port and highest_proxy accordingly.
The previous commit relied on a per-user zsh function. Make this team- friendly by exposing the workflow as Makefile targets the whole repo can use, and add a cleanup path that takes the worktree down safely: - `make task-setup NAME=<name>` — creates the paired worktree. - `make task-clean NAME=<name> [FORCE=1]` — removes the worktree, both feat/<name> branches, and the Lobu CLI context. Refuses by default when there's uncommitted work or unpushed commits; FORCE=1 overrides. - `lobu context rm <name>` — new CLI command + removeContext() helper. Idempotent on missing entries; refuses the default context. The optional `task-start` / `task-resume` shell functions are still documented in the script header for users who want one-command setup + exec claude — Make can't `cd` the parent shell, but a shell function can.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
scripts/task-clean.sh (1)
23-28: ⚡ Quick winUse explicit
iffor positional arg assignment (avoid&& ... || ...control flow).The current pattern on Line 27 is brittle Bash control flow and can trigger
usagein edge failure cases. Prefer an explicit branch.Proposed refactor
- *) [[ -z "$name" ]] && name="$arg" || usage ;; + *) + if [[ -z "$name" ]]; then + name="$arg" + else + usage + fi + ;;#!/usr/bin/env bash # Verify this SC2015-style pattern appears only where expected. rg -n '&&\s*[^;]+?\s*\|\|' scripts/task-clean.sh🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/task-clean.sh` around lines 23 - 28, The positional-arg assignment in the for-loop uses the brittle `[[ -z "$name" ]] && name="$arg" || usage` pattern; replace it with an explicit if/else branch so setting the variable `name` is atomic and `usage` is only called on the actual else case — edit the `for arg in "$@"` case arm (the `*)` branch) to use `if [[ -z "$name" ]]; then name="$arg"; else usage; fi`, keeping the `--force|-f` and unknown-flag handling unchanged and still calling the existing `usage` function when appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Makefile`:
- Around line 84-86: The Makefile's task-clean target currently treats any
non-empty FORCE as true and passes --force to ./scripts/task-clean.sh, which
lets values like FORCE=0 bypass safety; change the condition so --force is only
added when FORCE is explicitly "1" (i.e., test that $(FORCE) equals "1") when
invoking the script in the task-clean target to match documented behavior and
prevent unintended opt-in.
In `@scripts/task-clean.sh`:
- Around line 92-94: The script always appends --force to the git worktree
remove command (git -C "$repo" worktree remove "$worktree_dir" --force), which
defeats Git's safety checks; change the invocation to conditionally include
--force only when a cleanup-forced flag/variable is set (e.g., a parsed
FORCE_CLEANUP or --force option) so that by default it calls git -C "$repo"
worktree remove "$worktree_dir" and only uses --force when the explicit force
variable is true; update any option parsing to set that variable and reference
worktree_dir and repo when building the final command.
---
Nitpick comments:
In `@scripts/task-clean.sh`:
- Around line 23-28: The positional-arg assignment in the for-loop uses the
brittle `[[ -z "$name" ]] && name="$arg" || usage` pattern; replace it with an
explicit if/else branch so setting the variable `name` is atomic and `usage` is
only called on the actual else case — edit the `for arg in "$@"` case arm (the
`*)` branch) to use `if [[ -z "$name" ]]; then name="$arg"; else usage; fi`,
keeping the `--force|-f` and unknown-flag handling unchanged and still calling
the existing `usage` function when appropriate.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 253122cc-757b-4789-ac15-57313412986b
📒 Files selected for processing (8)
Makefilepackages/cli/src/commands/context.tspackages/cli/src/index.tspackages/cli/src/internal/__tests__/context.test.tspackages/cli/src/internal/context.tspackages/cli/src/internal/index.tsscripts/task-clean.shscripts/task-setup.sh
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/cli/src/commands/context.ts
- packages/cli/src/internal/tests/context.test.ts
- packages/cli/src/internal/context.ts
| echo "→ removing worktree $worktree_dir" | ||
| git -C "$repo" worktree remove "$worktree_dir" --force | ||
|
|
There was a problem hiding this comment.
git worktree remove should respect the --force flag.
Line 93 always passes --force, which defeats Git’s native protection even when the user did not request force cleanup. This can delete work unexpectedly if earlier custom checks miss a state.
Proposed fix
echo "→ removing worktree $worktree_dir"
-git -C "$repo" worktree remove "$worktree_dir" --force
+if [[ $force -eq 1 ]]; then
+ git -C "$repo" worktree remove "$worktree_dir" --force
+else
+ git -C "$repo" worktree remove "$worktree_dir"
+fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| echo "→ removing worktree $worktree_dir" | |
| git -C "$repo" worktree remove "$worktree_dir" --force | |
| echo "→ removing worktree $worktree_dir" | |
| if [[ $force -eq 1 ]]; then | |
| git -C "$repo" worktree remove "$worktree_dir" --force | |
| else | |
| git -C "$repo" worktree remove "$worktree_dir" | |
| fi |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/task-clean.sh` around lines 92 - 94, The script always appends
--force to the git worktree remove command (git -C "$repo" worktree remove
"$worktree_dir" --force), which defeats Git's safety checks; change the
invocation to conditionally include --force only when a cleanup-forced
flag/variable is set (e.g., a parsed FORCE_CLEANUP or --force option) so that by
default it calls git -C "$repo" worktree remove "$worktree_dir" and only uses
--force when the explicit force variable is true; update any option parsing to
set that variable and reference worktree_dir and repo when building the final
command.
Pi review of #891 surfaced 7 real issues (no cosmetic). All addressed: 1. .task untracked → task-clean always refuses without FORCE=1. Fix: add `.task` to repo .gitignore so worktrees inherit the rule. 2. Submodule branch was created from origin/main, immediately dirtying the worktree when the parent pin and owletto's origin/main differ (today's reality). Fix: branch from HEAD (the just-checked-out submodule pin) instead. 3. `make task-clean FORCE=0` was treated as truthy ([ -n ] on any non-empty string). Fix: explicit `[ "$(FORCE)" = "1" ]`. 4. task-clean trusted potentially-stale origin refs when checking "ahead of remote". Fix: `git fetch origin --prune` first in both the parent and submodule repos. 5. `task-setup NAME=lobu` would have overwritten the default Lobu CLI context (and `lobu context rm` refuses the default, so cleanup couldn't recover). Fix at two layers: - task-setup.sh reserves names matching the built-in contexts (lobu, dev, local) and refuses early. - addContext() in the CLI refuses to overwrite the default context — protects any caller, not just the script. 6. `lobu context add --port` silently accepted malformed values ("abc" → no port; "8788abc" → 8788; "70000" → out of TCP range but persisted). Fix: parser validates `^\d+$` and 1..65535 range. 7. Tests: cover addContext refusing the default name. 12/12 pass.
Pi review pass 1 — addressed (commit c716f24)All 7 findings fixed with verifiable e2e:
|
There was a problem hiding this comment.
♻️ Duplicate comments (1)
scripts/task-clean.sh (1)
100-101:⚠️ Potential issue | 🟠 Major | ⚡ Quick winHonor the
--forceflag when removing the worktree.Line 101 always forces removal, so non-force mode still bypasses Git’s built-in protection.
Proposed fix
echo "→ removing worktree $worktree_dir" -git -C "$repo" worktree remove "$worktree_dir" --force +if [[ $force -eq 1 ]]; then + git -C "$repo" worktree remove "$worktree_dir" --force +else + git -C "$repo" worktree remove "$worktree_dir" +fi🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/task-clean.sh` around lines 100 - 101, The git worktree removal currently always passes --force (the line git -C "$repo" worktree remove "$worktree_dir" --force), which ignores the intended non-force behavior; change this so --force is only appended when the script's force flag/variable (e.g., FORCE or force_flag) is true/Set, otherwise call git -C "$repo" worktree remove "$worktree_dir" without --force; implement this conditional append near the existing worktree removal call so the removal honors the passed flag.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@scripts/task-clean.sh`:
- Around line 100-101: The git worktree removal currently always passes --force
(the line git -C "$repo" worktree remove "$worktree_dir" --force), which ignores
the intended non-force behavior; change this so --force is only appended when
the script's force flag/variable (e.g., FORCE or force_flag) is true/Set,
otherwise call git -C "$repo" worktree remove "$worktree_dir" without --force;
implement this conditional append near the existing worktree removal call so the
removal honors the passed flag.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: a57e75f0-804b-4a8c-82e7-1a1f34fce639
📒 Files selected for processing (7)
.gitignoreMakefilepackages/cli/src/index.tspackages/cli/src/internal/__tests__/context.test.tspackages/cli/src/internal/context.tsscripts/task-clean.shscripts/task-setup.sh
✅ Files skipped from review due to trivial changes (1)
- .gitignore
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/cli/src/internal/context.ts
- Makefile
- packages/cli/src/index.ts
- scripts/task-setup.sh
Lets external tools that hardcode a path (Chrome's "Load unpacked" /
Xcode opening the Mac app) follow whichever worktree is currently
active, without manually re-pointing them.
- `scripts/task-use.sh <name|main>` retargets two fixed symlinks:
~/.config/lobu-dev/active/chrome → <root>/packages/owletto/apps/chrome
~/.config/lobu-dev/active/mac → <root>/packages/owletto/apps/mac
Chrome's Load-unpacked / Xcode's Open are pointed at the symlinks
permanently; `task-use` swaps which worktree they resolve to.
- `task-setup` auto-activates each new worktree it creates.
- `task-clean` of the *active* worktree falls back to `main` so the
symlinks never dangle into a deleted directory.
- Records the active name at ~/.config/lobu-dev/active/.active-name.
- New Makefile target: `make task-use NAME=<name|main>`.
Scoped to two specific consumer dirs intentionally — no
~/.config/lobu/worktree-targets.json registry yet. When a third
consumer appears, revisit the registry design.
Verified end-to-end: setup auto-activates, swap to main, swap back,
clean-of-active falls back to main with no dangling links.
Pi second-pass review noted that Chrome's MV3 extension does not auto- reload when the "Load unpacked" symlink retargets — the user must click Reload in chrome://extensions. Without the reminder it looks like the worktree swap didn't take effect. No code path change; only a multi-line note appended to task-use.sh's success output. Pi verdict (verbatim summary): - Option B (symlink swap, no registry) is sound for this stage. - Registry would be overengineering at 2 hardcoded entries; revisit when a third real consumer appears. - LobuServerConfig.cwd is the right layer for the dev server (no symlink needed); caveat: running servers must be restarted after cwd/config change. - Sharper-name suggestion (e.g. owletto-chrome-ext over bare chrome) is nice-to-have — deferred.
Summary
Adds a team-friendly, Makefile-driven workflow for paired-branch worktrees that don't lose changes inside the
packages/owlettosubmodule, plus the Lobu CLI context plumbing the Mac menubar will need to surface each worktree as a managed server.make task-setup NAME=<name>— creates the worktree at.claude/worktrees/<name>(lobu + submodule on realfeat/<name>branches), copies.env, picks free ports, writes.env.local, drops a.taskmarker, and registers the worktree as a Lobu CLI context withlifecycle: "managed".make task-clean NAME=<name> [FORCE=1]— removes the worktree, both branches, and the Lobu context. Refuses by default when there's uncommitted work or unpushed commits;FORCE=1overrides.lobu context addnow accepts--port,--host,--database-url,--data-dir,--cwd,--lifecycle.lobu context rm <name>(new) — idempotent, refuses the default context.LobuServerConfig.cwd(new field) — "directory the lifecycle owner cd's into before spawninglobu run". No reader yet; menubar update is a follow-up in owletto.Optional shell-function sugar (
task-start/task-resume) documented inscripts/task-setup.shheader — only needed if you want one command that alsocd's andexec claude's for you (Make can't move the parent shell).What this fixes
The four submodule footguns that have been costing us work:
git submodule updatechecks out a SHA, not a branch; commits there get orphaned. Script forces a realfeat/<name>branch in the submodule before any edits.bun installbeforegit submodule update --initdrops the locked subdir. Script orders correctly..env—.envis gitignored, sogit worktree addleaves a worktree that can't boot. Script copies from the main repo.make devfights for:8787/:8118. Script scans existing.env.localfiles and picks the next free pair.Reproducer (red → green)
Red — today, without the script (documented in AGENTS.md:21–22):
git submodule update --init packages/owlettolands the submodule on detached HEAD. Edits + commits there are reachable only via reflog. Nextgit submodule updateor parentgit switchorphans them.Green — with this PR:
Cleanup path verified:
lobu context rmverified end-to-end:Also verified:
bun run typecheckclean.bun test packages/cli/src/internal/__tests__/context.test.ts— 11/11 pass (5 new: addContext server config, addContext bare, removeContext + idempotent + refuses-default).Follow-ups (separate PRs)
LobuServerConfig.cwdandcdthere before spawninglobu run. Until then, context registration is benign —lobu chat -c <name>already routes to the right port viaapiUrl.~/Code/owletto: once the wrapper is in active use, remove the standalone clone and the matching AGENTS.md guidance.Test plan
bun run typecheckbun test packages/cli/src/internal/__tests__/context.test.ts(11/11)make task-setup) — submodule branch non-detached, .env present, ports assigned, marker file writtenmake task-clean— bails on uncommitted work, completes withFORCE=1, removes worktree + branchlobu context add/rmend-to-end with new flagsmake devon the assigned port without colliding with:8787) — manual, requires running servicesSummary by CodeRabbit
New Features
context addaccepts and persists optional server settings (port, host, database-url, data-dir, cwd, lifecycle).context rm <name>added to remove saved contexts idempotently.Chores
Tests