feat(mm-worktree-task): run from any planner-backed repo — cp_cli dir + context resolution (--context, MM_WORKTREE_TASK_CP_*), empty-docs guard, tests + spec
cp_cli lives in agent-runtimes; previously every planner call ran 'python3 -m lib.cp_cli' from the current repo with no --context, so the script only worked inside agent-runtimes. The context now resolves from ~/.cpconfig by matching project_id to the repo directory name. Verified live from hatchmate (item + parent + documents fetched via the hatchmate context). Claude-Session: https://claude.ai/code/session_01JgJbWzVJioVznVTFyQFEfx
This commit is contained in:
BIN
scripts/__pycache__/idle-draftcpython-312.pyc
Normal file
BIN
scripts/__pycache__/idle-draftcpython-312.pyc
Normal file
Binary file not shown.
@@ -31,11 +31,21 @@ Options:
|
||||
--docs all|recent|none Which item documents to bundle (default: recent)
|
||||
--tools "<list>" Extra --allowedTools entries (comma-separated)
|
||||
--timeout <secs> Sub-session timeout (default: 1800)
|
||||
--context <name> cp-cli planner context (default: the ~/.cpconfig context
|
||||
whose project_id equals the repo name, else cp-cli's own
|
||||
current_context)
|
||||
--upload After --collect, upload the report + mark impl-complete
|
||||
--collect Don't launch; print status for an already-run slug
|
||||
--dryrun, -n Assemble everything, print it, create nothing
|
||||
--help, -h Show this help
|
||||
|
||||
Environment:
|
||||
MM_WORKTREE_TASK_CP_CLI_DIR=<dir> checkout holding lib/cp_cli (default: the
|
||||
current repo if it has one, else
|
||||
~/dev/claude/projects/agent-runtimes)
|
||||
MM_WORKTREE_TASK_CP_CONTEXT=<name> same as --context
|
||||
MM_WORKTREE_TASK_CPCONFIG=<file> cpconfig to resolve the context from
|
||||
(default: ~/.cpconfig)
|
||||
Env overrides (for testing):
|
||||
MM_WORKTREE_TASK_FAKE_ITEM=<file> fake `planner items get --json` output
|
||||
MM_WORKTREE_TASK_FAKE_DOCS_DIR=<dir> fake docs: <dir>/list.json + <dir>/<name>
|
||||
@@ -52,6 +62,7 @@ TIMEOUT=1800
|
||||
UPLOAD=0
|
||||
COLLECT=0
|
||||
DRYRUN=0
|
||||
CP_CONTEXT="${MM_WORKTREE_TASK_CP_CONTEXT:-}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
@@ -61,6 +72,7 @@ while [[ $# -gt 0 ]]; do
|
||||
--docs) DOCS="$2"; shift 2 ;;
|
||||
--tools) EXTRA_TOOLS="$2"; shift 2 ;;
|
||||
--timeout) TIMEOUT="$2"; shift 2 ;;
|
||||
--context) CP_CONTEXT="$2"; shift 2 ;;
|
||||
--upload) UPLOAD=1; shift ;;
|
||||
--collect) COLLECT=1; shift ;;
|
||||
--dryrun|-n) DRYRUN=1; shift ;;
|
||||
@@ -97,6 +109,50 @@ if ! REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"; then
|
||||
fi
|
||||
REPO_NAME="$(basename "$REPO_ROOT")"
|
||||
|
||||
# --- planner CLI: where it runs from, and which context it talks to ---
|
||||
# The script is used from any planner-backed project; cp_cli itself lives in
|
||||
# agent-runtimes. Resolve the checkout once and the context once, then every
|
||||
# planner call goes through cp_cli().
|
||||
resolve_cp_cli_dir() {
|
||||
local d
|
||||
for d in "${MM_WORKTREE_TASK_CP_CLI_DIR:-}" "$REPO_ROOT" "$HOME/dev/claude/projects/agent-runtimes"; do
|
||||
[[ -n "$d" && ( -f "$d/lib/cp_cli.py" || -d "$d/lib/cp_cli" ) ]] && { echo "$d"; return 0; }
|
||||
done
|
||||
return 1
|
||||
}
|
||||
CP_CLI_DIR="$(resolve_cp_cli_dir || true)"
|
||||
|
||||
if [[ -z "$CP_CONTEXT" ]]; then
|
||||
CPCONFIG="${MM_WORKTREE_TASK_CPCONFIG:-$HOME/.cpconfig}"
|
||||
if [[ -f "$CPCONFIG" ]]; then
|
||||
CP_CONTEXT="$(python3 - "$CPCONFIG" "$REPO_NAME" <<'PY' 2>/dev/null || true
|
||||
import sys
|
||||
try:
|
||||
import yaml
|
||||
cfg = yaml.safe_load(open(sys.argv[1])) or {}
|
||||
except Exception:
|
||||
sys.exit(0)
|
||||
for ctx in cfg.get("contexts") or []:
|
||||
if ctx.get("project_id") == sys.argv[2]:
|
||||
print(ctx.get("name", ""))
|
||||
break
|
||||
PY
|
||||
)"
|
||||
fi
|
||||
fi
|
||||
|
||||
cp_cli() {
|
||||
if [[ -z "$CP_CLI_DIR" ]]; then
|
||||
err "cannot find lib/cp_cli.py (set MM_WORKTREE_TASK_CP_CLI_DIR or check out agent-runtimes)"
|
||||
return 1
|
||||
fi
|
||||
if [[ -n "$CP_CONTEXT" ]]; then
|
||||
( cd "$CP_CLI_DIR" && python3 -m lib.cp_cli --context "$CP_CONTEXT" "$@" )
|
||||
else
|
||||
( cd "$CP_CLI_DIR" && python3 -m lib.cp_cli "$@" )
|
||||
fi
|
||||
}
|
||||
|
||||
REPORT_DIR="${XDG_RUNTIME_DIR:-/tmp}/mm-worktree-task/${REPO_NAME}"
|
||||
WORKTREE="${REPO_ROOT}/.claude/worktrees/mm-${SLUG}"
|
||||
BRANCH="mm/${SLUG}"
|
||||
@@ -140,9 +196,9 @@ if [[ $COLLECT -eq 1 ]]; then
|
||||
DATE="$(date +%Y-%m-%d)"
|
||||
DOC_NAME="mm-${SLUG}-${DATE}.md"
|
||||
info "uploading ${DOC_NAME} to item ${UUID}"
|
||||
( cd "$REPO_ROOT" && python3 -m lib.cp_cli planner items documents put "$UUID" "$DOC_NAME" --file "$RESULT_FILE" )
|
||||
( cd "$REPO_ROOT" && python3 -m lib.cp_cli planner items patch "$UUID" --flow-state impl-complete \
|
||||
--description "mm-worktree-task: ${SLUG} committed on mm/${SLUG}" )
|
||||
cp_cli planner items documents put "$UUID" "$DOC_NAME" --file "$RESULT_FILE"
|
||||
cp_cli planner items patch "$UUID" --flow-state impl-complete \
|
||||
--description "mm-worktree-task: ${SLUG} committed on mm/${SLUG}"
|
||||
ok "uploaded and marked impl-complete"
|
||||
fi
|
||||
exit 0
|
||||
@@ -160,7 +216,7 @@ fetch_item() {
|
||||
if [[ -n "${MM_WORKTREE_TASK_FAKE_ITEM:-}" ]]; then
|
||||
ITEM_JSON="$(cat "$MM_WORKTREE_TASK_FAKE_ITEM")"
|
||||
else
|
||||
ITEM_JSON="$(cd "$REPO_ROOT" && python3 -m lib.cp_cli planner items get "$UUID" --json)"
|
||||
ITEM_JSON="$(cp_cli planner items get "$UUID" --json)"
|
||||
fi
|
||||
TITLE="$(echo "$ITEM_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin).get("title",""))')"
|
||||
fi
|
||||
@@ -173,7 +229,7 @@ fetch_docs() {
|
||||
[[ -f "${MM_WORKTREE_TASK_FAKE_DOCS_DIR}/list.json" ]] || return
|
||||
list_json="$(cat "${MM_WORKTREE_TASK_FAKE_DOCS_DIR}/list.json")"
|
||||
elif [[ -n "$UUID" ]]; then
|
||||
list_json="$(cd "$REPO_ROOT" && python3 -m lib.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
|
||||
return
|
||||
fi
|
||||
@@ -201,7 +257,7 @@ fetch_docs() {
|
||||
if [[ -n "${MM_WORKTREE_TASK_FAKE_DOCS_DIR:-}" ]]; then
|
||||
DOC_BODY["$name"]="$(cat "${MM_WORKTREE_TASK_FAKE_DOCS_DIR}/${name}" 2>/dev/null || echo "")"
|
||||
else
|
||||
DOC_BODY["$name"]="$(cd "$REPO_ROOT" && python3 -m lib.cp_cli planner items documents get "$UUID" "$name" 2>/dev/null || echo "")"
|
||||
DOC_BODY["$name"]="$(cp_cli planner items documents get "$UUID" "$name" 2>/dev/null || echo "")"
|
||||
fi
|
||||
fi
|
||||
done < <(echo "$list_json" | python3 -c '
|
||||
@@ -223,7 +279,7 @@ if [[ -n "$ITEM_JSON" ]]; then
|
||||
PARENT_ID="$(echo "$ITEM_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin).get("parent_id") or "")')"
|
||||
fi
|
||||
if [[ -n "$PARENT_ID" && -z "${MM_WORKTREE_TASK_FAKE_ITEM:-}" ]]; then
|
||||
PARENT_JSON="$(cd "$REPO_ROOT" && python3 -m lib.cp_cli planner items get "$PARENT_ID" --json 2>/dev/null || echo "")"
|
||||
PARENT_JSON="$(cp_cli planner items get "$PARENT_ID" --json 2>/dev/null || echo "")"
|
||||
fi
|
||||
|
||||
# --- build bundle content ---
|
||||
@@ -256,6 +312,7 @@ build_bundle() {
|
||||
fi
|
||||
echo
|
||||
for n in "${DOC_BUNDLED[@]:-}"; do
|
||||
[[ -z "$n" ]] && continue
|
||||
echo "## Document: ${n}"
|
||||
echo '```'
|
||||
echo "${DOC_BODY[$n]:-}"
|
||||
@@ -308,6 +365,8 @@ CMD="cd ${WORKTREE} && setsid nohup timeout ${TIMEOUT} ask-minimax-run --model \
|
||||
if [[ $DRYRUN -eq 1 ]]; then
|
||||
echo "[dryrun] mm-worktree-task — no changes will be made"
|
||||
echo " Repo: ${REPO_ROOT}"
|
||||
echo " cp-cli from: ${CP_CLI_DIR:-<not found>}"
|
||||
echo " cp-cli ctx: ${CP_CONTEXT:-<cp-cli default>}"
|
||||
echo " Slug: ${SLUG}"
|
||||
echo " UUID: ${UUID:-(none, --brief only)}"
|
||||
echo " Worktree: ${WORKTREE}"
|
||||
|
||||
Reference in New Issue
Block a user