Skip to content
Open
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
69 changes: 58 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,27 @@ The main entry point is:

The plugin deliberately keeps Claude as the implementation orchestrator and uses fresh, read-only Codex executions for independent planning and review. It does not push, merge, deploy, rotate credentials, alter remote infrastructure, or apply irreversible production migrations.

### Three entry-point workflows

| Slash command | Worktree | Branch | When to use |
|---|---|---|---|
| `/autonomous-development:autonomous-feature` | Isolated `.claude/worktrees/*` (default safe) | New worktree branch | Default. Edits land in a disposable worktree; your checkout is untouched. |
| `/autonomous-development:autonomous-current` | Current checkout | Your already-created non-`main`/`master` feature branch | You created and switched to a clean feature branch yourself and want changes to land there. |
| `/autonomous-development:autonomous-main` | Current checkout | `main` / `master` (explicit opt-in via `--allow-main`) | You explicitly want direct edits on `main`/`master`. Still requires a clean tree. |

Both current-checkout workflows are opt-in. They do not create `.claude/worktrees/*`, do not enter a worktree, and never commit — the user reviews with normal `git diff` and commits manually. They require a clean working tree (`git status --porcelain` must be empty): modified, staged, deleted, and untracked files all make it unclean. They also require an attached named branch; detached HEAD is unsupported. `--allow-main` is valid only with `--worktree-mode current`; `autonomous-current` refuses `main`/`master`, while `autonomous-main` passes `--allow-main` to bypass that guard.

## Upstream

