Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ When the user pivots mid-session, the default failure mode is piling unrelated w
3. `git switch main && git pull && git switch -c feat/<new-thing>` before touching any new code.
- **When the new ask genuinely builds on unmerged code**, stack it: `git switch -c feat/b feat/a` off the existing feature branch and open PR #2 targeting `feat/a` (not `main`). Rebase PR #2 onto `main` once PR #1 merges.
- **Never `git stash`.** Stashes are invisible, easy to lose, and collide across agents. If you need to pivot without finishing, commit WIP to the current branch (`git add -A && git commit -m "wip"`) and squash later. WIP commits are visible, pushable, recoverable.
- **`~/Code/lobu` is read-only for agents.** All writes — commits, branch creation, submodule bumps, even one-line build fixes — go through a `make task-setup NAME=<slug>` worktree. For the trivial "advance a submodule pointer" case, `make bump SUBMODULE=<path> [TARGET=<ref>]` is the lightweight shortcut (skips bun install, .env copy, port allocation). The main checkout staying on `main` is the invariant that lets other agents `git worktree add` cleanly — leaving it on `chore/some-fix` silently breaks every parallel agent's `task-setup`.
- **Per-agent isolation:** when launching a parallel Claude Code session, use `claude --worktree <name>` so each agent gets its own checkout + branch. No shared working dir = no cross-agent collisions.
- **Subagent isolation (mandatory):** any spawned subagent that may `git switch`, commit, push, or run a destructive command MUST run with `isolation: "worktree"`. Read-only research/exploration agents may share the parent checkout. If unsure, use a worktree — the cost is a temp checkout, the cost of skipping is overwriting the user's working tree.
- **Cross-repo dispatch:** owletto changes go through a `make task-setup NAME=<slug>` worktree, which fetches a fresh owletto checkout under `.claude/worktrees/<slug>/packages/owletto` on a real branch (not a detached submodule SHA). The submodule worktree inherits the parent's `.git` and pushes to the wrong remote; an isolation worktree of lobu that needs to edit owletto code ends up with `origin = lobu-ai/owletto` and can't push to lobu. After an owletto PR merges, bump the submodule pointer in lobu in a separate small PR.
Expand Down
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Development Makefile for Lobu

.PHONY: help setup build test clean dev build-packages ensure-submodule clean-workers test-unit test-integration test-e2e typecheck task-setup task-clean task-use
.PHONY: help setup build test clean dev build-packages ensure-submodule clean-workers test-unit test-integration test-e2e typecheck task-setup task-clean task-use bump

# Default target
help:
Expand All @@ -17,6 +17,7 @@ help:
@echo " make task-setup NAME=<name> - Create a paired worktree at .claude/worktrees/<name> (lobu + submodule on real branch, .env copied, ports auto-assigned, Lobu context registered)"
@echo " make task-clean NAME=<name> [FORCE=1] - Remove the worktree, both branches, and the Lobu context (refuses if there's uncommitted/unpushed work unless FORCE=1)"
@echo " make task-use NAME=<name|main> - Point Chrome ext / Mac app symlinks at this worktree (or 'main' for the canonical checkout)"
@echo " make bump SUBMODULE=<path> [TARGET=<ref>] - Lightweight worktree + commit + PR for a trivial submodule pointer bump (skips bun install, .env, ports)"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document the optional NAME parameter.

The help text omits [NAME=<slug>], but line 94 shows the target accepts it and the script uses it to customize the branch slug. Users reading make help won't discover this option.

📝 Proposed fix
-	`@echo` "  make bump SUBMODULE=<path> [TARGET=<ref>]  - Lightweight worktree + commit + PR for a trivial submodule pointer bump (skips bun install, .env, ports)"
+	`@echo` "  make bump SUBMODULE=<path> [TARGET=<ref>] [NAME=<slug>]  - Lightweight worktree + commit + PR for a trivial submodule pointer bump (skips bun install, .env, ports)"
📝 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.

