taskgate 0.1.0__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.
- taskgate-0.1.0/LICENSE +21 -0
- taskgate-0.1.0/PKG-INFO +156 -0
- taskgate-0.1.0/README.md +128 -0
- taskgate-0.1.0/pyproject.toml +57 -0
- taskgate-0.1.0/setup.cfg +4 -0
- taskgate-0.1.0/src/taskgate/__init__.py +3 -0
- taskgate-0.1.0/src/taskgate/__main__.py +5 -0
- taskgate-0.1.0/src/taskgate/cli.py +133 -0
- taskgate-0.1.0/src/taskgate/clients.py +52 -0
- taskgate-0.1.0/src/taskgate/commands/__init__.py +1 -0
- taskgate-0.1.0/src/taskgate/commands/common.py +25 -0
- taskgate-0.1.0/src/taskgate/commands/flow.py +307 -0
- taskgate-0.1.0/src/taskgate/commands/plan.py +101 -0
- taskgate-0.1.0/src/taskgate/commands/registry.py +70 -0
- taskgate-0.1.0/src/taskgate/config.py +135 -0
- taskgate-0.1.0/src/taskgate/mcp/__init__.py +1 -0
- taskgate-0.1.0/src/taskgate/mcp/bridge.py +42 -0
- taskgate-0.1.0/src/taskgate/mcp/protocol.py +76 -0
- taskgate-0.1.0/src/taskgate/mcp/registry.py +44 -0
- taskgate-0.1.0/src/taskgate/mcp/server.py +25 -0
- taskgate-0.1.0/src/taskgate/mcp/tools.py +207 -0
- taskgate-0.1.0/src/taskgate/model.py +158 -0
- taskgate-0.1.0/src/taskgate/ordering.py +119 -0
- taskgate-0.1.0/src/taskgate/render.py +39 -0
- taskgate-0.1.0/src/taskgate/store.py +143 -0
- taskgate-0.1.0/src/taskgate.egg-info/PKG-INFO +156 -0
- taskgate-0.1.0/src/taskgate.egg-info/SOURCES.txt +33 -0
- taskgate-0.1.0/src/taskgate.egg-info/dependency_links.txt +1 -0
- taskgate-0.1.0/src/taskgate.egg-info/entry_points.txt +3 -0
- taskgate-0.1.0/src/taskgate.egg-info/requires.txt +5 -0
- taskgate-0.1.0/src/taskgate.egg-info/top_level.txt +1 -0
- taskgate-0.1.0/tests/test_clients.py +50 -0
- taskgate-0.1.0/tests/test_config.py +89 -0
- taskgate-0.1.0/tests/test_flow.py +240 -0
- taskgate-0.1.0/tests/test_mcp.py +109 -0
taskgate-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 prachwal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
taskgate-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: taskgate
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Gated task registry with an MCP server: strict status flow, verification and review evidence, for Claude Code, Codex, VS Code Copilot and other MCP clients.
|
|
5
|
+
Author: prachwal
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/prachwal/taskgate
|
|
8
|
+
Project-URL: Repository, https://github.com/prachwal/taskgate
|
|
9
|
+
Project-URL: Issues, https://github.com/prachwal/taskgate/issues
|
|
10
|
+
Keywords: mcp,model-context-protocol,task-tracker,agents,workflow,claude-code,codex,copilot
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
25
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
26
|
+
Requires-Dist: mypy>=1.11; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# taskgate
|
|
30
|
+
|
|
31
|
+
A task registry that makes "done" mean something. Tasks move through a strict status flow, and the
|
|
32
|
+
transitions that matter are gated by evidence: a task enters review only with a green verification, and
|
|
33
|
+
closes only with a review from a *different* identity that lists the commands it checked. It ships as a CLI
|
|
34
|
+
and as an MCP server, so coding agents (Claude Code, Codex, VS Code Copilot, Cursor, Gemini CLI) and humans
|
|
35
|
+
work from the same registry.
|
|
36
|
+
|
|
37
|
+
- stdlib-only, no runtime dependencies; Python 3.11+; Linux, macOS and Windows
|
|
38
|
+
- state is plain files you commit: `tasks.json` (source of truth), an append-only `tasks.log.jsonl` and a
|
|
39
|
+
generated `tasks.md`
|
|
40
|
+
- one code path: every MCP tool calls the same CLI command a human would run
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
cd your-project
|
|
46
|
+
uvx taskgate init
|
|
47
|
+
uvx taskgate add --id T-1 --title "Parse the header" --verification-cmd "pytest tests/test_header.py"
|
|
48
|
+
uvx taskgate next # which task may start now
|
|
49
|
+
uvx taskgate claim T-1 --by dev
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Add it to your agent (prints a ready-to-paste block; see [docs/clients.md](docs/clients.md)):
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
uvx taskgate config-snippet claude # also: vscode, codex, cursor, gemini, generic
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
From a checkout instead of PyPI: `uvx --from /path/to/taskgate taskgate ...`, or
|
|
59
|
+
`taskgate config-snippet claude --from /path/to/taskgate`.
|
|
60
|
+
|
|
61
|
+
## The flow
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
draft -> planned -> in_progress -> in_review -> done
|
|
65
|
+
| ^ |
|
|
66
|
+
v | +--> in_progress (review rejected)
|
|
67
|
+
blocked
|
|
68
|
+
any non-terminal status -> cancelled
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| Rule | What it enforces |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| order | `claim` works only on tasks whose predecessors in the registry are `done` or `cancelled` |
|
|
74
|
+
| phase gate | a task in a later phase waits until every earlier phase is closed and the registry validates |
|
|
75
|
+
| dependencies | `depends_on` tasks must be `done` before the task can start |
|
|
76
|
+
| verification | `in_review` and `done` need a green `verification_result`; the tool stamps who recorded it and when |
|
|
77
|
+
| review | `done` needs a green `review_result` from the reviewer identity, different from the verifier, listing `checks`, recorded after a delay, in the same call as the closing update |
|
|
78
|
+
| fresh evidence | a new verification voids an earlier review; results are never accepted together in one call |
|
|
79
|
+
| planning | adding, moving and re-linking tasks is limited to the `planners` identities; started work is never reordered |
|
|
80
|
+
|
|
81
|
+
Identities (`--by`, or the `TASKGATE_BY` environment variable, default `human`) are *declared by the caller*.
|
|
82
|
+
This is a process barrier that keeps roles apart and leaves a trail in the log; it is not authentication.
|
|
83
|
+
|
|
84
|
+
## Configuration
|
|
85
|
+
|
|
86
|
+
Optional `taskgate.toml` in the project root; every key has a default
|
|
87
|
+
([examples/taskgate.toml](examples/taskgate.toml)).
|
|
88
|
+
|
|
89
|
+
| Key | Default | Meaning |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| `data_dir` | `.taskgate` | where `tasks.json`, `tasks.log.jsonl`, `tasks.md` live |
|
|
92
|
+
| `title` | `Tasks` | heading of the Markdown view |
|
|
93
|
+
| `id_pattern` | `^[A-Za-z][A-Za-z0-9]*-\d+$` | task id regex; a named group `(?P<phase>\d+)` ties the id to the phase |
|
|
94
|
+
| `phase_names` | none | `{0 = "Foundations"}` labels for the view |
|
|
95
|
+
| `planners` | `["human"]` | identities allowed to add, move and re-link tasks |
|
|
96
|
+
| `reviewer` | `"reviewer"` | identity whose `review_result` closes a task |
|
|
97
|
+
| `owners` | any | restrict `owner` values |
|
|
98
|
+
| `require_review` | `true` | `false`: `done` needs only a green verification |
|
|
99
|
+
| `review_delay_seconds` | `60` | minimum time between verification and review |
|
|
100
|
+
| `[quality]` | none | named gates for the `quality_check` tool, each a list of argv arrays |
|
|
101
|
+
|
|
102
|
+
The project root is `TASKGATE_ROOT` if set, otherwise the nearest ancestor of the working directory that has
|
|
103
|
+
`taskgate.toml` or `.taskgate/`.
|
|
104
|
+
[examples/embersail.toml](examples/embersail.toml) reproduces a stricter real-world setup (ids `P<phase>-NN`,
|
|
105
|
+
architect/human planners) to show the engine is not tied to one convention.
|
|
106
|
+
|
|
107
|
+
## Commands
|
|
108
|
+
|
|
109
|
+
`init`, `validate`, `render [--check]`, `next`, `list [--status]`, `claim`, `update`, `add`, `sort`, `move`,
|
|
110
|
+
`depends`, `mcp`, `config-snippet`. `taskgate <command> --help` lists the flags.
|
|
111
|
+
|
|
112
|
+
Recording evidence, in separate calls:
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
taskgate update T-1 --verification-result '{"ok": true}' --by dev
|
|
116
|
+
taskgate update T-1 --status in_review --by dev
|
|
117
|
+
taskgate update T-1 --status done --by reviewer \
|
|
118
|
+
--review-result '{"ok": true, "by": "reviewer", "checks": ["pytest -q"]}'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## MCP tools
|
|
122
|
+
|
|
123
|
+
`task_list`, `task_next`, `task_validate` (read-only, so clients can skip the approval prompt), `task_claim`,
|
|
124
|
+
`task_update`, `task_add`, `task_move`, `task_depends`, `task_sort`, `task_render` and `quality_check`.
|
|
125
|
+
Tool failures come back as `isError` results rather than protocol errors, so an agent sees the reason.
|
|
126
|
+
|
|
127
|
+
The server speaks newline-delimited JSON-RPC 2.0 over stdio. Run it with `taskgate mcp` or `taskgate-mcp`.
|
|
128
|
+
|
|
129
|
+
## Development
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
python -m pip install -e ".[dev]"
|
|
133
|
+
ruff check . && mypy && pytest
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Releasing
|
|
137
|
+
|
|
138
|
+
Pushing to `main` runs the tests on Linux, macOS and Windows and builds the package. If `version` in
|
|
139
|
+
`pyproject.toml` has no `v<version>` tag yet, the workflow publishes to PyPI through trusted publishing and
|
|
140
|
+
creates the tag and GitHub release. To release: bump `version` in `pyproject.toml` and
|
|
141
|
+
`src/taskgate/__init__.py` (a test keeps them equal), add a changelog entry, merge to `main`.
|
|
142
|
+
|
|
143
|
+
One-time setup, either way: a GitHub environment named `pypi`. Then either store a PyPI API token as the
|
|
144
|
+
repository secret `PYPI_API_TOKEN` (the first release of a new project needs an account-wide token; narrow it
|
|
145
|
+
to the project afterwards), or add a (pending) trusted publisher on PyPI for this repository, workflow
|
|
146
|
+
`ci.yml`, environment `pypi`. With the secret set it is used; without it the workflow falls back to trusted
|
|
147
|
+
publishing.
|
|
148
|
+
|
|
149
|
+
## Status
|
|
150
|
+
|
|
151
|
+
Alpha. Not included: synchronization with a forge (GitLab/GitHub issues). It originated in the Embersail
|
|
152
|
+
project's task tooling.
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT
|
taskgate-0.1.0/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# taskgate
|
|
2
|
+
|
|
3
|
+
A task registry that makes "done" mean something. Tasks move through a strict status flow, and the
|
|
4
|
+
transitions that matter are gated by evidence: a task enters review only with a green verification, and
|
|
5
|
+
closes only with a review from a *different* identity that lists the commands it checked. It ships as a CLI
|
|
6
|
+
and as an MCP server, so coding agents (Claude Code, Codex, VS Code Copilot, Cursor, Gemini CLI) and humans
|
|
7
|
+
work from the same registry.
|
|
8
|
+
|
|
9
|
+
- stdlib-only, no runtime dependencies; Python 3.11+; Linux, macOS and Windows
|
|
10
|
+
- state is plain files you commit: `tasks.json` (source of truth), an append-only `tasks.log.jsonl` and a
|
|
11
|
+
generated `tasks.md`
|
|
12
|
+
- one code path: every MCP tool calls the same CLI command a human would run
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
cd your-project
|
|
18
|
+
uvx taskgate init
|
|
19
|
+
uvx taskgate add --id T-1 --title "Parse the header" --verification-cmd "pytest tests/test_header.py"
|
|
20
|
+
uvx taskgate next # which task may start now
|
|
21
|
+
uvx taskgate claim T-1 --by dev
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Add it to your agent (prints a ready-to-paste block; see [docs/clients.md](docs/clients.md)):
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
uvx taskgate config-snippet claude # also: vscode, codex, cursor, gemini, generic
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
From a checkout instead of PyPI: `uvx --from /path/to/taskgate taskgate ...`, or
|
|
31
|
+
`taskgate config-snippet claude --from /path/to/taskgate`.
|
|
32
|
+
|
|
33
|
+
## The flow
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
draft -> planned -> in_progress -> in_review -> done
|
|
37
|
+
| ^ |
|
|
38
|
+
v | +--> in_progress (review rejected)
|
|
39
|
+
blocked
|
|
40
|
+
any non-terminal status -> cancelled
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
| Rule | What it enforces |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| order | `claim` works only on tasks whose predecessors in the registry are `done` or `cancelled` |
|
|
46
|
+
| phase gate | a task in a later phase waits until every earlier phase is closed and the registry validates |
|
|
47
|
+
| dependencies | `depends_on` tasks must be `done` before the task can start |
|
|
48
|
+
| verification | `in_review` and `done` need a green `verification_result`; the tool stamps who recorded it and when |
|
|
49
|
+
| review | `done` needs a green `review_result` from the reviewer identity, different from the verifier, listing `checks`, recorded after a delay, in the same call as the closing update |
|
|
50
|
+
| fresh evidence | a new verification voids an earlier review; results are never accepted together in one call |
|
|
51
|
+
| planning | adding, moving and re-linking tasks is limited to the `planners` identities; started work is never reordered |
|
|
52
|
+
|
|
53
|
+
Identities (`--by`, or the `TASKGATE_BY` environment variable, default `human`) are *declared by the caller*.
|
|
54
|
+
This is a process barrier that keeps roles apart and leaves a trail in the log; it is not authentication.
|
|
55
|
+
|
|
56
|
+
## Configuration
|
|
57
|
+
|
|
58
|
+
Optional `taskgate.toml` in the project root; every key has a default
|
|
59
|
+
([examples/taskgate.toml](examples/taskgate.toml)).
|
|
60
|
+
|
|
61
|
+
| Key | Default | Meaning |
|
|
62
|
+
| --- | --- | --- |
|
|
63
|
+
| `data_dir` | `.taskgate` | where `tasks.json`, `tasks.log.jsonl`, `tasks.md` live |
|
|
64
|
+
| `title` | `Tasks` | heading of the Markdown view |
|
|
65
|
+
| `id_pattern` | `^[A-Za-z][A-Za-z0-9]*-\d+$` | task id regex; a named group `(?P<phase>\d+)` ties the id to the phase |
|
|
66
|
+
| `phase_names` | none | `{0 = "Foundations"}` labels for the view |
|
|
67
|
+
| `planners` | `["human"]` | identities allowed to add, move and re-link tasks |
|
|
68
|
+
| `reviewer` | `"reviewer"` | identity whose `review_result` closes a task |
|
|
69
|
+
| `owners` | any | restrict `owner` values |
|
|
70
|
+
| `require_review` | `true` | `false`: `done` needs only a green verification |
|
|
71
|
+
| `review_delay_seconds` | `60` | minimum time between verification and review |
|
|
72
|
+
| `[quality]` | none | named gates for the `quality_check` tool, each a list of argv arrays |
|
|
73
|
+
|
|
74
|
+
The project root is `TASKGATE_ROOT` if set, otherwise the nearest ancestor of the working directory that has
|
|
75
|
+
`taskgate.toml` or `.taskgate/`.
|
|
76
|
+
[examples/embersail.toml](examples/embersail.toml) reproduces a stricter real-world setup (ids `P<phase>-NN`,
|
|
77
|
+
architect/human planners) to show the engine is not tied to one convention.
|
|
78
|
+
|
|
79
|
+
## Commands
|
|
80
|
+
|
|
81
|
+
`init`, `validate`, `render [--check]`, `next`, `list [--status]`, `claim`, `update`, `add`, `sort`, `move`,
|
|
82
|
+
`depends`, `mcp`, `config-snippet`. `taskgate <command> --help` lists the flags.
|
|
83
|
+
|
|
84
|
+
Recording evidence, in separate calls:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
taskgate update T-1 --verification-result '{"ok": true}' --by dev
|
|
88
|
+
taskgate update T-1 --status in_review --by dev
|
|
89
|
+
taskgate update T-1 --status done --by reviewer \
|
|
90
|
+
--review-result '{"ok": true, "by": "reviewer", "checks": ["pytest -q"]}'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## MCP tools
|
|
94
|
+
|
|
95
|
+
`task_list`, `task_next`, `task_validate` (read-only, so clients can skip the approval prompt), `task_claim`,
|
|
96
|
+
`task_update`, `task_add`, `task_move`, `task_depends`, `task_sort`, `task_render` and `quality_check`.
|
|
97
|
+
Tool failures come back as `isError` results rather than protocol errors, so an agent sees the reason.
|
|
98
|
+
|
|
99
|
+
The server speaks newline-delimited JSON-RPC 2.0 over stdio. Run it with `taskgate mcp` or `taskgate-mcp`.
|
|
100
|
+
|
|
101
|
+
## Development
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
python -m pip install -e ".[dev]"
|
|
105
|
+
ruff check . && mypy && pytest
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Releasing
|
|
109
|
+
|
|
110
|
+
Pushing to `main` runs the tests on Linux, macOS and Windows and builds the package. If `version` in
|
|
111
|
+
`pyproject.toml` has no `v<version>` tag yet, the workflow publishes to PyPI through trusted publishing and
|
|
112
|
+
creates the tag and GitHub release. To release: bump `version` in `pyproject.toml` and
|
|
113
|
+
`src/taskgate/__init__.py` (a test keeps them equal), add a changelog entry, merge to `main`.
|
|
114
|
+
|
|
115
|
+
One-time setup, either way: a GitHub environment named `pypi`. Then either store a PyPI API token as the
|
|
116
|
+
repository secret `PYPI_API_TOKEN` (the first release of a new project needs an account-wide token; narrow it
|
|
117
|
+
to the project afterwards), or add a (pending) trusted publisher on PyPI for this repository, workflow
|
|
118
|
+
`ci.yml`, environment `pypi`. With the secret set it is used; without it the workflow falls back to trusted
|
|
119
|
+
publishing.
|
|
120
|
+
|
|
121
|
+
## Status
|
|
122
|
+
|
|
123
|
+
Alpha. Not included: synchronization with a forge (GitLab/GitHub issues). It originated in the Embersail
|
|
124
|
+
project's task tooling.
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "taskgate"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Gated task registry with an MCP server: strict status flow, verification and review evidence, for Claude Code, Codex, VS Code Copilot and other MCP clients."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
authors = [{ name = "prachwal" }]
|
|
14
|
+
keywords = ["mcp", "model-context-protocol", "task-tracker", "agents", "workflow", "claude-code", "codex", "copilot"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
25
|
+
]
|
|
26
|
+
dependencies = []
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = ["pytest>=8", "ruff>=0.6", "mypy>=1.11"]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/prachwal/taskgate"
|
|
33
|
+
Repository = "https://github.com/prachwal/taskgate"
|
|
34
|
+
Issues = "https://github.com/prachwal/taskgate/issues"
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
taskgate = "taskgate.cli:run"
|
|
38
|
+
taskgate-mcp = "taskgate.mcp.server:run"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
testpaths = ["tests"]
|
|
45
|
+
addopts = "-q"
|
|
46
|
+
|
|
47
|
+
[tool.ruff]
|
|
48
|
+
line-length = 130
|
|
49
|
+
target-version = "py311"
|
|
50
|
+
|
|
51
|
+
[tool.ruff.lint]
|
|
52
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
53
|
+
|
|
54
|
+
[tool.mypy]
|
|
55
|
+
python_version = "3.11"
|
|
56
|
+
strict = true
|
|
57
|
+
files = ["src"]
|
taskgate-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""CLI entrypoint: ``taskgate`` / ``python -m taskgate``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from . import __version__, clients, model
|
|
9
|
+
from .commands.flow import cmd_add, cmd_claim, cmd_list, cmd_next, cmd_update
|
|
10
|
+
from .commands.plan import cmd_depends, cmd_move
|
|
11
|
+
from .commands.registry import cmd_init, cmd_render, cmd_sort, cmd_validate
|
|
12
|
+
|
|
13
|
+
BY_HELP = "identity (default TASKGATE_BY env / 'human')"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _cmd_snippet(args: argparse.Namespace) -> int:
|
|
17
|
+
try:
|
|
18
|
+
print(clients.snippet(args.client, source=args.source, root=args.root), end="")
|
|
19
|
+
except ValueError as exc:
|
|
20
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
21
|
+
return 1
|
|
22
|
+
return 0
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _cmd_mcp(_args: argparse.Namespace) -> int:
|
|
26
|
+
from .mcp import server # lazy: the server imports this module's main()
|
|
27
|
+
|
|
28
|
+
server.serve()
|
|
29
|
+
return 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
33
|
+
"""Assemble the argparse tree."""
|
|
34
|
+
parser = argparse.ArgumentParser(
|
|
35
|
+
prog="taskgate",
|
|
36
|
+
description="Gated task registry: strict status flow with verification and review evidence.",
|
|
37
|
+
)
|
|
38
|
+
parser.add_argument("--version", action="version", version=f"taskgate {__version__}")
|
|
39
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
40
|
+
|
|
41
|
+
p = sub.add_parser("init", help="create the registry in the current directory (or TASKGATE_ROOT)")
|
|
42
|
+
p.add_argument("--seed", help="path to seed JSON (tasks array)")
|
|
43
|
+
p.add_argument("--force", action="store_true", help="overwrite existing registry")
|
|
44
|
+
p.add_argument("--by", help=BY_HELP)
|
|
45
|
+
p.set_defaults(func=cmd_init)
|
|
46
|
+
|
|
47
|
+
p = sub.add_parser("validate", help="schema + flow-rule validation")
|
|
48
|
+
p.set_defaults(func=cmd_validate)
|
|
49
|
+
|
|
50
|
+
p = sub.add_parser("render", help="generate the Markdown view")
|
|
51
|
+
p.add_argument("--check", action="store_true", help="fail when the view drifted from tasks.json")
|
|
52
|
+
p.add_argument("--out", help="alternative output path (ignored with --check)")
|
|
53
|
+
p.set_defaults(func=cmd_render)
|
|
54
|
+
|
|
55
|
+
p = sub.add_parser("next", help="show the one task that may start now (queue head)")
|
|
56
|
+
p.add_argument("--owner", help="only tasks unowned or owned by this identity")
|
|
57
|
+
p.set_defaults(func=cmd_next)
|
|
58
|
+
|
|
59
|
+
p = sub.add_parser("list", help="list all tasks")
|
|
60
|
+
p.add_argument("--status", choices=model.STATUSES, help="filter by status")
|
|
61
|
+
p.set_defaults(func=cmd_list)
|
|
62
|
+
|
|
63
|
+
p = sub.add_parser("claim", help="claim a planned task (-> in_progress)")
|
|
64
|
+
p.add_argument("id")
|
|
65
|
+
p.add_argument("--by", help=BY_HELP)
|
|
66
|
+
p.add_argument("--force", action="store_true", help="steal from another owner")
|
|
67
|
+
p.set_defaults(func=cmd_claim)
|
|
68
|
+
|
|
69
|
+
p = sub.add_parser("update", help="change status / record verification or review")
|
|
70
|
+
p.add_argument("id")
|
|
71
|
+
p.add_argument("--status", choices=model.STATUSES)
|
|
72
|
+
p.add_argument("--reason", help="required by blocked; recorded for cancelled")
|
|
73
|
+
p.add_argument("--verification-result", help="JSON object, e.g. '{\"ok\": true}'")
|
|
74
|
+
p.add_argument(
|
|
75
|
+
"--review-result",
|
|
76
|
+
help="JSON object from the reviewer, e.g. '{\"ok\": true, \"by\": \"reviewer\", \"checks\": [\"cmd\"]}'",
|
|
77
|
+
)
|
|
78
|
+
p.add_argument("--by", help=BY_HELP)
|
|
79
|
+
p.set_defaults(func=cmd_update)
|
|
80
|
+
|
|
81
|
+
p = sub.add_parser("add", help="append a new task to the registry")
|
|
82
|
+
p.add_argument("--id", required=True, help="id matching the configured id_pattern")
|
|
83
|
+
p.add_argument("--title", required=True)
|
|
84
|
+
p.add_argument("--phase", type=int, help="phase (default: from the id's 'phase' group, else 0)")
|
|
85
|
+
p.add_argument("--verification-cmd", required=True, dest="verification_cmd")
|
|
86
|
+
p.add_argument("--spec", help="spec_source anchor (path + section)")
|
|
87
|
+
p.add_argument("--status", default="planned", choices=model.ADD_STATUSES)
|
|
88
|
+
p.add_argument("--owner")
|
|
89
|
+
p.add_argument("--depends", help="comma-separated task ids")
|
|
90
|
+
p.add_argument("--by", help=BY_HELP)
|
|
91
|
+
p.set_defaults(func=cmd_add)
|
|
92
|
+
|
|
93
|
+
p = sub.add_parser("sort", help="reset the execution order to phase, then id")
|
|
94
|
+
p.add_argument("--by", help=BY_HELP)
|
|
95
|
+
p.set_defaults(func=cmd_sort)
|
|
96
|
+
|
|
97
|
+
p = sub.add_parser("move", help="place a draft/planned task before or after another")
|
|
98
|
+
p.add_argument("id")
|
|
99
|
+
anchor = p.add_mutually_exclusive_group(required=True)
|
|
100
|
+
anchor.add_argument("--before", metavar="ID", help="place directly before this task")
|
|
101
|
+
anchor.add_argument("--after", metavar="ID", help="place directly after this task")
|
|
102
|
+
p.add_argument("--by", help=BY_HELP)
|
|
103
|
+
p.set_defaults(func=cmd_move)
|
|
104
|
+
|
|
105
|
+
p = sub.add_parser("depends", help="change the dependencies of a draft/planned task")
|
|
106
|
+
p.add_argument("id")
|
|
107
|
+
p.add_argument("--set", help="comma-separated ids replacing the dependencies ('' clears)")
|
|
108
|
+
p.add_argument("--add", help="comma-separated ids to add")
|
|
109
|
+
p.add_argument("--remove", help="comma-separated ids to remove")
|
|
110
|
+
p.add_argument("--by", help=BY_HELP)
|
|
111
|
+
p.set_defaults(func=cmd_depends)
|
|
112
|
+
|
|
113
|
+
p = sub.add_parser("mcp", help="start the MCP stdio server (JSON-RPC 2.0, newline-delimited)")
|
|
114
|
+
p.set_defaults(func=_cmd_mcp)
|
|
115
|
+
|
|
116
|
+
p = sub.add_parser("config-snippet", help="print MCP configuration for a client")
|
|
117
|
+
p.add_argument("client", choices=list(clients.CLIENTS), help="; ".join(f"{k}: {v}" for k, v in clients.CLIENTS.items()))
|
|
118
|
+
p.add_argument("--from", dest="source", help="uvx source (path, git URL or version spec); default: PyPI")
|
|
119
|
+
p.add_argument("--root", help="absolute project root, written as TASKGATE_ROOT")
|
|
120
|
+
p.set_defaults(func=_cmd_snippet)
|
|
121
|
+
return parser
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def main(argv: list[str] | None = None) -> int:
|
|
125
|
+
"""Program entry: dispatch commands, return the exit code."""
|
|
126
|
+
args = build_parser().parse_args(argv)
|
|
127
|
+
code: int = args.func(args)
|
|
128
|
+
return code
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def run() -> None:
|
|
132
|
+
"""Console-script entry point."""
|
|
133
|
+
sys.exit(main())
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Ready-to-paste MCP server configuration for common clients (``taskgate config-snippet``)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
CLIENTS = {
|
|
8
|
+
"claude": "Claude Code: .mcp.json in the project (or `claude mcp add`)",
|
|
9
|
+
"vscode": "VS Code (GitHub Copilot agent mode): .vscode/mcp.json",
|
|
10
|
+
"codex": "OpenAI Codex CLI: ~/.codex/config.toml",
|
|
11
|
+
"cursor": "Cursor: .cursor/mcp.json",
|
|
12
|
+
"gemini": "Gemini CLI: .gemini/settings.json",
|
|
13
|
+
"generic": "any client that takes a command and arguments",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def launch(source: str | None) -> tuple[str, list[str]]:
|
|
18
|
+
"""Command and arguments that start the stdio server through ``uvx``."""
|
|
19
|
+
args = ["--from", source, "taskgate", "mcp"] if source else ["taskgate", "mcp"]
|
|
20
|
+
return "uvx", args
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def snippet(client: str, *, source: str | None = None, root: str | None = None) -> str:
|
|
24
|
+
"""The configuration text for ``client``; raises ``ValueError`` for an unknown client."""
|
|
25
|
+
if client not in CLIENTS:
|
|
26
|
+
raise ValueError(f"unknown client {client!r}; choose one of {', '.join(CLIENTS)}")
|
|
27
|
+
command, args = launch(source)
|
|
28
|
+
env = {"TASKGATE_ROOT": root} if root else {}
|
|
29
|
+
if client == "codex":
|
|
30
|
+
lines = ["[mcp_servers.taskgate]", f"command = {json.dumps(command)}", f"args = {json.dumps(args)}"]
|
|
31
|
+
if env:
|
|
32
|
+
lines += ["", "[mcp_servers.taskgate.env]", f"TASKGATE_ROOT = {json.dumps(root)}"]
|
|
33
|
+
return "\n".join(lines) + "\n"
|
|
34
|
+
if client == "claude":
|
|
35
|
+
add = ["claude", "mcp", "add", "taskgate"]
|
|
36
|
+
if root:
|
|
37
|
+
add += ["-e", f"TASKGATE_ROOT={root}"]
|
|
38
|
+
add += ["--", command, *args]
|
|
39
|
+
entry: dict[str, object] = {"type": "stdio", "command": command, "args": args}
|
|
40
|
+
if env:
|
|
41
|
+
entry["env"] = env
|
|
42
|
+
text = json.dumps({"mcpServers": {"taskgate": entry}}, indent=2)
|
|
43
|
+
return f"# one command:\n{' '.join(add)}\n\n# or .mcp.json:\n{text}\n"
|
|
44
|
+
if client == "vscode":
|
|
45
|
+
entry = {"type": "stdio", "command": command, "args": args}
|
|
46
|
+
if env:
|
|
47
|
+
entry["env"] = env
|
|
48
|
+
return json.dumps({"servers": {"taskgate": entry}}, indent=2) + "\n"
|
|
49
|
+
entry = {"command": command, "args": args}
|
|
50
|
+
if env:
|
|
51
|
+
entry["env"] = env
|
|
52
|
+
return json.dumps({"mcpServers": {"taskgate": entry}}, indent=2) + "\n"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI commands."""
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Helpers shared by the CLI commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
EMPTY_REGISTRY: dict[str, Any] = {"version": 1, "tasks": []}
|
|
10
|
+
|
|
11
|
+
VERIFICATION_STATUSES = ("in_review", "done")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def identity(args: argparse.Namespace) -> str:
|
|
15
|
+
"""Declared caller identity: ``--by``, else ``TASKGATE_BY``, else ``human``."""
|
|
16
|
+
by: str | None = getattr(args, "by", None)
|
|
17
|
+
return by or os.environ.get("TASKGATE_BY", "human")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def find(data: dict[str, Any], task_id: str) -> tuple[dict[str, Any], bool]:
|
|
21
|
+
"""The task with this id and whether it exists."""
|
|
22
|
+
for task in data.get("tasks", []):
|
|
23
|
+
if task.get("id") == task_id:
|
|
24
|
+
return task, True
|
|
25
|
+
return {}, False
|