This plugin is based on [quaat/autonomous-development](https://github.com/quaat/autonomous-development). This fork keeps the original workflow and adds the current-checkout mode and related documentation updates.

## Included skills

| Skill | Purpose |
|---|---|
| `/autonomous-development:autonomous-feature` | Run the complete workflow from a high-level idea |
| `/autonomous-development:autonomous-feature` | Safe default: run the complete workflow inside a disposable worktree |
| `/autonomous-development:autonomous-current` | Run the complete workflow directly on the user's already-created feature branch (current checkout, never commits) |
| `/autonomous-development:autonomous-main` | Run the complete workflow directly on `main`/`master` (explicit opt-in via `--allow-main`, never commits) |
| `/autonomous-development:enhance-idea` | Ask Codex to turn a rough feature idea into a structured proposal |
| `/autonomous-development:implementation-plan` | Ask a fresh Codex execution for a repository-grounded plan |
| `/autonomous-development:implement-plan` | Implement the accepted plan with Claude |
Expand Down Expand Up @@ -286,20 +302,51 @@ Archiving is a metadata flag; no files are deleted.
- Do not share state directories across users or store them on world-readable paths.
- Remote URLs are stored with credentials stripped (the `user:pass@` portion is removed).

## Worktree support
## Worktree modes

The controller supports two explicit execution modes:

```bash
# Default safe workflow: isolated worktree
# Claude Code's autonomous-feature skill enters a disposable worktree before init.
controller.py init --feature "Experiment feature" --mode auto --worktree-mode isolated
```

All commands work correctly from any linked worktree. The repository identity is derived from
the shared git object store so runs created in different worktrees belong to the same
repository and are visible to `list-runs`.
```bash
# Current-branch workflow: use the branch you created manually
git checkout -b experiment
controller.py init --feature "Experiment feature" --mode auto --worktree-mode current
# Review the result with normal `git diff` in your checkout.
```

```bash
# Create a linked worktree and run the workflow there
git worktree add ../experiment feature-branch
cd ../experiment
controller.py init --feature "Experiment feature"
# State stored under the same repository ID, new run ID
# Current-checkout on main/master (explicit opt-in):
controller.py init --feature "Hotfix" --mode standard --worktree-mode current --allow-main
```

Two slash commands wrap these invocations so the user does not have to call `controller.py`
directly:

```text
/autonomous-development:autonomous-current "Experiment feature" # already on a feature branch
/autonomous-development:autonomous-main "Hotfix" # explicit edits on main/master
```

Both commands run `controller.py init --mode standard --worktree-mode current`; the `main`
variant additionally passes `--allow-main`. Neither creates `.claude/worktrees/*`, neither
enters a worktree, and neither commits. The agent's changes land directly in your checkout so
normal `git diff`, local testing, and manual commit flow continue to work.

All commands still work correctly from any linked worktree. The repository identity is derived
from the shared git object store so runs created in different worktrees belong to the same
repository and are visible to `list-runs`. Current-checkout mode is opt-in for people who create
their own feature branches first and want the agent's changes to land directly in that checkout.
In current mode the controller uses the current project root for both
`repository.canonical_root` and `repository.worktree_path`, records the current branch in
baseline metadata, does not create a `.claude/worktrees/*` worktree or `worktree-*` branch,
refuses detached HEAD and `main`/`master` unless you pass `--allow-main`, and refuses a
dirty tree containing modified, staged, deleted, or untracked files.

## Completion rules

A run succeeds only when:
Expand All @@ -323,7 +370,7 @@ The skill uses Codex with `--sandbox read-only`. Claude performs repository edit
- deleting unrelated user changes;
- weakening tests or security controls to obtain a passing result.

Run autonomous development in a disposable branch or worktree. The orchestrator asks Claude Code to use an isolated worktree whenever supported.
By default, run autonomous development in an isolated worktree. This fork also supports an explicit current-checkout mode when you want changes to land directly in your own feature branch.

## Customization

Expand Down
21 changes: 21 additions & 0 deletions examples/invocations.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
# Example invocations

```text
# Safe default: edits land in a disposable isolated worktree.
/autonomous-development:autonomous-feature "Add resumable multipart uploads with checksum validation and backward-compatible API behavior"
```

```text
# Direct edits on an already-created feature branch.
# Refuses main/master and a dirty tree. Never commits — review with `git diff`.
git checkout -b feature/uploads
/autonomous-development:autonomous-current "Add resumable multipart uploads"
```

```text
# Explicit opt-in: direct edits on main/master.
# Still requires a clean tree. Never commits — review with `git diff`.
/autonomous-development:autonomous-main "Patch a security hotfix in place"
```

```text
/autonomous-development:enhance-idea "Add organization-level API usage limits"
/autonomous-development:implementation-plan "Prioritize backward compatibility and migration safety"
Expand Down Expand Up @@ -31,6 +45,13 @@ controller.py init --feature "Rename a button label" --mode auto

# Force the full workflow with mandatory adversarial review.
controller.py init --feature "Add tenant-scoped billing" --mode rigorous

# Use your manually created feature branch instead of a disposable worktree.
git switch -c experiment
controller.py init --feature "Experiment feature" --mode auto --worktree-mode current

# Same mode, but explicitly authorized to land on main/master.
controller.py init --feature "Hotfix" --mode standard --worktree-mode current --allow-main
```

Drive phases and inspect token usage:
Expand Down
107 changes: 82 additions & 25 deletions scripts/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
_DEFAULT_PROFILE = {"reasoning": "high", "verbosity": "low", "reasoning_summary": "none"}

WORKFLOW_MODES = ("auto", "lean", "standard", "rigorous")
WORKTREE_MODES = ("isolated", "current")

# Conservative risk categories used by `--mode auto` escalation. Matching any
# category escalates an `auto` run to rigorous.
Expand Down Expand Up @@ -514,6 +515,32 @@ def select_mode(requested: str, feature: str) -> tuple[str, list[str]]:
return requested, [f"explicit mode requested: {requested}"]


def worktree_mode_label(worktree_mode: object) -> str:
"""Human-readable label for the init worktree mode."""
if worktree_mode == "current":
return "current checkout"
if worktree_mode == "isolated":
return "isolated worktree"
if isinstance(worktree_mode, str) and worktree_mode.strip():
return worktree_mode.strip()
return "(unknown)"


def repository_state_block(repo: RepoInfo, *, worktree_mode: str | None = None) -> dict[str, str]:
"""Return the repository block written into run-state.json."""
block = {
"id": repo.id,
"canonical_root": str(repo.canonical_root),
"git_common_dir": str(repo.git_common_dir),
"worktree_path": str(repo.worktree_path),
"display_name": repo.display_name,
"remote_display": repo.remote_display,
}
if worktree_mode:
block["worktree_mode"] = worktree_mode
return block


# ---------------------------------------------------------------------------
# Compact prompt context helpers
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -1306,6 +1333,12 @@ def cmd_init(args: argparse.Namespace) -> int:
if not feature:
raise WorkflowError("Feature idea must not be empty")

worktree_mode = getattr(args, "worktree_mode", "isolated")
if getattr(args, "allow_main", False) and worktree_mode != "current":
raise WorkflowError(
"--allow-main is only valid with --worktree-mode current."
)

label = ""
if getattr(args, "label", None):
raw_label = args.label.strip()
Expand Down Expand Up @@ -1362,17 +1395,37 @@ def cmd_init(args: argparse.Namespace) -> int:
)
run_dir.mkdir(parents=True, exist_ok=True, mode=0o700)

ctx_text = repository_context(repo)
(run_dir / "feature-request.md").write_text(
feature + "\n", encoding="utf-8"
)
(run_dir / "repository-context.txt").write_text(
ctx_text, encoding="utf-8"
)

