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.
Files changed (158) hide show
  1. {task_agent-1.3.9 → task_agent-1.3.11}/.gitignore +2 -0
  2. {task_agent-1.3.9 → task_agent-1.3.11}/PKG-INFO +1 -1
  3. {task_agent-1.3.9 → task_agent-1.3.11}/docs/cli/README.md +6 -2
  4. {task_agent-1.3.9/plugins/claude-code → task_agent-1.3.11/plugins/antigravity}/skills/complete-task/SKILL.md +8 -0
  5. {task_agent-1.3.9 → task_agent-1.3.11/plugins/claude-code}/skills/complete-task/SKILL.md +8 -0
  6. {task_agent-1.3.9 → task_agent-1.3.11}/pyproject.toml +1 -1
  7. task_agent-1.3.11/scripts/check_task_worktree_cleanup.py +240 -0
  8. {task_agent-1.3.9/plugins/antigravity → task_agent-1.3.11}/skills/complete-task/SKILL.md +8 -0
  9. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/cli.py +258 -33
  10. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/manager.py +416 -22
  11. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/mcp.py +32 -1
  12. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_cli.py +103 -1
  13. task_agent-1.3.11/tests/test_manager_git.py +571 -0
  14. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_mcp.py +27 -0
  15. {task_agent-1.3.9 → task_agent-1.3.11}/uv.lock +1 -1
  16. task_agent-1.3.9/src/taskagent/theme.py +0 -17
  17. task_agent-1.3.9/tests/test_manager_git.py +0 -253
  18. {task_agent-1.3.9 → task_agent-1.3.11}/.agents/skills/next/SKILL.md +0 -0
  19. {task_agent-1.3.9 → task_agent-1.3.11}/.gemini/settings.json +0 -0
  20. {task_agent-1.3.9 → task_agent-1.3.11}/.github/workflows/ci.yml +0 -0
  21. {task_agent-1.3.9 → task_agent-1.3.11}/.github/workflows/publish.yml +0 -0
  22. {task_agent-1.3.9 → task_agent-1.3.11}/.mise.toml +0 -0
  23. {task_agent-1.3.9 → task_agent-1.3.11}/.pre-commit-config.yaml +0 -0
  24. {task_agent-1.3.9 → task_agent-1.3.11}/.python-version +0 -0
  25. {task_agent-1.3.9 → task_agent-1.3.11}/.ta/agents/agy-cli/meta.toml +0 -0
  26. {task_agent-1.3.9 → task_agent-1.3.11}/.ta/agents/antigravity/meta.toml +0 -0
  27. {task_agent-1.3.9 → task_agent-1.3.11}/.ta/agents/gh/meta.toml +0 -0
  28. {task_agent-1.3.9 → task_agent-1.3.11}/.ta/agents/minimal/meta.toml +0 -0
  29. {task_agent-1.3.9 → task_agent-1.3.11}/.ta/agents/uat-aws/meta.toml +0 -0
  30. {task_agent-1.3.9 → task_agent-1.3.11}/.ta/worker +0 -0
  31. {task_agent-1.3.9 → task_agent-1.3.11}/.ta-config.json +0 -0
  32. {task_agent-1.3.9 → task_agent-1.3.11}/CLAUDE.md +0 -0
  33. {task_agent-1.3.9 → task_agent-1.3.11}/GEMINI.md +0 -0
  34. {task_agent-1.3.9 → task_agent-1.3.11}/Makefile +0 -0
  35. {task_agent-1.3.9 → task_agent-1.3.11}/README.md +0 -0
  36. {task_agent-1.3.9 → task_agent-1.3.11}/agent.env +0 -0
  37. {task_agent-1.3.9 → task_agent-1.3.11}/docs/README.md +0 -0
  38. {task_agent-1.3.9 → task_agent-1.3.11}/docs/STATION-MAP.md +0 -0
  39. {task_agent-1.3.9 → task_agent-1.3.11}/docs/architecture/README.md +0 -0
  40. {task_agent-1.3.9 → task_agent-1.3.11}/docs/chats/gemini-conversation-1773283034591.json +0 -0
  41. {task_agent-1.3.9 → task_agent-1.3.11}/docs/chats/project-init-to-v0.1.46.md +0 -0
  42. {task_agent-1.3.9 → task_agent-1.3.11}/docs/development/README.md +0 -0
  43. {task_agent-1.3.9 → task_agent-1.3.11}/docs/task-agents.md +0 -0
  44. {task_agent-1.3.9 → task_agent-1.3.11}/docs/workflow/README.md +0 -0
  45. {task_agent-1.3.9 → task_agent-1.3.11}/docs/workflow/trivial_worker_policy.md +0 -0
  46. {task_agent-1.3.9 → task_agent-1.3.11}/opencode.json +0 -0
  47. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/.gemini-plugin/plugin.json +0 -0
  48. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/.mcp.json +0 -0
  49. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/README.md +0 -0
  50. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/hooks/hooks.json +0 -0
  51. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/hooks/session_start.sh +0 -0
  52. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/rules/task_agent.md +0 -0
  53. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/scripts/statusline.sh +0 -0
  54. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/skills/README.md +0 -0
  55. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/skills/agent-import/SKILL.md +0 -0
  56. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/skills/mission-workflow/SKILL.md +0 -0
  57. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/skills/next/SKILL.md +0 -0
  58. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/antigravity/skills/next-task/SKILL.md +0 -0
  59. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  60. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/.mcp.json +0 -0
  61. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/README.md +0 -0
  62. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/hooks/hooks.json +0 -0
  63. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/hooks/session_start.sh +0 -0
  64. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/scripts/statusline.sh +0 -0
  65. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/skills/agent-import/SKILL.md +0 -0
  66. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/skills/mission-workflow/SKILL.md +0 -0
  67. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/claude-code/skills/next-task/SKILL.md +0 -0
  68. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/codex/.mcp.json +0 -0
  69. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/codex/README.md +0 -0
  70. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/copilot/.mcp.json +0 -0
  71. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/copilot/README.md +0 -0
  72. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/grok/.mcp.json +0 -0
  73. {task_agent-1.3.9 → task_agent-1.3.11}/plugins/grok/README.md +0 -0
  74. {task_agent-1.3.9 → task_agent-1.3.11}/scripts/clean_duplicates.py +0 -0
  75. {task_agent-1.3.9 → task_agent-1.3.11}/scripts/migrate_tasks.py +0 -0
  76. {task_agent-1.3.9 → task_agent-1.3.11}/scripts/recover_dates.py +0 -0
  77. {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/.env.example +0 -0
  78. {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/README.md +0 -0
  79. {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/instructions/manager.md +0 -0
  80. {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/instructions/validator.md +0 -0
  81. {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/instructions/worker.md +0 -0
  82. {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/profiles/default.json +0 -0
  83. {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/pyproject.toml +0 -0
  84. {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/uv.lock +0 -0
  85. {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/adk-worker/worker.py +0 -0
  86. {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/keet-bridge/README.md +0 -0
  87. {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/keet-bridge/bridge.js +0 -0
  88. {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/keet-bridge/package-lock.json +0 -0
  89. {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/keet-bridge/package.json +0 -0
  90. {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/keet-bridge/run.sh +0 -0
  91. {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/keet-bridge/test-e2e.js +0 -0
  92. {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/matrix-bridge/README.md +0 -0
  93. {task_agent-1.3.9 → task_agent-1.3.11}/sidecars/matrix-bridge/matrix_bridge.py +0 -0
  94. {task_agent-1.3.9 → task_agent-1.3.11}/skills/README.md +0 -0
  95. {task_agent-1.3.9 → task_agent-1.3.11}/skills/agent-import/SKILL.md +0 -0
  96. {task_agent-1.3.9 → task_agent-1.3.11}/skills/mission-workflow/SKILL.md +0 -0
  97. {task_agent-1.3.9 → task_agent-1.3.11}/skills/next/SKILL.md +0 -0
  98. {task_agent-1.3.9 → task_agent-1.3.11}/skills/next-task/SKILL.md +0 -0
  99. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/__init__.py +0 -0
  100. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/__main__.py +0 -0
  101. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/agent.py +0 -0
  102. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/agent_registry.py +0 -0
  103. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/audit.py +0 -0
  104. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/chat/__init__.py +0 -0
  105. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/chat/config.py +0 -0
  106. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/chat/discovery.py +0 -0
  107. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/chat/last_used.py +0 -0
  108. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/config.py +0 -0
  109. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/discovery.py +0 -0
  110. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/importers/__init__.py +0 -0
  111. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/importers/antigravity.py +0 -0
  112. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/importers/base.py +0 -0
  113. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/importers/claude_code.py +0 -0
  114. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/importers/generic.py +0 -0
  115. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/inbox.py +0 -0
  116. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/models/__init__.py +0 -0
  117. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/models/issue.py +0 -0
  118. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/models/metric.py +0 -0
  119. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/perf.py +0 -0
  120. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/plugins/__init__.py +0 -0
  121. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/plugins/github.py +0 -0
  122. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/runner.py +0 -0
  123. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/store_registry.py +0 -0
  124. {task_agent-1.3.9 → task_agent-1.3.11}/src/taskagent/templates.py +0 -0
  125. {task_agent-1.3.9 → task_agent-1.3.11}/test-dir/opencode.json +0 -0
  126. {task_agent-1.3.9 → task_agent-1.3.11}/tests/conftest.py +0 -0
  127. {task_agent-1.3.9 → task_agent-1.3.11}/tests/e2e/test_agent_sandboxing.sh +0 -0
  128. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_agent.py +0 -0
  129. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_agent_registry.py +0 -0
  130. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_agy_cli_worker.py +0 -0
  131. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_atomic_mutations.py +0 -0
  132. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_auto_commit.py +0 -0
  133. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_chat_config.py +0 -0
  134. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_chat_discovery.py +0 -0
  135. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_chat_last_used.py +0 -0
  136. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_cli_windows.py +0 -0
  137. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_discovery.py +0 -0
  138. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_github_token.py +0 -0
  139. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_importers.py +0 -0
  140. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_inbox.py +0 -0
  141. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_inbox_watch.py +0 -0
  142. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_lease.py +0 -0
  143. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_manager.py +0 -0
  144. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_metrics.py +0 -0
  145. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_migration.py +0 -0
  146. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_perf.py +0 -0
  147. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_plugin_antigravity.py +0 -0
  148. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_plugin_claude.py +0 -0
  149. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_project_host_root.py +0 -0
  150. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_rebind.py +0 -0
  151. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_remote_attach.py +0 -0
  152. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_remote_create.py +0 -0
  153. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_repo_resolve.py +0 -0
  154. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_runner.py +0 -0
  155. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_store_registry.py +0 -0
  156. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_store_symlink.py +0 -0
  157. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_templates.py +0 -0
  158. {task_agent-1.3.9 → task_agent-1.3.11}/tests/test_yazi.py +0 -0
@@ -20,3 +20,5 @@ docks/tasks
20
20
 
21
21
  # task-agent: human-facing store symlink
22
22
  docs/tasks
23
+ .map/
24
+ .ma/prompt/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: task-agent
3
- Version: 1.3.9
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 changes to the repository.
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`)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "task-agent"
3
- version = "1.3.9"
3
+ version = "1.3.11"
4
4
  description = "A prioritized, file-based task queue for autonomous agentic workers"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -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`)