task-agent 1.3.9__tar.gz → 1.3.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {task_agent-1.3.9 → task_agent-1.3.11}/.gitignore +2 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/PKG-INFO +1 -1
- {task_agent-1.3.9 → task_agent-1.3.11}/docs/cli/README.md +6 -2
- {task_agent-1.3.9/plugins/claude-code → task_agent-1.3.11/plugins/antigravity}/skills/complete-task/SKILL.md +8 -0
- {task_agent-1.3.9 → task_agent-1.3.11/plugins/claude-code}/skills/complete-task/SKILL.md +8 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/pyproject.toml +1 -1
- task_agent-1.3.11/scripts/check_task_worktree_cleanup.py +240 -0
- {task_agent-1.3.9/plugins/antigravity → task_agent-1.3.11}/skills/complete-task/SKILL.md +8 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/cli.py +258 -33
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/manager.py +416 -22
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/mcp.py +32 -1
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_cli.py +103 -1
- task_agent-1.3.11/tests/test_manager_git.py +571 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_mcp.py +27 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/uv.lock +1 -1
- task_agent-1.3.9/src/taskagent/theme.py +0 -17
- task_agent-1.3.9/tests/test_manager_git.py +0 -253
- {task_agent-1.3.9 → task_agent-1.3.11}/.agents/skills/next/SKILL.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/.gemini/settings.json +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/.github/workflows/ci.yml +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/.github/workflows/publish.yml +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/.mise.toml +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/.pre-commit-config.yaml +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/.python-version +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/.ta/agents/agy-cli/meta.toml +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/.ta/agents/antigravity/meta.toml +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/.ta/agents/gh/meta.toml +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/.ta/agents/minimal/meta.toml +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/.ta/agents/uat-aws/meta.toml +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/.ta/worker +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/.ta-config.json +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/CLAUDE.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/GEMINI.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/Makefile +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/README.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/agent.env +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/docs/README.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/docs/STATION-MAP.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/docs/architecture/README.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/docs/chats/gemini-conversation-1773283034591.json +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/docs/chats/project-init-to-v0.1.46.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/docs/development/README.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/docs/task-agents.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/docs/workflow/README.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/docs/workflow/trivial_worker_policy.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/opencode.json +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/.gemini-plugin/plugin.json +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/.mcp.json +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/README.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/hooks/hooks.json +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/hooks/session_start.sh +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/rules/task_agent.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/scripts/statusline.sh +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/skills/README.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/skills/agent-import/SKILL.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/skills/mission-workflow/SKILL.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/skills/next/SKILL.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/skills/next-task/SKILL.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/.mcp.json +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/README.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/hooks/hooks.json +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/hooks/session_start.sh +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/scripts/statusline.sh +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/skills/agent-import/SKILL.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/skills/mission-workflow/SKILL.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/skills/next-task/SKILL.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/codex/.mcp.json +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/codex/README.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/copilot/.mcp.json +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/copilot/README.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/grok/.mcp.json +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/plugins/grok/README.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/scripts/clean_duplicates.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/scripts/migrate_tasks.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/scripts/recover_dates.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/.env.example +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/README.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/instructions/manager.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/instructions/validator.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/instructions/worker.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/profiles/default.json +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/pyproject.toml +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/uv.lock +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/worker.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/keet-bridge/README.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/keet-bridge/bridge.js +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/keet-bridge/package-lock.json +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/keet-bridge/package.json +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/keet-bridge/run.sh +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/keet-bridge/test-e2e.js +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/matrix-bridge/README.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/matrix-bridge/matrix_bridge.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/skills/README.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/skills/agent-import/SKILL.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/skills/mission-workflow/SKILL.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/skills/next/SKILL.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/skills/next-task/SKILL.md +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/__init__.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/__main__.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/agent.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/agent_registry.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/audit.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/chat/__init__.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/chat/config.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/chat/discovery.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/chat/last_used.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/config.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/discovery.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/importers/__init__.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/importers/antigravity.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/importers/base.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/importers/claude_code.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/importers/generic.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/inbox.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/models/__init__.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/models/issue.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/models/metric.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/perf.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/plugins/__init__.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/plugins/github.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/runner.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/store_registry.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/templates.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/test-dir/opencode.json +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/conftest.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/e2e/test_agent_sandboxing.sh +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_agent.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_agent_registry.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_agy_cli_worker.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_atomic_mutations.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_auto_commit.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_chat_config.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_chat_discovery.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_chat_last_used.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_cli_windows.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_discovery.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_github_token.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_importers.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_inbox.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_inbox_watch.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_lease.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_manager.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_metrics.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_migration.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_perf.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_plugin_antigravity.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_plugin_claude.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_project_host_root.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_rebind.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_remote_attach.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_remote_create.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_repo_resolve.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_runner.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_store_registry.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_store_symlink.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_templates.py +0 -0
- {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_yazi.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: task-agent
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.11
|
|
4
4
|
Summary: A prioritized, file-based task queue for autonomous agentic workers
|
|
5
5
|
Project-URL: Homepage, https://github.com/InTEGr8or/task-agent
|
|
6
6
|
Project-URL: Repository, https://github.com/InTEGr8or/task-agent.git
|
|
@@ -53,12 +53,16 @@ Prints a fast, lightweight one-liner for shell prompt integration (e.g. bash, zs
|
|
|
53
53
|
Marks a task as completed.
|
|
54
54
|
- **Flags**:
|
|
55
55
|
- `-m`, `--message`: Custom git commit message.
|
|
56
|
+
- `--code-repo`: Checkout to commit (path or moniker) when the edits are not in the task store's host.
|
|
57
|
+
- `--path`: File to commit. Repeat for several files. All paths must be in one git repository.
|
|
58
|
+
- `--sweep-host`: `git add .` in the bound host checkout. Can include unrelated pending work in that host.
|
|
59
|
+
- `--cached-only`: Commit only what is already staged.
|
|
56
60
|
- `--no-commit`: Skip the automatic git commit.
|
|
61
|
+
- **Code commits follow the task's repo.** If that repo is not the one your shell is in, `ta done` commits there. It does not `git add .` the shell's repo. `--path` is a partial commit: a relative path is read in the task repo, and an absolute path can partial-commit `pwd` instead. Repeat `--path` for several files in the same git repo.
|
|
57
62
|
- **Process**:
|
|
58
63
|
1. Moves the file to `completed/{year}/`.
|
|
59
|
-
2. Commits
|
|
64
|
+
2. Commits the partitioned checkout when one was named, or the task worktree.
|
|
60
65
|
3. Appends the resulting commit hash to the task file.
|
|
61
|
-
4. Increments the project patch version (if `pyproject.toml` or `package.json` is present).
|
|
62
66
|
|
|
63
67
|
### `ta promote <slug>`
|
|
64
68
|
Moves a task from `draft/` to `pending/`.
|
|
@@ -41,6 +41,14 @@ are actually satisfied.
|
|
|
41
41
|
3. **Complete via task-agent (required)**
|
|
42
42
|
- MCP: `complete_task` with `name` + `solution` (and optional commit `message`)
|
|
43
43
|
- CLI: `ta done <slug>` (with project flags as needed)
|
|
44
|
+
- **Commit in the task's repo.** If `repo` is a different checkout from
|
|
45
|
+
the session `pwd`, the code commit goes there, not into `pwd`.
|
|
46
|
+
- **Partial commits.** Pass `paths` to stage only those files. Relative
|
|
47
|
+
paths resolve in the task repo when it is not `pwd` (a small fix in the
|
|
48
|
+
other repo, no new session). Absolute paths partial-commit `pwd` or any
|
|
49
|
+
other checkout they point at. All paths in one call must share one git
|
|
50
|
+
repo. `sweep_host` commits the whole cwd repo and can pick up unrelated
|
|
51
|
+
files.
|
|
44
52
|
4. **Self-report cost metrics when known** (cost optimization)
|
|
45
53
|
- Model name / version, provider
|
|
46
54
|
- Agent harness (e.g. `claude-code`, `antigravity`, `grok`, `opencode`)
|
|
@@ -41,6 +41,14 @@ are actually satisfied.
|
|
|
41
41
|
3. **Complete via task-agent (required)**
|
|
42
42
|
- MCP: `complete_task` with `name` + `solution` (and optional commit `message`)
|
|
43
43
|
- CLI: `ta done <slug>` (with project flags as needed)
|
|
44
|
+
- **Commit in the task's repo.** If `repo` is a different checkout from
|
|
45
|
+
the session `pwd`, the code commit goes there, not into `pwd`.
|
|
46
|
+
- **Partial commits.** Pass `paths` to stage only those files. Relative
|
|
47
|
+
paths resolve in the task repo when it is not `pwd` (a small fix in the
|
|
48
|
+
other repo, no new session). Absolute paths partial-commit `pwd` or any
|
|
49
|
+
other checkout they point at. All paths in one call must share one git
|
|
50
|
+
repo. `sweep_host` commits the whole cwd repo and can pick up unrelated
|
|
51
|
+
files.
|
|
44
52
|
4. **Self-report cost metrics when known** (cost optimization)
|
|
45
53
|
- Model name / version, provider
|
|
46
54
|
- Agent harness (e.g. `claude-code`, `antigravity`, `grok`, `opencode`)
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""Classify task-agent worktrees whose branch is merged and whose task is closed.
|
|
2
|
+
|
|
3
|
+
A worktree is safe to remove when three local signals all agree:
|
|
4
|
+
|
|
5
|
+
1. Branch merged: ``issue/<slug>`` is an ancestor of the integration branch
|
|
6
|
+
(--base, default uat). ``git cherry`` additionally detects content that
|
|
7
|
+
landed via cherry-pick/rebase (patch-merged); those branches need ``-D``
|
|
8
|
+
because ``git branch -d`` only trusts ancestry.
|
|
9
|
+
2. Task closed: the task-agent store encodes status as directory location, so
|
|
10
|
+
the slug under ``completed/YYYY/MM/`` (or ``deleted/``) means closed, while
|
|
11
|
+
``active/``, ``pending/``, ``draft/`` mean the worktree may still be a
|
|
12
|
+
runner staging area.
|
|
13
|
+
3. Nothing to lose: ``git status --porcelain`` in the worktree is empty. A
|
|
14
|
+
worktree whose directory is gone (prunable registration) counts as clean;
|
|
15
|
+
removal uses ``git worktree prune`` for those.
|
|
16
|
+
|
|
17
|
+
Run from any project repo registered in the task-agent registry.
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
python scripts/check_task_worktree_cleanup.py # report only
|
|
21
|
+
python scripts/check_task_worktree_cleanup.py --remove # remove safe ones
|
|
22
|
+
python scripts/check_task_worktree_cleanup.py --remove --allow-patch-merged
|
|
23
|
+
|
|
24
|
+
--base defaults to the current branch of the repo it runs in.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import argparse
|
|
30
|
+
import json
|
|
31
|
+
import subprocess
|
|
32
|
+
import sys
|
|
33
|
+
from dataclasses import dataclass
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
from typing import Optional
|
|
36
|
+
|
|
37
|
+
REGISTRY_PATH = Path.home() / ".local/share/task-agent/registry.json"
|
|
38
|
+
OPEN_STATUSES = ("active", "pending", "draft")
|
|
39
|
+
CLOSED_STATUSES = ("completed", "deleted")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def run_git(
|
|
43
|
+
args: list[str], cwd: Optional[Path] = None
|
|
44
|
+
) -> subprocess.CompletedProcess[str]:
|
|
45
|
+
return subprocess.run(
|
|
46
|
+
["git", *args], capture_output=True, text=True, cwd=cwd, check=False
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def moniker_from_remote(url: str) -> Optional[str]:
|
|
51
|
+
tail = url.removesuffix(".git")
|
|
52
|
+
tail = tail.rsplit(":", 1)[-1] if ":" in tail else tail
|
|
53
|
+
parts = [p for p in tail.split("/") if p]
|
|
54
|
+
if len(parts) >= 2:
|
|
55
|
+
return f"{parts[-2]}/{parts[-1]}"
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def resolve_store(repo: Path, override: Optional[str]) -> Optional[Path]:
|
|
60
|
+
if override:
|
|
61
|
+
return Path(override).expanduser()
|
|
62
|
+
if not REGISTRY_PATH.is_file():
|
|
63
|
+
return None
|
|
64
|
+
try:
|
|
65
|
+
registry = json.loads(REGISTRY_PATH.read_text(encoding="utf-8"))
|
|
66
|
+
except (OSError, ValueError):
|
|
67
|
+
return None
|
|
68
|
+
entries = registry.get("stores", {})
|
|
69
|
+
for entry in entries.values():
|
|
70
|
+
if str(repo) in entry.get("host_paths", []):
|
|
71
|
+
return Path(entry["store_path"])
|
|
72
|
+
remote = run_git(["remote", "get-url", "origin"], cwd=repo).stdout.strip()
|
|
73
|
+
moniker = moniker_from_remote(remote)
|
|
74
|
+
if moniker and moniker in entries:
|
|
75
|
+
return Path(entries[moniker]["store_path"])
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def task_status(store: Path, slug: str) -> str:
|
|
80
|
+
for status in OPEN_STATUSES:
|
|
81
|
+
if (store / status / slug).is_dir():
|
|
82
|
+
return status
|
|
83
|
+
if (store / "deleted" / slug).is_dir():
|
|
84
|
+
return "deleted"
|
|
85
|
+
for match in (store / "completed").glob(f"*/*/{slug}"):
|
|
86
|
+
if match.is_dir():
|
|
87
|
+
return "completed"
|
|
88
|
+
return "not-found"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def merged_state(repo: Path, branch: str, base: str) -> str:
|
|
92
|
+
if run_git(["merge-base", "--is-ancestor", branch, base], cwd=repo).returncode == 0:
|
|
93
|
+
return "ancestor"
|
|
94
|
+
cherry = run_git(["cherry", base, branch], cwd=repo)
|
|
95
|
+
if cherry.returncode == 0:
|
|
96
|
+
lines = [line for line in cherry.stdout.splitlines() if line.strip()]
|
|
97
|
+
if lines and all(line.startswith("-") for line in lines):
|
|
98
|
+
return "patch"
|
|
99
|
+
return "no"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def dirty_count(worktree: Path) -> Optional[int]:
|
|
103
|
+
if not worktree.is_dir():
|
|
104
|
+
return None
|
|
105
|
+
proc = run_git(["status", "--porcelain"], cwd=worktree)
|
|
106
|
+
return len([line for line in proc.stdout.splitlines() if line.strip()])
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def issue_worktrees(repo: Path) -> list[tuple[Path, str]]:
|
|
110
|
+
proc = run_git(["worktree", "list", "--porcelain"], cwd=repo)
|
|
111
|
+
pairs: list[tuple[Path, str]] = []
|
|
112
|
+
worktree: Optional[Path] = None
|
|
113
|
+
for line in proc.stdout.splitlines():
|
|
114
|
+
if line.startswith("worktree "):
|
|
115
|
+
worktree = Path(line.split(" ", 1)[1])
|
|
116
|
+
elif line.startswith("branch refs/heads/issue/"):
|
|
117
|
+
branch = line.split(" ", 1)[1][len("refs/heads/") :]
|
|
118
|
+
if worktree is not None:
|
|
119
|
+
pairs.append((worktree, branch))
|
|
120
|
+
return pairs
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@dataclass
|
|
124
|
+
class Row:
|
|
125
|
+
slug: str
|
|
126
|
+
worktree: Path
|
|
127
|
+
branch: str
|
|
128
|
+
merged: str
|
|
129
|
+
status: str
|
|
130
|
+
dirty: Optional[int]
|
|
131
|
+
verdict: str
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def classify(repo: Path, store: Optional[Path], base: str) -> list[Row]:
|
|
135
|
+
rows: list[Row] = []
|
|
136
|
+
for worktree, branch in issue_worktrees(repo):
|
|
137
|
+
slug = branch[len("issue/") :]
|
|
138
|
+
merged = merged_state(repo, branch, base)
|
|
139
|
+
status = task_status(store, slug) if store else "unknown-store"
|
|
140
|
+
dirty = dirty_count(worktree)
|
|
141
|
+
if status not in CLOSED_STATUSES:
|
|
142
|
+
verdict = f"KEEP (task {status})"
|
|
143
|
+
elif merged == "no":
|
|
144
|
+
verdict = "REVIEW (unmerged)"
|
|
145
|
+
elif dirty:
|
|
146
|
+
verdict = "REVIEW (dirty)"
|
|
147
|
+
elif merged == "patch":
|
|
148
|
+
verdict = "REMOVE (patch-merged, -D)"
|
|
149
|
+
else:
|
|
150
|
+
verdict = "REMOVE"
|
|
151
|
+
rows.append(Row(slug, worktree, branch, merged, status, dirty, verdict))
|
|
152
|
+
return rows
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def remove_row(repo: Path, row: Row, force_branch: bool) -> bool:
|
|
156
|
+
if row.worktree.is_dir():
|
|
157
|
+
proc = run_git(["worktree", "remove", str(row.worktree)], cwd=repo)
|
|
158
|
+
else:
|
|
159
|
+
proc = run_git(["worktree", "prune"], cwd=repo)
|
|
160
|
+
if proc.returncode != 0:
|
|
161
|
+
print(f" worktree remove FAILED: {row.worktree}")
|
|
162
|
+
return False
|
|
163
|
+
delete_flag = "-D" if force_branch else "-d"
|
|
164
|
+
proc = run_git(["branch", delete_flag, row.branch], cwd=repo)
|
|
165
|
+
if proc.returncode != 0:
|
|
166
|
+
print(f" branch {delete_flag} FAILED: {row.branch}: {proc.stderr.strip()}")
|
|
167
|
+
return False
|
|
168
|
+
print(f" removed {row.branch} ({row.worktree.name})")
|
|
169
|
+
return True
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def main(argv: Optional[list[str]] = None) -> int:
|
|
173
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
174
|
+
parser.add_argument("--base", help="integration branch (default: current branch)")
|
|
175
|
+
parser.add_argument("--store", help="task-agent store path override")
|
|
176
|
+
parser.add_argument(
|
|
177
|
+
"--remove",
|
|
178
|
+
action="store_true",
|
|
179
|
+
help="remove worktrees/branches verdicted REMOVE",
|
|
180
|
+
)
|
|
181
|
+
parser.add_argument(
|
|
182
|
+
"--allow-patch-merged",
|
|
183
|
+
action="store_true",
|
|
184
|
+
help="also remove patch-merged branches (deleted with -D)",
|
|
185
|
+
)
|
|
186
|
+
args = parser.parse_args(argv)
|
|
187
|
+
|
|
188
|
+
toplevel = run_git(["rev-parse", "--show-toplevel"]).stdout.strip()
|
|
189
|
+
if not toplevel:
|
|
190
|
+
print("not a git repository")
|
|
191
|
+
return 1
|
|
192
|
+
repo = Path(toplevel)
|
|
193
|
+
store = resolve_store(repo, args.store)
|
|
194
|
+
base = (
|
|
195
|
+
args.base
|
|
196
|
+
or run_git(["rev-parse", "--abbrev-ref", "HEAD"], cwd=repo).stdout.strip()
|
|
197
|
+
)
|
|
198
|
+
if not base:
|
|
199
|
+
print("could not determine integration branch; pass --base")
|
|
200
|
+
return 1
|
|
201
|
+
if store is None:
|
|
202
|
+
print("warning: task-agent store not found; task status unavailable")
|
|
203
|
+
else:
|
|
204
|
+
print(f"store: {store}")
|
|
205
|
+
|
|
206
|
+
rows = sorted(classify(repo, store, base), key=lambda r: (r.verdict, r.slug))
|
|
207
|
+
for row in rows:
|
|
208
|
+
if row.dirty is None:
|
|
209
|
+
dirty = "missing"
|
|
210
|
+
elif row.dirty:
|
|
211
|
+
dirty = f"{row.dirty} changed"
|
|
212
|
+
else:
|
|
213
|
+
dirty = "clean"
|
|
214
|
+
print(
|
|
215
|
+
f"{row.verdict:<26} merged={row.merged:<8} "
|
|
216
|
+
f"task={row.status:<10} {dirty:<12} {row.branch}"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
removable = [r for r in rows if r.verdict == "REMOVE"]
|
|
220
|
+
patchable = [r for r in rows if r.verdict == "REMOVE (patch-merged, -D)"]
|
|
221
|
+
print(
|
|
222
|
+
f"\n{len(removable)} removable, {len(patchable)} patch-merged, "
|
|
223
|
+
f"{len(rows) - len(removable) - len(patchable)} to keep/review"
|
|
224
|
+
)
|
|
225
|
+
if patchable and not args.allow_patch_merged:
|
|
226
|
+
print("patch-merged branches need --allow-patch-merged (or manual -D)")
|
|
227
|
+
|
|
228
|
+
if args.remove:
|
|
229
|
+
ok = True
|
|
230
|
+
for row in removable:
|
|
231
|
+
ok = remove_row(repo, row, force_branch=False) and ok
|
|
232
|
+
if args.allow_patch_merged:
|
|
233
|
+
for row in patchable:
|
|
234
|
+
ok = remove_row(repo, row, force_branch=True) and ok
|
|
235
|
+
return 0 if ok else 1
|
|
236
|
+
return 0
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
if __name__ == "__main__":
|
|
240
|
+
sys.exit(main())
|
|
@@ -41,6 +41,14 @@ are actually satisfied.
|
|
|
41
41
|
3. **Complete via task-agent (required)**
|
|
42
42
|
- MCP: `complete_task` with `name` + `solution` (and optional commit `message`)
|
|
43
43
|
- CLI: `ta done <slug>` (with project flags as needed)
|
|
44
|
+
- **Commit in the task's repo.** If `repo` is a different checkout from
|
|
45
|
+
the session `pwd`, the code commit goes there, not into `pwd`.
|
|
46
|
+
- **Partial commits.** Pass `paths` to stage only those files. Relative
|
|
47
|
+
paths resolve in the task repo when it is not `pwd` (a small fix in the
|
|
48
|
+
other repo, no new session). Absolute paths partial-commit `pwd` or any
|
|
49
|
+
other checkout they point at. All paths in one call must share one git
|
|
50
|
+
repo. `sweep_host` commits the whole cwd repo and can pick up unrelated
|
|
51
|
+
files.
|
|
44
52
|
4. **Self-report cost metrics when known** (cost optimization)
|
|
45
53
|
- Model name / version, provider
|
|
46
54
|
- Agent harness (e.g. `claude-code`, `antigravity`, `grok`, `opencode`)
|