dirty = git(
repo.canonical_root, "status", "--short", check=False
repo.canonical_root, "status", "--porcelain", check=False
).splitlines()
if worktree_mode == "current":
if not repo.branch:
raise WorkflowError(
"Current-checkout mode does not support detached HEAD. "
"Check out a named branch before initializing."
)
if repo.branch in {"main", "master"} and not getattr(args, "allow_main", False):
Comment thread
sigmundas marked this conversation as resolved.
Comment thread
sigmundas marked this conversation as resolved.
raise WorkflowError(
"Current-checkout mode refuses to initialize on "
f"branch {repo.branch!r}. Create and check out a feature "
"branch first, or pass --allow-main to override this guard "
"on main/master."
)
if dirty:
preview = ", ".join(dirty[:5])
suffix = "" if len(dirty) <= 5 else f" (+{len(dirty) - 5} more)"
raise WorkflowError(
"Current-checkout mode requires a clean working tree. "
"Modified, staged, deleted, and untracked files all make "
"the working tree unclean. "
f"Dirty entries: {preview}{suffix}"
)

ctx_text = repository_context(repo) + (
f"\nWorktree mode: {worktree_mode_label(worktree_mode)}\n"
)
(run_dir / "feature-request.md").write_text(feature + "\n", encoding="utf-8")
(run_dir / "repository-context.txt").write_text(ctx_text, encoding="utf-8")

requested_mode = getattr(args, "mode", "auto")
effective_mode, mode_reasons = select_mode(requested_mode, feature)
Expand All @@ -1393,14 +1446,7 @@ def cmd_init(args: argparse.Namespace) -> int:
"phase": "initialized",
"created_at": utc_now(),
"updated_at": utc_now(),
"repository": {
"id": repo.id,
"canonical_root": str(repo.canonical_root),
"git_common_dir": str(repo.git_common_dir),
"worktree_path": str(repo.worktree_path),
"display_name": repo.display_name,
"remote_display": repo.remote_display,
},
"repository": repository_state_block(repo, worktree_mode=worktree_mode),
"baseline": {
"commit": repo.head_commit,
"branch": repo.branch,
Expand Down Expand Up @@ -2635,6 +2681,10 @@ def cmd_status(args: argparse.Namespace) -> int:
print(f"Status: {state.get('status')}")
print(f"Phase: {state.get('phase')}")
print(f"Feature: {state.get('feature')}")
repo_block = state.get("repository", {})
worktree_mode = repo_block.get("worktree_mode") if isinstance(repo_block, dict) else None
if worktree_mode:
print(f"Worktree mode: {worktree_mode_label(worktree_mode)}")
print(f"Baseline: {state.get('baseline', {}).get('commit')}")
print(f"Verification: {passed}/{len(checks)} passing")
print(
Expand Down Expand Up @@ -2959,15 +3009,11 @@ def cmd_accept_drift(args: argparse.Namespace) -> int:
require_active_run_state(state, run_ref.run_id, "accept-drift")
old_baseline = dict(state.get("baseline", {}))
old_repo_block = dict(state.get("repository", {}))
old_worktree_mode = old_repo_block.get("worktree_mode")

state["repository"] = {
"id": repo.id,
"canonical_root": str(repo.canonical_root),
"git_common_dir": str(repo.git_common_dir),
"worktree_path": str(repo.worktree_path),
"display_name": repo.display_name,
"remote_display": repo.remote_display,
}
state["repository"] = repository_state_block(
repo, worktree_mode=old_worktree_mode if isinstance(old_worktree_mode, str) else None
)
state["baseline"] = {
"commit": repo.head_commit,
"branch": repo.branch,
Expand Down Expand Up @@ -3240,6 +3286,17 @@ def build_parser() -> argparse.ArgumentParser:
default="auto",
help="Workflow rigor mode; auto escalates conservatively by risk",
)
init.add_argument(
"--worktree-mode",
choices=WORKTREE_MODES,
default="isolated",
help="Repository execution mode; isolated stays in a disposable worktree, current uses the current checkout",
)
init.add_argument(
"--allow-main",
action="store_true",
help="Allow current-checkout mode on main/master (still requires a clean tree)",
)
init.add_argument("--max-review-rounds", type=int, default=3, choices=range(1, 6))
init.add_argument("--reuse", action="store_true")
init.add_argument("--force", action="store_true")
Expand Down
1 change: 1 addition & 0 deletions scripts/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,7 @@ def migrate_v1_to_v2(
"id": repo.id,
"display_name": repo.display_name,
"canonical_root": str(repo.canonical_root),
"worktree_mode": "isolated",
"remote_display": repo.remote_display,
}

Expand Down
Loading