Suggested change
@echo " make bump SUBMODULE=<path> [TARGET=<ref>] - Lightweight worktree + commit + PR for a trivial submodule pointer bump (skips bun install, .env, ports)"
`@echo` " make bump SUBMODULE=<path> [TARGET=<ref>] [NAME=<slug>] - Lightweight worktree + commit + PR for a trivial submodule pointer bump (skips bun install, .env, ports)"
🤖 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 `@Makefile` at line 20, Update the Makefile help string for the bump target to
document the optional NAME parameter: modify the help echo that currently shows
"make bump SUBMODULE=<path> [TARGET=<ref>]" so it includes "[NAME=<slug>]" (e.g.
"make bump SUBMODULE=<path> [TARGET=<ref>] [NAME=<slug>]") to match how the bump
target uses NAME to customize the branch slug; ensure the updated help text
appears in the same echo statement that prints the bump usage.


# Strict typecheck — mirrors the Dockerfile so local matches CI. Catches
# what `build-packages` (relaxed, bundler-only) misses.
Expand Down Expand Up @@ -85,6 +86,14 @@ task-use:
@: $${NAME?Usage: make task-use NAME=<name|main>}
@./scripts/task-use.sh "$(NAME)"

# Lightweight shortcut for "trivial submodule pointer bump" work. Creates a
# minimal worktree (no bun install, no .env copy, no port allocation), advances
# the submodule, opens an auto-merge PR. For agent work that also touches
# submodule *code*, use `make task-setup` instead — it sets up the full env.
bump:
@: $${SUBMODULE?Usage: make bump SUBMODULE=<path> [TARGET=<sha-or-ref>] [NAME=<slug>]}
@NAME="$(NAME)" ./scripts/bump-submodule.sh "$(SUBMODULE)" "$(TARGET)"

# --- Test pipelines ---------------------------------------------------------
# These mirror what CI runs (.github/workflows/ci.yml) so a passing local run
# is a strong signal CI will pass.
Expand Down
127 changes: 127 additions & 0 deletions scripts/bump-submodule.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#!/usr/bin/env bash
# bump-submodule.sh — open a one-line submodule pointer PR via a lightweight worktree.
#
# Usage:
# make bump SUBMODULE=packages/owletto # bump to origin/main
# make bump SUBMODULE=packages/owletto TARGET=abc123def # bump to specific SHA / ref
# make bump SUBMODULE=packages/owletto NAME=cancel-button # custom slug
#
# This is the cheap shortcut for the trivial "bump a submodule pointer" case.
# For agent work that also touches submodule code, use `make task-setup` instead,
# which sets up the full dev environment (ports, .env, bun install, etc).
#
# What this script DOES NOT do that task-setup does:
# - bun install (no dev server runs here)
# - .env copy (no secrets needed for a pointer commit)
# - port allocation (no listener)
# - Lobu CLI context (nothing to talk to)
#
# It just creates a worktree off origin/main, advances the submodule pointer,
# commits, pushes, and opens an auto-merge PR.
#
# Why this exists: the convention is `~/Code/lobu` is read-only for agents
# (see AGENTS.md "Scope discipline"). Without this shortcut, agents reach
# for the main checkout to do "trivial" bumps because task-setup feels
# heavyweight for a one-line change. This makes worktree the easy path.
set -euo pipefail

SUBMODULE=${1:?Usage: bump-submodule.sh <submodule-path> [target-sha-or-ref]}
TARGET=${2:-origin/main}

REPO_ROOT="$(git rev-parse --show-toplevel)"
cd "$REPO_ROOT"

# Verify SUBMODULE is actually a configured submodule
if ! grep -qE "^\s*path\s*=\s*${SUBMODULE//\//\\/}$" .gitmodules 2>/dev/null; then
echo "error: '$SUBMODULE' is not a configured submodule (no matching path= entry in .gitmodules)" >&2
echo "available submodules:" >&2
grep -E '^\s*path\s*=' .gitmodules | awk -F= '{print " " $2}' >&2
exit 1
fi

