feat: add worktree support and refactor do skill to Python

- Add worktree module for git worktree management
- Refactor do skill scripts from shell to Python for better maintainability
- Add install.py for do skill installation
- Update stop-hook to Python implementation
- Enhance executor with additional configuration options
- Update CLAUDE.md with first-principles thinking guidelines

Generated with SWE-Agent.ai

Co-Authored-By: SWE-Agent.ai <noreply@swe-agent.ai>
This commit is contained in:
cexll
2026-02-03 21:58:08 +08:00
parent 04fa1626ae
commit 74e4d181c2
19 changed files with 1257 additions and 433 deletions

View File

@@ -1,6 +1,6 @@
# do - Feature Development Orchestrator
7-phase feature development workflow orchestrating multiple agents via codeagent-wrapper.
5-phase feature development workflow orchestrating multiple agents via codeagent-wrapper.
## Installation
@@ -24,17 +24,15 @@ Examples:
/do implement order export to CSV
```
## 7-Phase Workflow
## 5-Phase Workflow
| Phase | Name | Goal | Key Actions |
|-------|------|------|-------------|
| 1 | Discovery | Understand requirements | AskUserQuestion + code-architect draft |
| 2 | Exploration | Map codebase patterns | 2-3 parallel code-explorer tasks |
| 3 | Clarification | Resolve ambiguities | **MANDATORY** - must answer before proceeding |
| 4 | Architecture | Design implementation | 2 parallel code-architect approaches |
| 5 | Implementation | Build the feature | **Requires approval** - develop agent |
| 6 | Review | Catch defects | 2-3 parallel code-reviewer tasks |
| 7 | Summary | Document results | code-reviewer summary |
| 1 | Understand | Gather requirements | AskUserQuestion + code-explorer analysis |
| 2 | Clarify | Resolve ambiguities | **MANDATORY** - must answer before proceeding |
| 3 | Design | Plan implementation | code-architect approaches |
| 4 | Implement | Build the feature | **Requires approval** - develop agent |
| 5 | Complete | Finalize and document | code-reviewer summary |
## Agents
@@ -50,8 +48,8 @@ To customize agents, create same-named files in `~/.codeagent/agents/` to overri
## Hard Constraints
1. **Never write code directly** - delegate all changes to codeagent-wrapper agents
2. **Phase 3 is mandatory** - do not proceed until questions are answered
3. **Phase 5 requires approval** - stop after Phase 4 if not approved
2. **Phase 2 is mandatory** - do not proceed until questions are answered
3. **Phase 4 requires approval** - stop after Phase 3 if not approved
4. **Pass complete context forward** - every agent gets the Context Pack
5. **Parallel-first** - run independent tasks via `codeagent-wrapper --parallel`
6. **Update state after each phase** - keep `.claude/do.{task_id}.local.md` current
@@ -63,7 +61,7 @@ To customize agents, create same-named files in `~/.codeagent/agents/` to overri
<verbatim request>
## Context Pack
- Phase: <1-7 name>
- Phase: <1-5 name>
- Decisions: <requirements/constraints/choices>
- Code-explorer output: <paste or "None">
- Code-architect output: <paste or "None">
@@ -83,7 +81,7 @@ To customize agents, create same-named files in `~/.codeagent/agents/` to overri
When triggered via `/do <task>`, initializes `.claude/do.{task_id}.local.md` with:
- `active: true`
- `current_phase: 1`
- `max_phases: 7`
- `max_phases: 5`
- `completion_promise: "<promise>DO_COMPLETE</promise>"`
After each phase, update frontmatter:
@@ -92,7 +90,7 @@ current_phase: <next phase number>
phase_name: "<next phase name>"
```
When all 7 phases complete, output:
When all 5 phases complete, output:
```
<promise>DO_COMPLETE</promise>
```
@@ -184,3 +182,29 @@ Required when using `agent:` in parallel tasks or `--agent`. Create `~/.codeagen
```bash
python install.py --uninstall --module do
```
## Worktree Mode
Use `--worktree` to execute tasks in an isolated git worktree, preventing changes to your main branch:
```bash
codeagent-wrapper --worktree --agent develop "implement feature X" .
```
This automatically:
1. Generates a unique task ID (format: `YYYYMMDD-xxxxxx`)
2. Creates a new worktree at `.worktrees/do-{task_id}/`
3. Creates a new branch `do/{task_id}`
4. Executes the task in the isolated worktree
Output includes: `Using worktree: .worktrees/do-{task_id}/ (task_id: {id}, branch: do/{id})`
In parallel mode, add `worktree: true` to task blocks:
```
---TASK---
id: feature_impl
agent: develop
worktree: true
---CONTENT---
Implement the feature
```

View File

@@ -1,7 +1,7 @@
---
name: do
description: This skill should be used for structured feature development with codebase understanding. Triggers on /do command. Provides a 7-phase workflow (Discovery, Exploration, Clarification, Architecture, Implementation, Review, Summary) using codeagent-wrapper to orchestrate code-explorer, code-architect, code-reviewer, and develop agents in parallel.
allowed-tools: ["Bash(${SKILL_DIR}/scripts/setup-do.sh:*)"]
description: This skill should be used for structured feature development with codebase understanding. Triggers on /do command. Provides a 5-phase workflow (Understand, Clarify, Design, Implement, Complete) using codeagent-wrapper to orchestrate code-explorer, code-architect, code-reviewer, and develop agents in parallel.
allowed-tools: ["Bash(${SKILL_DIR}/scripts/setup-do.py:*)"]
---
# do - Feature Development Orchestrator
@@ -10,17 +10,57 @@ An orchestrator for systematic feature development. Invoke agents via `codeagent
## Loop Initialization (REQUIRED)
When triggered via `/do <task>`, **first** initialize the loop state:
When triggered via `/do <task>`, follow these steps:
### Step 1: Ask about worktree mode
Use AskUserQuestion to ask:
```
Develop in a separate worktree? (Isolates changes from main branch)
- Yes (Recommended for larger changes)
- No (Work directly in current directory)
```
### Step 2: Initialize state
```bash
"${SKILL_DIR}/scripts/setup-do.sh" "<task description>"
# If worktree mode selected:
python3 "${SKILL_DIR}/scripts/setup-do.py" --worktree "<task description>"
# If no worktree:
python3 "${SKILL_DIR}/scripts/setup-do.py" "<task description>"
```
This creates `.claude/do.{task_id}.local.md` with:
- `active: true`
- `current_phase: 1`
- `max_phases: 7`
- `max_phases: 5`
- `completion_promise: "<promise>DO_COMPLETE</promise>"`
- `use_worktree: true/false`
## Worktree Mode
When `use_worktree: true` in state file, ALL `codeagent-wrapper` calls that modify code MUST include `--worktree`:
```bash
# With worktree mode enabled
codeagent-wrapper --worktree --agent develop - . <<'EOF'
...
EOF
# Parallel tasks with worktree
codeagent-wrapper --worktree --parallel <<'EOF'
---TASK---
id: task1
agent: develop
workdir: .
---CONTENT---
...
EOF
```
The `--worktree` flag tells codeagent-wrapper to create/use a worktree internally. Read-only agents (code-explorer, code-architect, code-reviewer) do NOT need `--worktree`.
## Loop State Management
@@ -30,7 +70,7 @@ current_phase: <next phase number>
phase_name: "<next phase name>"
```
When all 7 phases complete, output the completion signal:
When all 5 phases complete, output the completion signal:
```
<promise>DO_COMPLETE</promise>
```
@@ -40,22 +80,35 @@ To abort early, set `active: false` in the state file.
## Hard Constraints
1. **Never write code directly.** Delegate all code changes to `codeagent-wrapper` agents.
2. **Phase 3 (Clarification) is mandatory.** Do not proceed until questions are answered.
3. **Phase 5 (Implementation) requires explicit approval.** Stop after Phase 4 if not approved.
4. **Pass complete context forward.** Every agent invocation includes the Context Pack.
5. **Parallel-first.** Run independent tasks via `codeagent-wrapper --parallel`.
6. **Update state after each phase.** Keep `.claude/do.{task_id}.local.md` current.
7. **Expect long-running `codeagent-wrapper` calls.** High-reasoning modes (e.g. `xhigh`) can take a long time; stay in the orchestrator role and wait for agents to complete.
8. **Timeouts are not an escape hatch.** If a `codeagent-wrapper` invocation times out/errors, retry `codeagent-wrapper` (split/narrow the task if needed); never switch to direct implementation.
2. **Pass complete context forward.** Every agent invocation includes the Context Pack.
3. **Parallel-first.** Run independent tasks via `codeagent-wrapper --parallel`.
4. **Update state after each phase.** Keep `.claude/do.{task_id}.local.md` current.
5. **Expect long-running `codeagent-wrapper` calls.** High-reasoning modes can take a long time; stay in the orchestrator role and wait for agents to complete.
6. **Timeouts are not an escape hatch.** If a `codeagent-wrapper` invocation times out/errors, retry (split/narrow the task if needed); never switch to direct implementation.
7. **Respect worktree setting.** If `use_worktree: true`, always pass `--worktree` to develop agent calls.
## Agents
| Agent | Purpose | Prompt |
|-------|---------|--------|
| `code-explorer` | Trace code, map architecture, find patterns | `agents/code-explorer.md` |
| `code-architect` | Design approaches, file plans, build sequences | `agents/code-architect.md` |
| `code-reviewer` | Review for bugs, simplicity, conventions | `agents/code-reviewer.md` |
| `develop` | Implement code, run tests | (uses global config) |
| Agent | Purpose | Needs --worktree |
|-------|---------|------------------|
| `code-explorer` | Trace code, map architecture, find patterns | No (read-only) |
| `code-architect` | Design approaches, file plans, build sequences | No (read-only) |
| `code-reviewer` | Review for bugs, simplicity, conventions | No (read-only) |
| `develop` | Implement code, run tests | **Yes** (if worktree enabled) |
## Issue Severity Definitions
**Blocking issues** (require user input):
- Impacts core functionality or correctness
- Security vulnerabilities
- Architectural conflicts with existing patterns
- Ambiguous requirements with multiple valid interpretations
**Minor issues** (auto-fix without asking):
- Code style inconsistencies
- Naming improvements
- Missing documentation
- Non-critical test coverage gaps
## Context Pack Template
@@ -64,7 +117,7 @@ To abort early, set `active: false` in the state file.
<verbatim request>
## Context Pack
- Phase: <1-7 name>
- Phase: <1-5 name>
- Decisions: <requirements/constraints/choices>
- Code-explorer output: <paste or "None">
- Code-architect output: <paste or "None">
@@ -79,18 +132,21 @@ To abort early, set `active: false` in the state file.
<checkable outputs>
```
## 7-Phase Workflow
## 5-Phase Workflow
### Phase 1: Discovery
### Phase 1: Understand (Parallel, No Interaction)
**Goal:** Understand what to build.
**Goal:** Understand requirements and map codebase simultaneously.
**Actions:**
1. Use AskUserQuestion for: user-visible behavior, scope, constraints, acceptance criteria
2. Invoke `code-architect` to draft requirements checklist and clarifying questions
**Actions:** Run `code-architect` and 2-3 `code-explorer` tasks in parallel.
```bash
codeagent-wrapper --agent code-architect - . <<'EOF'
codeagent-wrapper --parallel <<'EOF'
---TASK---
id: p1_requirements
agent: code-architect
workdir: .
---CONTENT---
## Original User Request
/do <request>
@@ -99,33 +155,29 @@ codeagent-wrapper --agent code-architect - . <<'EOF'
- Code-architect output: None
## Current Task
Produce requirements checklist and identify missing information.
Output: Requirements, Non-goals, Risks, Acceptance criteria, Questions (<= 10)
1. Analyze requirements completeness (score 1-10)
2. Extract explicit requirements, constraints, acceptance criteria
3. Identify blocking questions (issues that prevent implementation)
4. Identify minor clarifications (nice-to-have but can proceed without)
Output format:
- Completeness score: X/10
- Requirements: [list]
- Non-goals: [list]
- Blocking questions: [list, if any]
- Minor clarifications: [list, if any]
## Acceptance Criteria
Concrete, testable checklist; specific questions; no implementation.
EOF
```
Concrete checklist; blocking vs minor questions clearly separated.
### Phase 2: Exploration
**Goal:** Map codebase patterns and extension points.
**Actions:** Run 2-3 `code-explorer` tasks in parallel (similar features, architecture, tests/conventions).
```bash
codeagent-wrapper --parallel <<'EOF'
---TASK---
id: p2_similar_features
id: p1_similar_features
agent: code-explorer
workdir: .
---CONTENT---
## Original User Request
/do <request>
## Context Pack
- Code-architect output: <Phase 1 output>
## Current Task
Find 1-3 similar features, trace end-to-end. Return: key files with line numbers, call flow, extension points.
@@ -133,16 +185,13 @@ Find 1-3 similar features, trace end-to-end. Return: key files with line numbers
Concrete file:line map + reuse points.
---TASK---
id: p2_architecture
id: p1_architecture
agent: code-explorer
workdir: .
---CONTENT---
## Original User Request
/do <request>
## Context Pack
- Code-architect output: <Phase 1 output>
## Current Task
Map architecture for relevant subsystem. Return: module map + 5-10 key files.
@@ -150,16 +199,13 @@ Map architecture for relevant subsystem. Return: module map + 5-10 key files.
Clear boundaries; file:line references.
---TASK---
id: p2_conventions
id: p1_conventions
agent: code-explorer
workdir: .
---CONTENT---
## Original User Request
/do <request>
## Context Pack
- Code-architect output: <Phase 1 output>
## Current Task
Identify testing patterns, conventions, config. Return: test commands + file locations.
@@ -168,86 +214,74 @@ Test commands + relevant test file paths.
EOF
```
### Phase 3: Clarification (MANDATORY)
### Phase 2: Clarify (Conditional)
**Goal:** Resolve all ambiguities before design.
**Goal:** Resolve blocking ambiguities only.
**Actions:**
1. Invoke `code-architect` to generate prioritized questions from Phase 1+2 outputs
2. Use AskUserQuestion to present questions and wait for answers
3. **Do not proceed until answered or defaults accepted**
### Phase 4: Architecture
**Goal:** Produce implementation plan fitting existing patterns.
**Actions:** Run 2 `code-architect` tasks in parallel (minimal-change vs pragmatic-clean).
1. Review `p1_requirements` output for blocking questions
2. **IF blocking questions exist** → Use AskUserQuestion
3. **IF no blocking questions (completeness >= 8)** → Skip to Phase 3, log "Requirements clear, proceeding"
```bash
codeagent-wrapper --parallel <<'EOF'
---TASK---
id: p4_minimal
agent: code-architect
workdir: .
---CONTENT---
# Only if blocking questions exist:
# Use AskUserQuestion with the blocking questions from Phase 1
```
### Phase 3: Design (No Interaction)
**Goal:** Produce minimal-change implementation plan.
**Actions:** Invoke `code-architect` with all Phase 1 context to generate a single implementation plan.
```bash
codeagent-wrapper --agent code-architect - . <<'EOF'
## Original User Request
/do <request>
## Context Pack
- Code-explorer output: <ALL Phase 2 outputs>
- Code-architect output: <Phase 1 + Phase 3 answers>
- Code-explorer output: <ALL Phase 1 explorer outputs>
- Code-architect output: <Phase 1 requirements + Phase 2 answers if any>
## Current Task
Propose minimal-change architecture: reuse existing abstractions, minimize new files.
Output: file touch list, risks, edge cases.
Design minimal-change implementation:
- Reuse existing abstractions
- Minimize new files
- Follow established patterns from code-explorer output
Output:
- File touch list with specific changes
- Build sequence
- Test plan
- Risks and mitigations
## Acceptance Criteria
Concrete blueprint; minimal moving parts.
---TASK---
id: p4_pragmatic
agent: code-architect
workdir: .
---CONTENT---
## Original User Request
/do <request>
## Context Pack
- Code-explorer output: <ALL Phase 2 outputs>
- Code-architect output: <Phase 1 + Phase 3 answers>
## Current Task
Propose pragmatic-clean architecture: introduce seams for testability.
Output: file touch list, testing plan, risks.
## Acceptance Criteria
Implementable blueprint with build sequence and tests.
Concrete, implementable blueprint with minimal moving parts.
EOF
```
Use AskUserQuestion to let user choose approach.
### Phase 4: Implement + Review (Single Interaction Point)
### Phase 5: Implementation (Approval Required)
**Goal:** Build the feature.
**Goal:** Build feature and review in one phase.
**Actions:**
1. Use AskUserQuestion: "Approve starting implementation?" (Approve / Not yet)
2. If approved, invoke `develop`:
1. Invoke `develop` to implement (add `--worktree` if `use_worktree: true`):
```bash
codeagent-wrapper --agent develop - . <<'EOF'
# Check use_worktree from state file, add --worktree if true
codeagent-wrapper --worktree --agent develop - . <<'EOF'
## Original User Request
/do <request>
## Context Pack
- Code-explorer output: <ALL Phase 2 outputs>
- Code-architect output: <selected Phase 4 blueprint + Phase 3 answers>
- Code-explorer output: <ALL Phase 1 outputs>
- Code-architect output: <Phase 3 blueprint>
## Current Task
Implement with minimal change set following chosen architecture.
- Follow Phase 2 patterns
- Add/adjust tests per Phase 4 plan
Implement with minimal change set following the blueprint.
- Follow Phase 1 patterns
- Add/adjust tests per Phase 3 plan
- Run narrowest relevant tests
## Acceptance Criteria
@@ -255,16 +289,12 @@ Feature works end-to-end; tests pass; diff is minimal.
EOF
```
### Phase 6: Review
**Goal:** Catch defects and unnecessary complexity.
**Actions:** Run 2-3 `code-reviewer` tasks in parallel (correctness, simplicity).
2. Run parallel reviews (no --worktree needed, read-only):
```bash
codeagent-wrapper --parallel <<'EOF'
---TASK---
id: p6_correctness
id: p4_correctness
agent: code-reviewer
workdir: .
---CONTENT---
@@ -272,17 +302,18 @@ workdir: .
/do <request>
## Context Pack
- Code-architect output: <Phase 4 blueprint>
- Develop output: <Phase 5 output>
- Code-architect output: <Phase 3 blueprint>
- Develop output: <implementation output>
## Current Task
Review for correctness, edge cases, failure modes. Assume adversarial inputs.
Review for correctness, edge cases, failure modes.
Classify each issue as BLOCKING or MINOR.
## Acceptance Criteria
Issues with file:line references and concrete fixes.
Issues with file:line references, severity, and concrete fixes.
---TASK---
id: p6_simplicity
id: p4_simplicity
agent: code-reviewer
workdir: .
---CONTENT---
@@ -290,20 +321,23 @@ workdir: .
/do <request>
## Context Pack
- Code-architect output: <Phase 4 blueprint>
- Develop output: <Phase 5 output>
- Code-architect output: <Phase 3 blueprint>
- Develop output: <implementation output>
## Current Task
Review for KISS: remove bloat, collapse needless abstractions.
Classify each issue as BLOCKING or MINOR.
## Acceptance Criteria
Actionable simplifications with justification.
Actionable simplifications with severity and justification.
EOF
```
Use AskUserQuestion: Fix now / Fix later / Proceed as-is.
3. Handle review results:
- **MINOR issues only** → Auto-fix via `develop` (with `--worktree` if enabled), no user interaction
- **BLOCKING issues** → Use AskUserQuestion: "Fix now / Proceed as-is"
### Phase 7: Summary
### Phase 5: Complete (No Interaction)
**Goal:** Document what was built.
@@ -315,9 +349,9 @@ codeagent-wrapper --agent code-reviewer - . <<'EOF'
/do <request>
## Context Pack
- Code-architect output: <Phase 4 blueprint>
- Code-reviewer output: <Phase 6 outcomes>
- Develop output: <Phase 5 output + fixes>
- Code-architect output: <Phase 3 blueprint>
- Code-reviewer output: <Phase 4 review outcomes>
- Develop output: <Phase 4 implementation + fixes>
## Current Task
Write completion summary:
@@ -331,3 +365,8 @@ Write completion summary:
Short, technical, actionable summary.
EOF
```
Output the completion signal:
```
<promise>DO_COMPLETE</promise>
```

View File

@@ -1,12 +1,12 @@
{
"description": "do loop hook for 7-phase workflow",
"description": "do loop hook for 5-phase workflow",
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh"
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.py"
}
]
}

144
skills/do/hooks/stop-hook.py Executable file
View File

@@ -0,0 +1,144 @@
#!/usr/bin/env python3
import glob
import json
import os
import re
import sys
PHASE_NAMES = {
1: "Understand",
2: "Clarify",
3: "Design",
4: "Implement",
5: "Complete",
}
def phase_name_for(n: int) -> str:
return PHASE_NAMES.get(n, f"Phase {n}")
def frontmatter_get(file_path: str, key: str) -> str:
try:
with open(file_path, "r", encoding="utf-8") as f:
lines = f.readlines()
except Exception:
return ""
if not lines or lines[0].strip() != "---":
return ""
for i, line in enumerate(lines[1:], start=1):
if line.strip() == "---":
break
match = re.match(rf"^{re.escape(key)}:\s*(.*)$", line)
if match:
value = match.group(1).strip()
if value.startswith('"') and value.endswith('"'):
value = value[1:-1]
return value
return ""
def get_body(file_path: str) -> str:
try:
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
except Exception:
return ""
parts = content.split("---", 2)
if len(parts) >= 3:
return parts[2]
return ""
def check_state_file(state_file: str, stdin_payload: str) -> str:
active_raw = frontmatter_get(state_file, "active")
active_lc = active_raw.lower()
if active_lc not in ("true", "1", "yes", "on"):
return ""
current_phase_raw = frontmatter_get(state_file, "current_phase")
max_phases_raw = frontmatter_get(state_file, "max_phases")
phase_name = frontmatter_get(state_file, "phase_name")
completion_promise = frontmatter_get(state_file, "completion_promise")
try:
current_phase = int(current_phase_raw)
except (ValueError, TypeError):
current_phase = 1
try:
max_phases = int(max_phases_raw)
except (ValueError, TypeError):
max_phases = 5
if not phase_name:
phase_name = phase_name_for(current_phase)
if not completion_promise:
completion_promise = "<promise>DO_COMPLETE</promise>"
phases_done = current_phase >= max_phases
promise_met = False
if completion_promise:
if stdin_payload and completion_promise in stdin_payload:
promise_met = True
else:
body = get_body(state_file)
if body and completion_promise in body:
promise_met = True
if phases_done and promise_met:
try:
os.remove(state_file)
except Exception:
pass
return ""
if not phases_done:
return (f"do loop incomplete: current phase {current_phase}/{max_phases} ({phase_name}). "
f"Continue with remaining phases; update {state_file} current_phase/phase_name after each phase. "
f"Include completion_promise in final output when done: {completion_promise}. "
f"To exit early, set active to false.")
else:
return (f"do reached final phase (current_phase={current_phase} / max_phases={max_phases}, "
f"phase_name={phase_name}), but completion_promise not detected: {completion_promise}. "
f"Please include this marker in your final output (or write it to {state_file} body), "
f"then finish; to force exit, set active to false.")
def main():
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
state_dir = os.path.join(project_dir, ".claude")
do_task_id = os.environ.get("DO_TASK_ID", "")
if do_task_id:
candidate = os.path.join(state_dir, f"do.{do_task_id}.local.md")
state_files = [candidate] if os.path.isfile(candidate) else []
else:
state_files = glob.glob(os.path.join(state_dir, "do.*.local.md"))
if not state_files:
sys.exit(0)
stdin_payload = ""
if not sys.stdin.isatty():
try:
stdin_payload = sys.stdin.read()
except Exception:
pass
blocking_reasons = []
for state_file in state_files:
reason = check_state_file(state_file, stdin_payload)
if reason:
blocking_reasons.append(reason)
if not blocking_reasons:
sys.exit(0)
combined_reason = " ".join(blocking_reasons)
print(json.dumps({"decision": "block", "reason": combined_reason}))
sys.exit(0)
if __name__ == "__main__":
main()

View File

@@ -1,160 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
phase_name_for() {
case "${1:-}" in
1) echo "Discovery" ;;
2) echo "Exploration" ;;
3) echo "Clarification" ;;
4) echo "Architecture" ;;
5) echo "Implementation" ;;
6) echo "Review" ;;
7) echo "Summary" ;;
*) echo "Phase ${1:-unknown}" ;;
esac
}
json_escape() {
local s="${1:-}"
s=${s//\\/\\\\}
s=${s//\"/\\\"}
s=${s//$'\n'/\\n}
s=${s//$'\r'/\\r}
s=${s//$'\t'/\\t}
printf "%s" "$s"
}
project_dir="${CLAUDE_PROJECT_DIR:-$PWD}"
state_dir="${project_dir}/.claude"
shopt -s nullglob
if [ -n "${DO_TASK_ID:-}" ]; then
candidate="${state_dir}/do.${DO_TASK_ID}.local.md"
if [ -f "$candidate" ]; then
state_files=("$candidate")
else
state_files=()
fi
else
state_files=("${state_dir}"/do.*.local.md)
fi
shopt -u nullglob
if [ ${#state_files[@]} -eq 0 ]; then
exit 0
fi
stdin_payload=""
if [ ! -t 0 ]; then
stdin_payload="$(cat || true)"
fi
frontmatter_get() {
local file="$1" key="$2"
awk -v k="$key" '
BEGIN { in_fm=0 }
NR==1 && $0=="---" { in_fm=1; next }
in_fm==1 && $0=="---" { exit }
in_fm==1 {
if ($0 ~ "^"k":[[:space:]]*") {
sub("^"k":[[:space:]]*", "", $0)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $0)
if ($0 ~ /^".*"$/) { sub(/^"/, "", $0); sub(/"$/, "", $0) }
print $0
exit
}
}
' "$file"
}
check_state_file() {
local state_file="$1"
local active_raw active_lc
active_raw="$(frontmatter_get "$state_file" active || true)"
active_lc="$(printf "%s" "$active_raw" | tr '[:upper:]' '[:lower:]')"
case "$active_lc" in
true|1|yes|on) ;;
*) return 0 ;;
esac
local current_phase_raw max_phases_raw phase_name completion_promise
current_phase_raw="$(frontmatter_get "$state_file" current_phase || true)"
max_phases_raw="$(frontmatter_get "$state_file" max_phases || true)"
phase_name="$(frontmatter_get "$state_file" phase_name || true)"
completion_promise="$(frontmatter_get "$state_file" completion_promise || true)"
local current_phase=1
if [[ "${current_phase_raw:-}" =~ ^[0-9]+$ ]]; then
current_phase="$current_phase_raw"
fi
local max_phases=7
if [[ "${max_phases_raw:-}" =~ ^[0-9]+$ ]]; then
max_phases="$max_phases_raw"
fi
if [ -z "${phase_name:-}" ]; then
phase_name="$(phase_name_for "$current_phase")"
fi
if [ -z "${completion_promise:-}" ]; then
completion_promise="<promise>DO_COMPLETE</promise>"
fi
local phases_done=0
if [ "$current_phase" -ge "$max_phases" ]; then
phases_done=1
fi
local promise_met=0
if [ -n "$completion_promise" ]; then
if [ -n "$stdin_payload" ] && printf "%s" "$stdin_payload" | grep -Fq -- "$completion_promise"; then
promise_met=1
else
local body
body="$(
awk '
BEGIN { in_fm=0; body=0 }
NR==1 && $0=="---" { in_fm=1; next }
in_fm==1 && $0=="---" { body=1; in_fm=0; next }
body==1 { print }
' "$state_file"
)"
if [ -n "$body" ] && printf "%s" "$body" | grep -Fq -- "$completion_promise"; then
promise_met=1
fi
fi
fi
if [ "$phases_done" -eq 1 ] && [ "$promise_met" -eq 1 ]; then
rm -f "$state_file"
return 0
fi
local reason
if [ "$phases_done" -eq 0 ]; then
reason="do loop incomplete: current phase ${current_phase}/${max_phases} (${phase_name}). Continue with remaining phases; update ${state_file} current_phase/phase_name after each phase. Include completion_promise in final output when done: ${completion_promise}. To exit early, set active to false."
else
reason="do reached final phase (current_phase=${current_phase} / max_phases=${max_phases}, phase_name=${phase_name}), but completion_promise not detected: ${completion_promise}. Please include this marker in your final output (or write it to ${state_file} body), then finish; to force exit, set active to false."
fi
printf "%s" "$reason"
}
blocking_reasons=()
for state_file in "${state_files[@]}"; do
reason="$(check_state_file "$state_file")"
if [ -n "$reason" ]; then
blocking_reasons+=("$reason")
fi
done
if [ ${#blocking_reasons[@]} -eq 0 ]; then
exit 0
fi
combined_reason="${blocking_reasons[*]}"
printf '{"decision":"block","reason":"%s"}\n' "$(json_escape "$combined_reason")"
exit 0

164
skills/do/install.py Executable file
View File

@@ -0,0 +1,164 @@
#!/usr/bin/env python3
"""Install/uninstall do skill to ~/.claude/skills/do"""
import argparse
import json
import os
import shutil
import sys
from pathlib import Path
SKILL_NAME = "do"
HOOK_PATH = "~/.claude/skills/do/hooks/stop-hook.py"
MODELS_JSON_TEMPLATE = {
"agents": {
"code-explorer": {
"backend": "claude",
"model": "claude-sonnet-4-5-20250929"
},
"code-architect": {
"backend": "claude",
"model": "claude-sonnet-4-5-20250929"
},
"code-reviewer": {
"backend": "claude",
"model": "claude-sonnet-4-5-20250929"
}
}
}
def get_settings_path() -> Path:
return Path.home() / ".claude" / "settings.json"
def load_settings() -> dict:
path = get_settings_path()
if path.exists():
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
return {}
def save_settings(settings: dict):
path = get_settings_path()
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
json.dump(settings, f, indent=2)
def add_hook(settings: dict) -> dict:
hook_command = str(Path(HOOK_PATH).expanduser())
hook_entry = {
"type": "command",
"command": f"python3 {hook_command}"
}
if "hooks" not in settings:
settings["hooks"] = {}
if "Stop" not in settings["hooks"]:
settings["hooks"]["Stop"] = []
stop_hooks = settings["hooks"]["Stop"]
for item in stop_hooks:
if "hooks" in item:
for h in item["hooks"]:
if "stop-hook" in h.get("command", "") and "do" in h.get("command", ""):
h["command"] = f"python3 {hook_command}"
return settings
stop_hooks.append({"hooks": [hook_entry]})
return settings
def remove_hook(settings: dict) -> dict:
if "hooks" not in settings or "Stop" not in settings["hooks"]:
return settings
stop_hooks = settings["hooks"]["Stop"]
new_stop_hooks = []
for item in stop_hooks:
if "hooks" in item:
filtered = [h for h in item["hooks"]
if "stop-hook" not in h.get("command", "")
or "do" not in h.get("command", "")]
if filtered:
item["hooks"] = filtered
new_stop_hooks.append(item)
else:
new_stop_hooks.append(item)
settings["hooks"]["Stop"] = new_stop_hooks
if not settings["hooks"]["Stop"]:
del settings["hooks"]["Stop"]
if not settings["hooks"]:
del settings["hooks"]
return settings
def install_models_json():
"""Install ~/.codeagent/models.json if not exists"""
path = Path.home() / ".codeagent" / "models.json"
if path.exists():
print(f"{path} already exists, skipping")
return
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
json.dump(MODELS_JSON_TEMPLATE, f, indent=2)
print(f"✓ Created {path}")
def install():
src = Path(__file__).parent.resolve()
dest = Path.home() / ".claude" / "skills" / SKILL_NAME
dest.mkdir(parents=True, exist_ok=True)
exclude = {".git", "__pycache__", ".DS_Store", "install.py"}
for item in src.iterdir():
if item.name in exclude:
continue
target = dest / item.name
if target.exists():
if target.is_dir():
shutil.rmtree(target)
else:
target.unlink()
if item.is_dir():
shutil.copytree(item, target)
else:
shutil.copy2(item, target)
settings = load_settings()
settings = add_hook(settings)
save_settings(settings)
install_models_json()
print(f"✓ Installed to {dest}")
print(f"✓ Hook added to settings.json")
def uninstall():
dest = Path.home() / ".claude" / "skills" / SKILL_NAME
settings = load_settings()
settings = remove_hook(settings)
save_settings(settings)
print(f"✓ Hook removed from settings.json")
if dest.exists():
shutil.rmtree(dest)
print(f"✓ Removed {dest}")
else:
print(f"{dest} not found")
def main():
parser = argparse.ArgumentParser(description="Install/uninstall do skill")
parser.add_argument("--uninstall", "-u", action="store_true", help="Uninstall the skill")
args = parser.parse_args()
if args.uninstall:
uninstall()
else:
install()
if __name__ == "__main__":
main()

85
skills/do/scripts/setup-do.py Executable file
View File

@@ -0,0 +1,85 @@
#!/usr/bin/env python3
import argparse
import os
import secrets
import sys
import time
PHASE_NAMES = {
1: "Understand",
2: "Clarify",
3: "Design",
4: "Implement",
5: "Complete",
}
def phase_name_for(n: int) -> str:
return PHASE_NAMES.get(n, f"Phase {n}")
def die(msg: str):
print(f"{msg}", file=sys.stderr)
sys.exit(1)
def main():
parser = argparse.ArgumentParser(
description="Creates (or overwrites) project state file: .claude/do.local.md"
)
parser.add_argument("--max-phases", type=int, default=5, help="Default: 5")
parser.add_argument(
"--completion-promise",
default="<promise>DO_COMPLETE</promise>",
help="Default: <promise>DO_COMPLETE</promise>",
)
parser.add_argument("--worktree", action="store_true", help="Enable worktree mode")
parser.add_argument("prompt", nargs="+", help="Task description")
args = parser.parse_args()
max_phases = args.max_phases
completion_promise = args.completion_promise
use_worktree = args.worktree
prompt = " ".join(args.prompt)
if max_phases < 1:
die("--max-phases must be a positive integer")
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
state_dir = os.path.join(project_dir, ".claude")
task_id = f"{int(time.time())}-{os.getpid()}-{secrets.token_hex(4)}"
state_file = os.path.join(state_dir, f"do.{task_id}.local.md")
os.makedirs(state_dir, exist_ok=True)
phase_name = phase_name_for(1)
content = f"""---
active: true
current_phase: 1
phase_name: "{phase_name}"
max_phases: {max_phases}
completion_promise: "{completion_promise}"
use_worktree: {str(use_worktree).lower()}
---
# do loop state
## Prompt
{prompt}
## Notes
- Update frontmatter current_phase/phase_name as you progress
- When complete, include the frontmatter completion_promise in your final output
"""
with open(state_file, "w", encoding="utf-8") as f:
f.write(content)
print(f"Initialized: {state_file}")
print(f"task_id: {task_id}")
print(f"phase: 1/{max_phases} ({phase_name})")
print(f"completion_promise: {completion_promise}")
print(f"use_worktree: {use_worktree}")
print(f"export DO_TASK_ID={task_id}")
if __name__ == "__main__":
main()

View File

@@ -1,115 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage: setup-do.sh [options] PROMPT...
Creates (or overwrites) project state file:
.claude/do.local.md
Options:
--max-phases N Default: 7
--completion-promise STR Default: <promise>DO_COMPLETE</promise>
-h, --help Show this help
EOF
}
die() {
echo "$*" >&2
exit 1
}
phase_name_for() {
case "${1:-}" in
1) echo "Discovery" ;;
2) echo "Exploration" ;;
3) echo "Clarification" ;;
4) echo "Architecture" ;;
5) echo "Implementation" ;;
6) echo "Review" ;;
7) echo "Summary" ;;
*) echo "Phase ${1:-unknown}" ;;
esac
}
max_phases=7
completion_promise="<promise>DO_COMPLETE</promise>"
declare -a prompt_parts=()
while [ $# -gt 0 ]; do
case "$1" in
-h|--help)
usage
exit 0
;;
--max-phases)
[ $# -ge 2 ] || die "--max-phases requires a value"
max_phases="$2"
shift 2
;;
--completion-promise)
[ $# -ge 2 ] || die "--completion-promise requires a value"
completion_promise="$2"
shift 2
;;
--)
shift
while [ $# -gt 0 ]; do
prompt_parts+=("$1")
shift
done
break
;;
-*)
die "Unknown argument: $1 (use --help)"
;;
*)
prompt_parts+=("$1")
shift
;;
esac
done
prompt="${prompt_parts[*]:-}"
[ -n "$prompt" ] || die "PROMPT is required (use --help)"
if ! [[ "$max_phases" =~ ^[0-9]+$ ]] || [ "$max_phases" -lt 1 ]; then
die "--max-phases must be a positive integer"
fi
project_dir="${CLAUDE_PROJECT_DIR:-$PWD}"
state_dir="${project_dir}/.claude"
task_id="$(date +%s)-$$-$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' \n')"
state_file="${state_dir}/do.${task_id}.local.md"
mkdir -p "$state_dir"
phase_name="$(phase_name_for 1)"
cat > "$state_file" << EOF
---
active: true
current_phase: 1
phase_name: "$phase_name"
max_phases: $max_phases
completion_promise: "$completion_promise"
---
# do loop state
## Prompt
$prompt
## Notes
- Update frontmatter current_phase/phase_name as you progress
- When complete, include the frontmatter completion_promise in your final output
EOF
echo "Initialized: $state_file"
echo "task_id: $task_id"
echo "phase: 1/$max_phases ($phase_name)"
echo "completion_promise: $completion_promise"
echo "export DO_TASK_ID=$task_id"