tasks-toolkit 0.1.4__tar.gz → 0.1.5__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.
- {tasks_toolkit-0.1.4/tasks_toolkit.egg-info → tasks_toolkit-0.1.5}/PKG-INFO +119 -17
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/README.md +119 -17
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/pyproject.toml +1 -1
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/run_stats.py +2 -2
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/run_summary.py +8 -12
- tasks_toolkit-0.1.5/scripts/runner_config.py +67 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/sprint_auto.py +6 -4
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/sprint_auto_execute.py +0 -1
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/sprint_auto_preflight.py +41 -32
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/sprint_auto_prepare.py +1 -3
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/sprint_auto_state.py +1 -1
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/task-report.sh +1 -1
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/task_runner.py +22 -40
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/tasks.py +1 -1
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/tasks_mcp.py +1 -1
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/test_python_fast.sh +1 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/test_run_stats.py +6 -0
- tasks_toolkit-0.1.5/scripts/test_run_summary.py +44 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/test_sprint_auto_execute.py +1 -1
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/test_sprint_auto_preflight.py +2 -1
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/test_sprint_lifecycle.py +31 -2
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/test_task_runner.py +59 -3
- tasks_toolkit-0.1.5/scripts/test_tasks_storage.py +70 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5/tasks_toolkit.egg-info}/PKG-INFO +119 -17
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/tasks_toolkit.egg-info/SOURCES.txt +2 -0
- tasks_toolkit-0.1.4/scripts/test_tasks_storage.py +0 -66
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/LICENSE +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/__init__.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/apply-plan.sh +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/run-task.sh +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/spec-lint.sh +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/sprint-auto.sh +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/sprint_auto_resume.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/sprint_plan.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/tasks_cli.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/tasks_commands.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/tasks_init.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/tasks_model.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/tasks_plan_import.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/tasks_render.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/tasks_storage.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/tasks_watch.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/test_python_lint.sh +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/test_python_slow.sh +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/test_spec_lint.sh +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/test_sprint_auto.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/test_sprint_auto_prepare.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/test_sprint_auto_resume.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/test_sprint_auto_state.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/test_tasks.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/test_tasks_mcp.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/test_tasks_plan_import.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/scripts/test_tasks_watch.py +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/setup.cfg +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/tasks_toolkit.egg-info/dependency_links.txt +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/tasks_toolkit.egg-info/entry_points.txt +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/tasks_toolkit.egg-info/requires.txt +0 -0
- {tasks_toolkit-0.1.4 → tasks_toolkit-0.1.5}/tasks_toolkit.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tasks-toolkit
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: Task tracker toolkit: CLI + MCP server for sprint/task/subtask management, delegation helpers.
|
|
5
5
|
Author: prachwal
|
|
6
6
|
License: MIT License
|
|
@@ -101,17 +101,97 @@ export TASKS_ROOT=/path/to/data
|
|
|
101
101
|
python3 scripts/tasks.py overview
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
## MCP
|
|
104
|
+
## MCP clients
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
The `tasks-mcp` command runs the same stdio server for every client. Set `TASKS_ROOT`
|
|
107
|
+
to the absolute path of the project whose tasks you want to manage. It points to
|
|
108
|
+
the project, not to the toolkit installation. An installed server needs no copy of
|
|
109
|
+
this source repository. Use an absolute executable path if the client cannot find
|
|
110
|
+
`tasks-mcp` on its PATH; `command -v tasks-mcp` shows it on Unix.
|
|
111
|
+
|
|
112
|
+
### Claude Desktop
|
|
113
|
+
|
|
114
|
+
Open **Settings → Developer → Edit Config**, merge this entry into
|
|
115
|
+
`claude_desktop_config.json`, and restart the app:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"mcpServers": {
|
|
120
|
+
"tasks": {
|
|
121
|
+
"command": "/absolute/path/to/tasks-mcp",
|
|
122
|
+
"args": [],
|
|
123
|
+
"env": {"TASKS_ROOT": "/absolute/path/to/your-project"}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
See the [official local-server setup guide](https://modelcontextprotocol.io/docs/develop/connect-local-servers)
|
|
130
|
+
for configuration locations and troubleshooting. The toolkit currently uses POSIX
|
|
131
|
+
file locking (`fcntl`); run the server on macOS or Linux. The Windows Desktop app
|
|
132
|
+
needs a launcher into a Linux environment such as WSL, rather than a native Windows
|
|
133
|
+
Python process.
|
|
134
|
+
|
|
135
|
+
### Claude Code
|
|
136
|
+
|
|
137
|
+
From your project directory, register a project-scoped stdio server:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
claude mcp add --transport stdio --scope project \
|
|
141
|
+
--env TASKS_ROOT="$(pwd)" tasks -- /absolute/path/to/tasks-mcp
|
|
142
|
+
claude mcp list
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Claude Code saves project-scoped servers to `.mcp.json`; see its
|
|
146
|
+
[official MCP documentation](https://code.claude.com/docs/en/mcp).
|
|
147
|
+
|
|
148
|
+
### VS Code / GitHub Copilot
|
|
149
|
+
|
|
150
|
+
Add a server to `.vscode/mcp.json`:
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"servers": {
|
|
155
|
+
"tasks": {
|
|
156
|
+
"type": "stdio",
|
|
157
|
+
"command": "/absolute/path/to/tasks-mcp",
|
|
158
|
+
"args": [],
|
|
159
|
+
"env": {"TASKS_ROOT": "${workspaceFolder}"}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
See [VS Code's MCP configuration reference](https://code.visualstudio.com/docs/agents/reference/mcp-configuration).
|
|
166
|
+
|
|
167
|
+
### OpenCode
|
|
168
|
+
|
|
169
|
+
Add this entry to `opencode.json`:
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"mcp": {
|
|
174
|
+
"tasks": {
|
|
175
|
+
"type": "local",
|
|
176
|
+
"command": ["/absolute/path/to/tasks-mcp"],
|
|
177
|
+
"enabled": true,
|
|
178
|
+
"environment": {"TASKS_ROOT": "/absolute/path/to/your-project"}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
112
181
|
}
|
|
113
182
|
```
|
|
114
183
|
|
|
184
|
+
See the [OpenCode MCP documentation](https://opencode.ai/docs/mcp-servers/).
|
|
185
|
+
|
|
186
|
+
### Other stdio clients and source installs
|
|
187
|
+
|
|
188
|
+
Configure any MCP client to launch `tasks-mcp`, use stdio transport and pass
|
|
189
|
+
`TASKS_ROOT` in the server environment. Clients use different JSON keys; adapt one
|
|
190
|
+
of the examples above to the client's schema. To run from a source checkout,
|
|
191
|
+
replace the executable with `python3` and pass
|
|
192
|
+
`/absolute/path/to/tasks-toolkit/scripts/tasks_mcp.py` as its first argument
|
|
193
|
+
(or use that two-element array for OpenCode's `command`).
|
|
194
|
+
|
|
115
195
|
## Verified task and sprint execution
|
|
116
196
|
|
|
117
197
|
Copy `.tasks-toolkit.example.json` to `.tasks-toolkit.json`, choose the checks for
|
|
@@ -119,12 +199,32 @@ Copy `.tasks-toolkit.example.json` to `.tasks-toolkit.json`, choose the checks f
|
|
|
119
199
|
no shell interpolation is performed. Every configured check must pass. Keep
|
|
120
200
|
`tmp/` and generated `tasks/` files in `.gitignore`, as in this repository.
|
|
121
201
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
202
|
+
Runner names and commands are defined entirely in `.tasks-toolkit.json`:
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"default_runner": "coder",
|
|
207
|
+
"runners": {
|
|
208
|
+
"coder": ["python3", "tools/coder_adapter.py"],
|
|
209
|
+
"backup-coder": ["/absolute/path/to/another-adapter"]
|
|
210
|
+
},
|
|
211
|
+
"checks": [["python3", "-m", "pytest", "-q"]],
|
|
212
|
+
"timeout": 120
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Replace these sample adapter commands with your own. Adapters receive the spec on
|
|
217
|
+
stdin, edit files in their current working directory, leave changes uncommitted,
|
|
218
|
+
and return nonzero on failure. There are no built-in providers or model choices.
|
|
219
|
+
`--runner NAME` selects any registered adapter; otherwise `default_runner` is used.
|
|
220
|
+
If no default is configured, select a runner explicitly. `--fallback NAME` uses
|
|
221
|
+
the same registry; `none` is reserved to disable fallback. Reports and statistics
|
|
222
|
+
accept any runner name containing letters, digits, underscores and hyphens.
|
|
223
|
+
|
|
224
|
+
When upgrading from 0.1.4, register the command you previously relied on explicitly:
|
|
225
|
+
implicit provider commands and the implicit default have been removed. Existing
|
|
226
|
+
checkpoint and report runner names remain valid when registered in the configuration.
|
|
227
|
+
Changing configuration still invalidates a resume checkpoint's approval fingerprint.
|
|
128
228
|
|
|
129
229
|
Installed commands are `tasks-run` and `tasks-sprint`; from source, the corresponding
|
|
130
230
|
wrappers are `scripts/run-task.sh` and `scripts/sprint-auto.sh`.
|
|
@@ -133,7 +233,7 @@ Run one task in a clean working tree, with an explicit allowed scope:
|
|
|
133
233
|
|
|
134
234
|
```bash
|
|
135
235
|
tasks-run result \
|
|
136
|
-
--file tmp/tasks/result.md --scope src/ --runner
|
|
236
|
+
--file tmp/tasks/result.md --scope src/ --runner coder
|
|
137
237
|
```
|
|
138
238
|
|
|
139
239
|
Alternatively, `--task-id 1.1` reads scope links from the tracker. The runner checks
|
|
@@ -147,14 +247,15 @@ that left the working tree clean.
|
|
|
147
247
|
For a complete sprint, supply a plan containing `sprint` (`slug`, `title`, `goal`)
|
|
148
248
|
and `tasks` (`key`, `title`, `scope`, `spec_name`, `spec`, `depends_on`). Specs must
|
|
149
249
|
pass `scripts/spec-lint.sh`; dependencies reference earlier task keys. An answers
|
|
150
|
-
file binds the reviewed goal, allowed paths and execution policy
|
|
250
|
+
file binds the reviewed goal, allowed paths and execution policy. `policy.runner`
|
|
251
|
+
overrides the configured default; delegated decisions use the same registry:
|
|
151
252
|
|
|
152
253
|
```json
|
|
153
254
|
{
|
|
154
255
|
"goal_reviewed": true,
|
|
155
256
|
"allowed_scopes": ["src/"],
|
|
156
257
|
"policy": {
|
|
157
|
-
"runner": "
|
|
258
|
+
"runner": "coder", "serial": true, "fallback": "none", "retries": 0,
|
|
158
259
|
"push": false, "accept_flags": false, "reset": false, "clean": false
|
|
159
260
|
}
|
|
160
261
|
}
|
|
@@ -220,6 +321,7 @@ builds to pass. VS Code tracker commands are provided in `.vscode/tasks.json`.
|
|
|
220
321
|
| `sprint_auto_resume.py` | Resume and finish owned sprints. |
|
|
221
322
|
| `apply-plan.sh` | Import a validated sprint plan (`tmp/plans/<name>.json`) into the tracker. |
|
|
222
323
|
| `run-task.sh` / `task_runner.py` | Run configured coders and verify actual changes before committing. |
|
|
324
|
+
| `runner_config.py` | Provider-independent runner registry, default selection and configuration validation. |
|
|
223
325
|
| `task-report.sh` | Report delegation progress to the tracker. |
|
|
224
326
|
| `run_summary.py` | Write a run summary JSON for finished runs. |
|
|
225
327
|
| `run_stats.py` | Delivery-speed statistics for delegation runners. |
|
|
@@ -52,17 +52,97 @@ export TASKS_ROOT=/path/to/data
|
|
|
52
52
|
python3 scripts/tasks.py overview
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
## MCP
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
## MCP clients
|
|
56
|
+
|
|
57
|
+
The `tasks-mcp` command runs the same stdio server for every client. Set `TASKS_ROOT`
|
|
58
|
+
to the absolute path of the project whose tasks you want to manage. It points to
|
|
59
|
+
the project, not to the toolkit installation. An installed server needs no copy of
|
|
60
|
+
this source repository. Use an absolute executable path if the client cannot find
|
|
61
|
+
`tasks-mcp` on its PATH; `command -v tasks-mcp` shows it on Unix.
|
|
62
|
+
|
|
63
|
+
### Claude Desktop
|
|
64
|
+
|
|
65
|
+
Open **Settings → Developer → Edit Config**, merge this entry into
|
|
66
|
+
`claude_desktop_config.json`, and restart the app:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"mcpServers": {
|
|
71
|
+
"tasks": {
|
|
72
|
+
"command": "/absolute/path/to/tasks-mcp",
|
|
73
|
+
"args": [],
|
|
74
|
+
"env": {"TASKS_ROOT": "/absolute/path/to/your-project"}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
63
77
|
}
|
|
64
78
|
```
|
|
65
79
|
|
|
80
|
+
See the [official local-server setup guide](https://modelcontextprotocol.io/docs/develop/connect-local-servers)
|
|
81
|
+
for configuration locations and troubleshooting. The toolkit currently uses POSIX
|
|
82
|
+
file locking (`fcntl`); run the server on macOS or Linux. The Windows Desktop app
|
|
83
|
+
needs a launcher into a Linux environment such as WSL, rather than a native Windows
|
|
84
|
+
Python process.
|
|
85
|
+
|
|
86
|
+
### Claude Code
|
|
87
|
+
|
|
88
|
+
From your project directory, register a project-scoped stdio server:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
claude mcp add --transport stdio --scope project \
|
|
92
|
+
--env TASKS_ROOT="$(pwd)" tasks -- /absolute/path/to/tasks-mcp
|
|
93
|
+
claude mcp list
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Claude Code saves project-scoped servers to `.mcp.json`; see its
|
|
97
|
+
[official MCP documentation](https://code.claude.com/docs/en/mcp).
|
|
98
|
+
|
|
99
|
+
### VS Code / GitHub Copilot
|
|
100
|
+
|
|
101
|
+
Add a server to `.vscode/mcp.json`:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"servers": {
|
|
106
|
+
"tasks": {
|
|
107
|
+
"type": "stdio",
|
|
108
|
+
"command": "/absolute/path/to/tasks-mcp",
|
|
109
|
+
"args": [],
|
|
110
|
+
"env": {"TASKS_ROOT": "${workspaceFolder}"}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
See [VS Code's MCP configuration reference](https://code.visualstudio.com/docs/agents/reference/mcp-configuration).
|
|
117
|
+
|
|
118
|
+
### OpenCode
|
|
119
|
+
|
|
120
|
+
Add this entry to `opencode.json`:
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"mcp": {
|
|
125
|
+
"tasks": {
|
|
126
|
+
"type": "local",
|
|
127
|
+
"command": ["/absolute/path/to/tasks-mcp"],
|
|
128
|
+
"enabled": true,
|
|
129
|
+
"environment": {"TASKS_ROOT": "/absolute/path/to/your-project"}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
See the [OpenCode MCP documentation](https://opencode.ai/docs/mcp-servers/).
|
|
136
|
+
|
|
137
|
+
### Other stdio clients and source installs
|
|
138
|
+
|
|
139
|
+
Configure any MCP client to launch `tasks-mcp`, use stdio transport and pass
|
|
140
|
+
`TASKS_ROOT` in the server environment. Clients use different JSON keys; adapt one
|
|
141
|
+
of the examples above to the client's schema. To run from a source checkout,
|
|
142
|
+
replace the executable with `python3` and pass
|
|
143
|
+
`/absolute/path/to/tasks-toolkit/scripts/tasks_mcp.py` as its first argument
|
|
144
|
+
(or use that two-element array for OpenCode's `command`).
|
|
145
|
+
|
|
66
146
|
## Verified task and sprint execution
|
|
67
147
|
|
|
68
148
|
Copy `.tasks-toolkit.example.json` to `.tasks-toolkit.json`, choose the checks for
|
|
@@ -70,12 +150,32 @@ Copy `.tasks-toolkit.example.json` to `.tasks-toolkit.json`, choose the checks f
|
|
|
70
150
|
no shell interpolation is performed. Every configured check must pass. Keep
|
|
71
151
|
`tmp/` and generated `tasks/` files in `.gitignore`, as in this repository.
|
|
72
152
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
153
|
+
Runner names and commands are defined entirely in `.tasks-toolkit.json`:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"default_runner": "coder",
|
|
158
|
+
"runners": {
|
|
159
|
+
"coder": ["python3", "tools/coder_adapter.py"],
|
|
160
|
+
"backup-coder": ["/absolute/path/to/another-adapter"]
|
|
161
|
+
},
|
|
162
|
+
"checks": [["python3", "-m", "pytest", "-q"]],
|
|
163
|
+
"timeout": 120
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Replace these sample adapter commands with your own. Adapters receive the spec on
|
|
168
|
+
stdin, edit files in their current working directory, leave changes uncommitted,
|
|
169
|
+
and return nonzero on failure. There are no built-in providers or model choices.
|
|
170
|
+
`--runner NAME` selects any registered adapter; otherwise `default_runner` is used.
|
|
171
|
+
If no default is configured, select a runner explicitly. `--fallback NAME` uses
|
|
172
|
+
the same registry; `none` is reserved to disable fallback. Reports and statistics
|
|
173
|
+
accept any runner name containing letters, digits, underscores and hyphens.
|
|
174
|
+
|
|
175
|
+
When upgrading from 0.1.4, register the command you previously relied on explicitly:
|
|
176
|
+
implicit provider commands and the implicit default have been removed. Existing
|
|
177
|
+
checkpoint and report runner names remain valid when registered in the configuration.
|
|
178
|
+
Changing configuration still invalidates a resume checkpoint's approval fingerprint.
|
|
79
179
|
|
|
80
180
|
Installed commands are `tasks-run` and `tasks-sprint`; from source, the corresponding
|
|
81
181
|
wrappers are `scripts/run-task.sh` and `scripts/sprint-auto.sh`.
|
|
@@ -84,7 +184,7 @@ Run one task in a clean working tree, with an explicit allowed scope:
|
|
|
84
184
|
|
|
85
185
|
```bash
|
|
86
186
|
tasks-run result \
|
|
87
|
-
--file tmp/tasks/result.md --scope src/ --runner
|
|
187
|
+
--file tmp/tasks/result.md --scope src/ --runner coder
|
|
88
188
|
```
|
|
89
189
|
|
|
90
190
|
Alternatively, `--task-id 1.1` reads scope links from the tracker. The runner checks
|
|
@@ -98,14 +198,15 @@ that left the working tree clean.
|
|
|
98
198
|
For a complete sprint, supply a plan containing `sprint` (`slug`, `title`, `goal`)
|
|
99
199
|
and `tasks` (`key`, `title`, `scope`, `spec_name`, `spec`, `depends_on`). Specs must
|
|
100
200
|
pass `scripts/spec-lint.sh`; dependencies reference earlier task keys. An answers
|
|
101
|
-
file binds the reviewed goal, allowed paths and execution policy
|
|
201
|
+
file binds the reviewed goal, allowed paths and execution policy. `policy.runner`
|
|
202
|
+
overrides the configured default; delegated decisions use the same registry:
|
|
102
203
|
|
|
103
204
|
```json
|
|
104
205
|
{
|
|
105
206
|
"goal_reviewed": true,
|
|
106
207
|
"allowed_scopes": ["src/"],
|
|
107
208
|
"policy": {
|
|
108
|
-
"runner": "
|
|
209
|
+
"runner": "coder", "serial": true, "fallback": "none", "retries": 0,
|
|
109
210
|
"push": false, "accept_flags": false, "reset": false, "clean": false
|
|
110
211
|
}
|
|
111
212
|
}
|
|
@@ -171,6 +272,7 @@ builds to pass. VS Code tracker commands are provided in `.vscode/tasks.json`.
|
|
|
171
272
|
| `sprint_auto_resume.py` | Resume and finish owned sprints. |
|
|
172
273
|
| `apply-plan.sh` | Import a validated sprint plan (`tmp/plans/<name>.json`) into the tracker. |
|
|
173
274
|
| `run-task.sh` / `task_runner.py` | Run configured coders and verify actual changes before committing. |
|
|
275
|
+
| `runner_config.py` | Provider-independent runner registry, default selection and configuration validation. |
|
|
174
276
|
| `task-report.sh` | Report delegation progress to the tracker. |
|
|
175
277
|
| `run_summary.py` | Write a run summary JSON for finished runs. |
|
|
176
278
|
| `run_stats.py` | Delivery-speed statistics for delegation runners. |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tasks-toolkit"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.5"
|
|
8
8
|
description = "Task tracker toolkit: CLI + MCP server for sprint/task/subtask management, delegation helpers."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"""Delivery-speed statistics for the delegation runners.
|
|
3
3
|
|
|
4
4
|
Reads tmp/runs/<name>.summary.json when present (exact) and falls back to the last line of
|
|
5
|
-
tmp/runs/<name>.status for older runs ("[mm:ss] done -
|
|
5
|
+
tmp/runs/<name>.status for older runs ("[mm:ss] done - worker exit 0, verification PASSED").
|
|
6
6
|
A run whose status never reached "done" counts as unfinished (killed, stalled or crashed).
|
|
7
7
|
Prints, per runner: runs, verified, failed, unfinished, median and p90 duration, plus the
|
|
8
8
|
share of runs that did not end verified ("rework rate"). Standard library only, read-only.
|
|
@@ -21,7 +21,7 @@ from pathlib import Path
|
|
|
21
21
|
|
|
22
22
|
LAST_STAMP = re.compile(r"^\[(\d+):(\d{2})\]\s+(.*)$")
|
|
23
23
|
RUNNER_TAIL = re.compile(r"\brunner=(\w+)")
|
|
24
|
-
DONE_RUNNER = re.compile(r"^done - (
|
|
24
|
+
DONE_RUNNER = re.compile(r"^done - ([A-Za-z0-9][A-Za-z0-9_-]*) exit (\d+), verification (PASSED|FAILED)")
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
def parse_status(path: Path) -> dict | None:
|
|
@@ -11,13 +11,13 @@ Usage: scripts/run_summary.py <outfile> <key=value> [<key=value> ...]
|
|
|
11
11
|
|
|
12
12
|
Keys:
|
|
13
13
|
name, runner, model, task_id, patch str (required)
|
|
14
|
-
agent str (
|
|
14
|
+
agent str (optional, for any runner)
|
|
15
15
|
started, finished int, epoch seconds (required)
|
|
16
16
|
agent_exit, verify_exit int (required)
|
|
17
17
|
files_changed int (required)
|
|
18
18
|
verdict verified|verify_failed|agent_failed
|
|
19
19
|
|
|
20
|
-
duration_s is derived as finished - started. Unknown keys,
|
|
20
|
+
duration_s is derived as finished - started. Unknown keys, an invalid runner name or verdict, or
|
|
21
21
|
a missing field are errors and nothing is written. Output is deterministic (sorted keys,
|
|
22
22
|
2-space indent, trailing newline).
|
|
23
23
|
"""
|
|
@@ -25,11 +25,14 @@ from __future__ import annotations
|
|
|
25
25
|
|
|
26
26
|
import json
|
|
27
27
|
import sys
|
|
28
|
+
from pathlib import Path
|
|
28
29
|
from typing import Any
|
|
29
30
|
|
|
31
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
32
|
+
from scripts.runner_config import valid_runner_name
|
|
33
|
+
|
|
30
34
|
STR_FIELDS = ("name", "runner", "model", "agent", "task_id", "patch", "verdict")
|
|
31
35
|
INT_FIELDS = ("started", "finished", "agent_exit", "verify_exit", "files_changed")
|
|
32
|
-
RUNNERS = ("junie", "kilo", "codex")
|
|
33
36
|
VERDICTS = ("verified", "verify_failed", "agent_failed")
|
|
34
37
|
|
|
35
38
|
|
|
@@ -63,19 +66,12 @@ def main(argv: list[str]) -> int:
|
|
|
63
66
|
print(f"error: missing fields: {', '.join(missing)}", file=sys.stderr)
|
|
64
67
|
return 2
|
|
65
68
|
|
|
66
|
-
if data["runner"]
|
|
67
|
-
print(
|
|
69
|
+
if not valid_runner_name(data["runner"]):
|
|
70
|
+
print("error: runner must be a name containing letters, digits, underscores or hyphens; 'none' is reserved", file=sys.stderr)
|
|
68
71
|
return 2
|
|
69
72
|
if data["verdict"] not in VERDICTS:
|
|
70
73
|
print(f"error: verdict must be one of {', '.join(VERDICTS)}, got {data['verdict']!r}", file=sys.stderr)
|
|
71
74
|
return 2
|
|
72
|
-
if data["runner"] == "kilo" and "agent" not in data:
|
|
73
|
-
print("error: field 'agent' is required for runner=kilo", file=sys.stderr)
|
|
74
|
-
return 2
|
|
75
|
-
if data["runner"] == "junie" and "agent" in data:
|
|
76
|
-
print("error: field 'agent' is only allowed for runner=kilo", file=sys.stderr)
|
|
77
|
-
return 2
|
|
78
|
-
|
|
79
75
|
duration = int(data["finished"]) - int(data["started"])
|
|
80
76
|
if duration < 0:
|
|
81
77
|
print("error: finished is before started", file=sys.stderr)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Provider-independent runner registry and execution configuration.
|
|
2
|
+
|
|
3
|
+
Each registered command receives a task spec on stdin and runs in the task
|
|
4
|
+
worktree. Providers and their CLI arguments belong in project configuration.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
CONFIG_FILE = ".tasks-toolkit.json"
|
|
13
|
+
RUNNER_NAME = re.compile(r"[A-Za-z0-9][A-Za-z0-9_-]*")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RunnerError(Exception):
|
|
17
|
+
"""Runner configuration or an execution prerequisite is invalid."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def valid_runner_name(name: object) -> bool:
|
|
21
|
+
return isinstance(name, str) and name != "none" and RUNNER_NAME.fullmatch(name) is not None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def valid_command(command: object) -> bool:
|
|
25
|
+
return isinstance(command, list) and bool(command) and all(isinstance(v, str) and bool(v) for v in command)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def runner_command(config: dict, runner: str) -> list[str]:
|
|
29
|
+
if not valid_runner_name(runner):
|
|
30
|
+
raise RunnerError("runner must be a name containing letters, digits, underscores or hyphens; 'none' is reserved")
|
|
31
|
+
command = config.get("runners", {}).get(runner)
|
|
32
|
+
if not valid_command(command):
|
|
33
|
+
raise RunnerError(f"configure runners.{runner} in {CONFIG_FILE}; the command receives the spec on stdin")
|
|
34
|
+
return list(command)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def select_runner(config: dict, requested: str | None = None) -> str:
|
|
38
|
+
name = requested if requested is not None else config.get("default_runner")
|
|
39
|
+
if name is None:
|
|
40
|
+
raise RunnerError(f"choose --runner or policy.runner, or set default_runner in {CONFIG_FILE}")
|
|
41
|
+
runner_command(config, name)
|
|
42
|
+
return str(name)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def load_config(root: Path) -> dict:
|
|
46
|
+
path = root / CONFIG_FILE
|
|
47
|
+
if not path.is_file():
|
|
48
|
+
raise RunnerError(f"create {CONFIG_FILE} with runners and a non-empty checks list before running tasks")
|
|
49
|
+
config = json.loads(path.read_text(encoding="utf-8"))
|
|
50
|
+
if not isinstance(config, dict):
|
|
51
|
+
raise RunnerError("runner config must be an object")
|
|
52
|
+
checks = config.get("checks")
|
|
53
|
+
if not isinstance(checks, list) or not checks or not all(valid_command(c) for c in checks):
|
|
54
|
+
raise RunnerError("checks must be a non-empty list of command argument arrays")
|
|
55
|
+
runners = config.get("runners")
|
|
56
|
+
if not isinstance(runners, dict) or not runners:
|
|
57
|
+
raise RunnerError("runners must be a non-empty mapping of names to command argument arrays")
|
|
58
|
+
for name in runners:
|
|
59
|
+
runner_command(config, name)
|
|
60
|
+
if "default_runner" in config:
|
|
61
|
+
if not valid_runner_name(config["default_runner"]):
|
|
62
|
+
raise RunnerError("default_runner must name a configured runner")
|
|
63
|
+
select_runner(config)
|
|
64
|
+
timeout = config.get("timeout", 120)
|
|
65
|
+
if type(timeout) is not int or timeout < 1:
|
|
66
|
+
raise RunnerError("timeout must be a positive integer")
|
|
67
|
+
return config
|
|
@@ -10,6 +10,7 @@ from pathlib import Path
|
|
|
10
10
|
|
|
11
11
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
12
12
|
from scripts import sprint_auto_execute as execute
|
|
13
|
+
from scripts import sprint_auto_preflight as preflight
|
|
13
14
|
from scripts import sprint_auto_prepare as prepare
|
|
14
15
|
from scripts import sprint_auto_state as sas
|
|
15
16
|
from scripts import task_runner as runner
|
|
@@ -61,7 +62,10 @@ def run(root: Path, goal: str, name: str, answers: dict, plan: Path) -> dict:
|
|
|
61
62
|
if not runner.NAME.fullmatch(name):
|
|
62
63
|
raise runner.RunnerError("invalid run name")
|
|
63
64
|
config = runner.load_config(root)
|
|
64
|
-
|
|
65
|
+
report = preflight.collect_preflight(root, {"goal": goal}, answers)
|
|
66
|
+
if report["blockers"] or report["questions"]:
|
|
67
|
+
return {"status": "blocked" if report["blockers"] else "needs_answers", "preflight": report}
|
|
68
|
+
policy = report["approval_scope"]["policy"]
|
|
65
69
|
if policy.get("fallback", "none") != "none" or policy.get("retries", 0) != 0:
|
|
66
70
|
raise runner.RunnerError("automated sprints require fallback=none and retries=0")
|
|
67
71
|
for flag in ("push", "accept_flags", "reset", "clean"):
|
|
@@ -69,9 +73,7 @@ def run(root: Path, goal: str, name: str, answers: dict, plan: Path) -> dict:
|
|
|
69
73
|
raise runner.RunnerError(f"automated sprints do not support {flag}=true")
|
|
70
74
|
if policy.get("serial", True) is not True:
|
|
71
75
|
raise runner.RunnerError("automated sprints execute serially")
|
|
72
|
-
|
|
73
|
-
if not runner_command:
|
|
74
|
-
raise runner.RunnerError("runner command missing")
|
|
76
|
+
runner.select_runner(config, policy.get("runner"))
|
|
75
77
|
prepared = prepare.prepare_run(root, {"goal": goal}, answers, name, plan)
|
|
76
78
|
if prepared["status"] != "prepared":
|
|
77
79
|
return prepared
|