NAME=${NAME:-"$(basename "$SUBMODULE")-$(date +%Y%m%d-%H%M%S)"}
SLUG="bump-${NAME}"
BRANCH="chore/${SLUG}"
WT="$REPO_ROOT/.claude/worktrees/$SLUG"

if [[ -e "$WT" ]]; then
echo "error: worktree $WT already exists; pick a different NAME or run: make task-clean NAME=$SLUG FORCE=1" >&2
exit 1
fi

echo "→ fetching latest main"
git fetch origin main --quiet

echo "→ creating worktree at $WT on branch $BRANCH"
git worktree add "$WT" -b "$BRANCH" origin/main >/dev/null
# Drop the same .task marker task-setup uses so `git worktree list` can tell
# this apart from agent-* isolation worktrees, and so task-clean knows where to look.
touch "$WT/.task"

echo "→ initializing $SUBMODULE in the new worktree"
git -C "$WT" submodule update --init -- "$SUBMODULE" >/dev/null

echo "→ resolving $TARGET in $SUBMODULE"
git -C "$WT/$SUBMODULE" fetch origin --quiet
# Cleanup helper: remove the worktree AND its branch ref so a re-run with the
# same NAME doesn't trip the "worktree already exists" or "branch already exists"
# guard. `git worktree remove` only removes the tree, not the branch.
cleanup_worktree() {
git worktree remove "$WT" --force 2>/dev/null || true
git branch -D "$BRANCH" 2>/dev/null || true
}

if ! TARGET_SHA=$(git -C "$WT/$SUBMODULE" rev-parse "$TARGET" 2>/dev/null); then
echo "error: can't resolve $TARGET inside $SUBMODULE" >&2
cleanup_worktree
exit 1
fi
BEFORE_SHA=$(git -C "$WT/$SUBMODULE" rev-parse HEAD)
if [[ "$BEFORE_SHA" == "$TARGET_SHA" ]]; then
echo "→ $SUBMODULE is already at $TARGET ($BEFORE_SHA); nothing to bump"
cleanup_worktree
exit 0
fi

SHORT_BEFORE=$(git -C "$WT/$SUBMODULE" rev-parse --short "$BEFORE_SHA")
SHORT_AFTER=$(git -C "$WT/$SUBMODULE" rev-parse --short "$TARGET_SHA")
TARGET_SUBJECT=$(git -C "$WT/$SUBMODULE" log -1 --format='%s' "$TARGET_SHA")

echo "→ advancing $SUBMODULE: $SHORT_BEFORE → $SHORT_AFTER"
git -C "$WT/$SUBMODULE" checkout --detach "$TARGET_SHA" >/dev/null 2>&1

echo "→ committing pointer bump"
git -C "$WT" add "$SUBMODULE"
git -C "$WT" commit -m "chore: bump $SUBMODULE pointer to $SHORT_AFTER

Picks up: $TARGET_SUBJECT

Before: $SHORT_BEFORE
After: $SHORT_AFTER" >/dev/null

echo "→ pushing $BRANCH"
git -C "$WT" push -u origin "$BRANCH" --quiet

if command -v gh >/dev/null 2>&1; then
echo "→ opening PR"
PR_URL=$(gh pr create --base main --head "$BRANCH" \
--title "chore: bump $SUBMODULE pointer to $SHORT_AFTER" \
--body "Bumps \`$SUBMODULE\` pointer.

\`\`\`
Before: $SHORT_BEFORE
After: $SHORT_AFTER
\`\`\`

Picks up: $TARGET_SUBJECT" 2>&1 | tail -1)
echo " $PR_URL"
echo "→ enabling auto-merge (squash)"
gh pr merge "$PR_URL" --auto --squash >/dev/null 2>&1 || \
echo " (auto-merge enable failed — admin-merge with: gh pr merge $PR_URL --squash --admin)"
else
echo "→ gh CLI not available; open the PR manually for branch $BRANCH"
fi

echo
echo "✓ done. After the PR merges, clean up:"
echo " make task-clean NAME=$SLUG FORCE=1"
Loading