mm-worktree-task: --base flag + set -e safe returns (uncommitted WIP)
Committing work found uncommitted in the checkout: --base lets a unit be based on a branch other than main (used throughout the M32 cut-over session for u6-tests and hatchmate-migration bases), and the bare 'return' statements become 'return 0' so they cannot abort under set -e when the preceding test is false. Per the project agent-safety rule: untracked/uncommitted work that matters gets committed the day it is found, so local loss cannot take it. Claude-Session: https://claude.ai/code/session_014KNyfHmHpMbdMrHZXDRkAp
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,3 +2,6 @@ local_secrets/
|
|||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
*~
|
*~
|
||||||
|
|
||||||
|
# Agent worktrees — never committed.
|
||||||
|
.claude/
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ Arguments:
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
--slug <slug> Worktree/branch suffix (default: first 8 chars of uuid)
|
--slug <slug> Worktree/branch suffix (default: first 8 chars of uuid)
|
||||||
|
--base <ref> Branch/commit the worktree is created from and that
|
||||||
|
--collect diffs against (default: main)
|
||||||
--focus "<line>" One-line focus appended to the task frame
|
--focus "<line>" One-line focus appended to the task frame
|
||||||
--brief <file> Use this file as the task body instead of/in addition
|
--brief <file> Use this file as the task body instead of/in addition
|
||||||
to the fetched item
|
to the fetched item
|
||||||
@@ -56,6 +58,7 @@ UUID=""
|
|||||||
SLUG=""
|
SLUG=""
|
||||||
FOCUS=""
|
FOCUS=""
|
||||||
BRIEF=""
|
BRIEF=""
|
||||||
|
BASE="main"
|
||||||
DOCS="recent"
|
DOCS="recent"
|
||||||
EXTRA_TOOLS=""
|
EXTRA_TOOLS=""
|
||||||
TIMEOUT=1800
|
TIMEOUT=1800
|
||||||
@@ -69,6 +72,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
--slug) SLUG="$2"; shift 2 ;;
|
--slug) SLUG="$2"; shift 2 ;;
|
||||||
--focus) FOCUS="$2"; shift 2 ;;
|
--focus) FOCUS="$2"; shift 2 ;;
|
||||||
--brief) BRIEF="$2"; shift 2 ;;
|
--brief) BRIEF="$2"; shift 2 ;;
|
||||||
|
--base) BASE="$2"; shift 2 ;;
|
||||||
--docs) DOCS="$2"; shift 2 ;;
|
--docs) DOCS="$2"; shift 2 ;;
|
||||||
--tools) EXTRA_TOOLS="$2"; shift 2 ;;
|
--tools) EXTRA_TOOLS="$2"; shift 2 ;;
|
||||||
--timeout) TIMEOUT="$2"; shift 2 ;;
|
--timeout) TIMEOUT="$2"; shift 2 ;;
|
||||||
@@ -171,11 +175,11 @@ if [[ $COLLECT -eq 1 ]]; then
|
|||||||
err "no worktree found for slug '${SLUG}' (${WORKTREE})"
|
err "no worktree found for slug '${SLUG}' (${WORKTREE})"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
info "git log --oneline main..HEAD"
|
info "git log --oneline ${BASE}..HEAD"
|
||||||
git -C "$WORKTREE" log --oneline main..HEAD || true
|
git -C "$WORKTREE" log --oneline "${BASE}..HEAD" || true
|
||||||
echo
|
echo
|
||||||
info "git diff --stat main...HEAD"
|
info "git diff --stat ${BASE}...HEAD"
|
||||||
git -C "$WORKTREE" diff --stat main...HEAD || true
|
git -C "$WORKTREE" diff --stat "${BASE}...HEAD" || true
|
||||||
echo
|
echo
|
||||||
if [[ -f "$RESULT_FILE" ]]; then
|
if [[ -f "$RESULT_FILE" ]]; then
|
||||||
info "report: $RESULT_FILE"
|
info "report: $RESULT_FILE"
|
||||||
@@ -223,15 +227,15 @@ fetch_item() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetch_docs() {
|
fetch_docs() {
|
||||||
[[ "$DOCS" == "none" ]] && return
|
[[ "$DOCS" == "none" ]] && return 0
|
||||||
local list_json
|
local list_json
|
||||||
if [[ -n "${MM_WORKTREE_TASK_FAKE_DOCS_DIR:-}" ]]; then
|
if [[ -n "${MM_WORKTREE_TASK_FAKE_DOCS_DIR:-}" ]]; then
|
||||||
[[ -f "${MM_WORKTREE_TASK_FAKE_DOCS_DIR}/list.json" ]] || return
|
[[ -f "${MM_WORKTREE_TASK_FAKE_DOCS_DIR}/list.json" ]] || return 0
|
||||||
list_json="$(cat "${MM_WORKTREE_TASK_FAKE_DOCS_DIR}/list.json")"
|
list_json="$(cat "${MM_WORKTREE_TASK_FAKE_DOCS_DIR}/list.json")"
|
||||||
elif [[ -n "$UUID" ]]; then
|
elif [[ -n "$UUID" ]]; then
|
||||||
list_json="$(cp_cli planner items documents list "$UUID" --json 2>/dev/null || echo '{"documents":[]}')"
|
list_json="$(cp_cli planner items documents list "$UUID" --json 2>/dev/null || echo '{"documents":[]}')"
|
||||||
else
|
else
|
||||||
return
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while IFS=$'\t' read -r name updated; do
|
while IFS=$'\t' read -r name updated; do
|
||||||
@@ -368,6 +372,7 @@ if [[ $DRYRUN -eq 1 ]]; then
|
|||||||
echo " cp-cli from: ${CP_CLI_DIR:-<not found>}"
|
echo " cp-cli from: ${CP_CLI_DIR:-<not found>}"
|
||||||
echo " cp-cli ctx: ${CP_CONTEXT:-<cp-cli default>}"
|
echo " cp-cli ctx: ${CP_CONTEXT:-<cp-cli default>}"
|
||||||
echo " Slug: ${SLUG}"
|
echo " Slug: ${SLUG}"
|
||||||
|
echo " Base: ${BASE}"
|
||||||
echo " UUID: ${UUID:-(none, --brief only)}"
|
echo " UUID: ${UUID:-(none, --brief only)}"
|
||||||
echo " Worktree: ${WORKTREE}"
|
echo " Worktree: ${WORKTREE}"
|
||||||
echo " Branch: ${BRANCH}"
|
echo " Branch: ${BRANCH}"
|
||||||
@@ -399,7 +404,7 @@ if [[ -d "$WORKTREE" ]] || git -C "$REPO_ROOT" show-ref --verify --quiet "refs/h
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
info "creating worktree ${WORKTREE} on branch ${BRANCH}"
|
info "creating worktree ${WORKTREE} on branch ${BRANCH}"
|
||||||
git -C "$REPO_ROOT" worktree add "$WORKTREE" -b "$BRANCH" main
|
git -C "$REPO_ROOT" worktree add "$WORKTREE" -b "$BRANCH" "$BASE"
|
||||||
|
|
||||||
info "launching ask-minimax-run (detached, timeout ${TIMEOUT}s)"
|
info "launching ask-minimax-run (detached, timeout ${TIMEOUT}s)"
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ mm-worktree-task <item-uuid> [OPTIONS]
|
|||||||
| `--timeout <secs>` | `1800` | Max seconds the sub-session may run (`timeout <secs>` wrapper). |
|
| `--timeout <secs>` | `1800` | Max seconds the sub-session may run (`timeout <secs>` wrapper). |
|
||||||
| `--context <name>` | resolved (see §0) | `cp-cli --context` used for every planner call. |
|
| `--context <name>` | resolved (see §0) | `cp-cli --context` used for every planner call. |
|
||||||
| `--upload` | off | After the sub-session exits, upload its report as a document on the item and patch `flow_state` to `impl-complete`. Implemented only in combination with `--collect` (see Behaviour §6) since launch is detached. |
|
| `--upload` | off | After the sub-session exits, upload its report as a document on the item and patch `flow_state` to `impl-complete`. Implemented only in combination with `--collect` (see Behaviour §6) since launch is detached. |
|
||||||
| `--collect` | off | Don't launch. Print `git log --oneline main..HEAD`, `git diff --stat main...HEAD`, and the report file for an already-run slug's worktree. |
|
| `--base <ref>` | `main` | Branch or commit the worktree is created from, and the ref `--collect` logs/diffs against. Needed when work lives on a long-running branch, or when one agent's branch must build on another's (e.g. an implementation agent starting from a test agent's `mm/<slug>` branch). |
|
||||||
|
| `--collect` | off | Don't launch. Print `git log --oneline <base>..HEAD`, `git diff --stat <base>...HEAD`, and the report file for an already-run slug's worktree. |
|
||||||
| `--dryrun`, `-n` | off | Assemble everything, print the bundle manifest, the full prompt, and the exact command that would run. Create nothing: no worktree, no branch, no launch, no bundle/prompt files left outside the report dir's dryrun preview. |
|
| `--dryrun`, `-n` | off | Assemble everything, print the bundle manifest, the full prompt, and the exact command that would run. Create nothing: no worktree, no branch, no launch, no bundle/prompt files left outside the report dir's dryrun preview. |
|
||||||
| `--help`, `-h` | — | Show usage and exit 0. |
|
| `--help`, `-h` | — | Show usage and exit 0. |
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ mm-worktree-task <item-uuid> [OPTIONS]
|
|||||||
- Fetch sibling tasks under the same parent: `planner items list <project> --parent <parent_id> --json` if `parent_id` is set (best-effort; skip silently if the command errors, e.g. flag unsupported for the active project).
|
- Fetch sibling tasks under the same parent: `planner items list <project> --parent <parent_id> --json` if `parent_id` is set (best-effort; skip silently if the command errors, e.g. flag unsupported for the active project).
|
||||||
- Fetching uses the operator's own `cp_cli` auth — the sub-session itself never calls `cp_cli`.
|
- Fetching uses the operator's own `cp_cli` auth — the sub-session itself never calls `cp_cli`.
|
||||||
5. Write `<report-dir>/<slug>-bundle.md` with `## ` sections: `## Item`, `## Parent` (if any), `## Blocked By` (if any), `## Siblings` (if any), `## Documents` (full name list, marking which are bundled below), then one `## Document: <name>` section per bundled document with its body. In `--dryrun`, the bundle is still assembled and its manifest (section list + doc names) is printed, but the file is not written to disk — instead its path is printed as the path that would be written.
|
5. Write `<report-dir>/<slug>-bundle.md` with `## ` sections: `## Item`, `## Parent` (if any), `## Blocked By` (if any), `## Siblings` (if any), `## Documents` (full name list, marking which are bundled below), then one `## Document: <name>` section per bundled document with its body. In `--dryrun`, the bundle is still assembled and its manifest (section list + doc names) is printed, but the file is not written to disk — instead its path is printed as the path that would be written.
|
||||||
6. Create the worktree (skipped entirely under `--dryrun` and `--collect`): `git worktree add <repo>/.claude/worktrees/mm-<slug> -b mm/<slug> main`. Fail clearly (exit 1) if the branch or worktree directory already exists.
|
6. Create the worktree (skipped entirely under `--dryrun` and `--collect`): `git worktree add <repo>/.claude/worktrees/mm-<slug> -b mm/<slug> <base>` (`--base`, default `main`). Fail clearly (exit 1) if the branch or worktree directory already exists.
|
||||||
7. Assemble the prompt file `<report-dir>/<slug>-prompt.txt`:
|
7. Assemble the prompt file `<report-dir>/<slug>-prompt.txt`:
|
||||||
- Fixed preamble: worktree path and branch; never `cd` elsewhere, never `git push`, never `git stash`, never touch `git config`; spec-driven (a behaviour change requires the matching `spec/*.md` requirement in the same commit); Python 3.12 / Pydantic v2 / `@pytest.mark.asyncio` only; do NOT run the full test suite, only the touched test files; don't fix test infrastructure, mock it; read specs and existing tests before editing, match existing names; keep the diff minimal; commit with `git add -A && git commit -m "<type>: <summary> (<uuid>)"` after verifying `git rev-parse --abbrev-ref HEAD` prints `mm/<slug>`; write the report to `<report-dir>/<slug>-result.md` with sections Summary / Files changed / Tests run / Decisions / Open questions; one-line stdout ack only.
|
- Fixed preamble: worktree path and branch; never `cd` elsewhere, never `git push`, never `git stash`, never touch `git config`; spec-driven (a behaviour change requires the matching `spec/*.md` requirement in the same commit); Python 3.12 / Pydantic v2 / `@pytest.mark.asyncio` only; do NOT run the full test suite, only the touched test files; don't fix test infrastructure, mock it; read specs and existing tests before editing, match existing names; keep the diff minimal; commit with `git add -A && git commit -m "<type>: <summary> (<uuid>)"` after verifying `git rev-parse --abbrev-ref HEAD` prints `mm/<slug>`; write the report to `<report-dir>/<slug>-result.md` with sections Summary / Files changed / Tests run / Decisions / Open questions; one-line stdout ack only.
|
||||||
- Task frame: `Implement work item <uuid>: <title>. Read the bundle at <bundle-path> fully before editing. The item body is the requirement; documents are supporting context. Documents available on the item (not all bundled): <names>.`
|
- Task frame: `Implement work item <uuid>: <title>. Read the bundle at <bundle-path> fully before editing. The item body is the requirement; documents are supporting context. Documents available on the item (not all bundled): <names>.`
|
||||||
@@ -81,7 +82,7 @@ mm-worktree-task <item-uuid> [OPTIONS]
|
|||||||
wrapped in a backgrounded subshell that, on exit, appends `<slug> rc=<rc> elapsed=<s>s` to `<report-dir>/launch.log`. The parent process returns immediately.
|
wrapped in a backgrounded subshell that, on exit, appends `<slug> rc=<rc> elapsed=<s>s` to `<report-dir>/launch.log`. The parent process returns immediately.
|
||||||
Default allowlist: `Read,Write,Edit,Glob,Grep,Bash(pytest *),Bash(python3 *),Bash(ruff *),Bash(grep *),Bash(ls *),Bash(git add *),Bash(git commit *),Bash(git status*),Bash(git diff*),Bash(git rev-parse*),Bash(git log*)`.
|
Default allowlist: `Read,Write,Edit,Glob,Grep,Bash(pytest *),Bash(python3 *),Bash(ruff *),Bash(grep *),Bash(ls *),Bash(git add *),Bash(git commit *),Bash(git status*),Bash(git diff*),Bash(git rev-parse*),Bash(git log*)`.
|
||||||
9. Print: report dir, prompt file path, stdout file path, launch.log path, and a one-line `poll with: tail -f <stdout file>` hint.
|
9. Print: report dir, prompt file path, stdout file path, launch.log path, and a one-line `poll with: tail -f <stdout file>` hint.
|
||||||
10. `--collect`: instead of steps 6-9, for the given `<slug>`'s existing worktree, print `git -C <worktree> log --oneline main..HEAD`, `git -C <worktree> diff --stat main...HEAD`, and the contents of `<report-dir>/<slug>-result.md` if present. If `--upload` is also given and the report file exists:
|
10. `--collect`: instead of steps 6-9, for the given `<slug>`'s existing worktree, print `git -C <worktree> log --oneline <base>..HEAD`, `git -C <worktree> diff --stat <base>...HEAD`, and the contents of `<report-dir>/<slug>-result.md` if present. If `--upload` is also given and the report file exists:
|
||||||
- `python3 -m lib.cp_cli planner items documents put <uuid> mm-<slug>-<YYYY-MM-DD>.md --file <report>`
|
- `python3 -m lib.cp_cli planner items documents put <uuid> mm-<slug>-<YYYY-MM-DD>.md --file <report>`
|
||||||
- `python3 -m lib.cp_cli planner items patch <uuid> --flow-state impl-complete --description "mm-worktree-task: <slug> committed on mm/<slug>"`
|
- `python3 -m lib.cp_cli planner items patch <uuid> --flow-state impl-complete --description "mm-worktree-task: <slug> committed on mm/<slug>"`
|
||||||
`--upload` without `--collect` is an error (exit 1): "`--upload` requires `--collect` (launch is detached; upload happens after the sub-session has produced a report)".
|
`--upload` without `--collect` is an error (exit 1): "`--upload` requires `--collect` (launch is detached; upload happens after the sub-session has produced a report)".
|
||||||
|
|||||||
Reference in New Issue
Block a user