259 lines
8.5 KiB
Markdown
259 lines
8.5 KiB
Markdown
# work-item
|
|
|
|
Safe, deterministic Git and Gitea workflow automation for a single agent working an
|
|
ordered user-story backlog.
|
|
|
|
`work-item` selects the first eligible story, creates its feature branch, runs the
|
|
configured automated tests, checks changes for unsafe content, pushes the commit,
|
|
opens a pull request, and closes the issue only after the merge is verified.
|
|
|
|
## Highlights
|
|
|
|
- deterministic numeric story selection with dependency checks;
|
|
- one active story at a time, with resume-safe local state;
|
|
- idempotent resume after blocked work or partially completed remote operations;
|
|
- mandatory automated tests before commit and push;
|
|
- configurable file, size, secret, and private-data safety checks;
|
|
- pull-request creation, optional CI gating, merge verification, and issue closure;
|
|
- TOML, YAML, dotenv, and process-environment configuration;
|
|
- no workflow state committed to the repository.
|
|
|
|
## Requirements
|
|
|
|
- Python 3.12 or newer;
|
|
- Git;
|
|
- [`tea`](https://gitea.com/gitea/tea), authenticated against the target Gitea
|
|
instance;
|
|
- PyYAML 6 or newer when using `.yml` or `.yaml` configuration.
|
|
|
|
The repository must use story titles in this form:
|
|
|
|
```text
|
|
US01-01 — Short imperative title
|
|
```
|
|
|
|
The two numeric groups define epic and story order. The helper recognizes these
|
|
status labels:
|
|
|
|
```text
|
|
status/backlog status/ready status/in-progress
|
|
status/review status/blocked status/done
|
|
```
|
|
|
|
Dependencies must be configured through Gitea's issue-dependency feature.
|
|
|
|
## Installation
|
|
|
|
The directory is a self-contained Python subproject. From the host repository root:
|
|
|
|
```bash
|
|
python -m pip install -e work_item
|
|
work-item --help
|
|
```
|
|
|
|
For a repository-local installation, copy the complete `work_item/` directory into the
|
|
target repository and run `work_item/scripts/work-item`. Its Python wrapper adds the
|
|
bundled `src/` directory to `PYTHONPATH`, prefers a compatible Conda base interpreter,
|
|
and otherwise uses `python3.13` or `python3.12`.
|
|
|
|
```text
|
|
work_item/
|
|
├── .work-item.yml # host-project configuration
|
|
├── README.md
|
|
├── pyproject.toml # independently installable package
|
|
├── scripts/ # repository-local launchers
|
|
├── src/work_item/ # implementation
|
|
└── tests/ # unit and fake-Gitea end-to-end tests
|
|
```
|
|
|
|
## Quick start
|
|
|
|
Create one supported configuration file either in the repository root or inside the
|
|
`work_item/` directory. The repository root takes precedence; within each location,
|
|
automatic discovery uses this order:
|
|
|
|
1. `.work-item.toml`
|
|
2. `.work-item.yml`
|
|
3. `.work-item.yaml`
|
|
4. `.work-item.env`
|
|
|
|
Then run:
|
|
|
|
```bash
|
|
work_item/scripts/work-item next
|
|
work_item/scripts/work-item claim
|
|
|
|
# Implement the claimed story and its tests.
|
|
work_item/scripts/work-item status
|
|
work_item/scripts/work-item submit --test "pytest -q"
|
|
|
|
# Review the displayed diff, then authorize Git/PR creation.
|
|
work_item/scripts/work-item submit --test "pytest -q" --yes
|
|
|
|
# After review and successful CI:
|
|
work_item/scripts/work-item complete --merge
|
|
```
|
|
|
|
When installed, the shorter `work-item` command is equivalent. Use a non-default file
|
|
explicitly with `work-item --config path/to/config.yml next`.
|
|
|
|
## Configuration
|
|
|
|
### TOML
|
|
|
|
```toml
|
|
[repository]
|
|
slug = "owner/project"
|
|
login = "my-tea-login"
|
|
assignee = "agent-user"
|
|
remote = "origin"
|
|
main_branch = "main"
|
|
|
|
[workflow]
|
|
branch_prefix = "us"
|
|
require_ci = true
|
|
required_tests = ["pytest -q", "ruff check ."]
|
|
|
|
[safety]
|
|
max_file_bytes = 5000000
|
|
allow = ["tests/fixtures/**"]
|
|
deny = ["*.env", "*.pem", "data/**", "photos/**", "*.jpg"]
|
|
```
|
|
|
|
### YAML
|
|
|
|
```yaml
|
|
repository:
|
|
slug: owner/project
|
|
login: my-tea-login
|
|
assignee: agent-user
|
|
remote: origin
|
|
main_branch: main
|
|
|
|
workflow:
|
|
branch_prefix: us
|
|
require_ci: true
|
|
required_tests:
|
|
- pytest -q
|
|
- ruff check .
|
|
|
|
safety:
|
|
max_file_bytes: 5000000
|
|
allow:
|
|
- tests/fixtures/**
|
|
deny:
|
|
- "*.env"
|
|
- "*.pem"
|
|
- data/**
|
|
- photos/**
|
|
```
|
|
|
|
### dotenv
|
|
|
|
Lists use JSON arrays so test commands and glob patterns remain unambiguous:
|
|
|
|
```dotenv
|
|
WORK_ITEM_REPO_SLUG=owner/project
|
|
WORK_ITEM_LOGIN=my-tea-login
|
|
WORK_ITEM_ASSIGNEE=agent-user
|
|
WORK_ITEM_REMOTE=origin
|
|
WORK_ITEM_MAIN_BRANCH=main
|
|
WORK_ITEM_BRANCH_PREFIX=us
|
|
WORK_ITEM_REQUIRE_CI=true
|
|
WORK_ITEM_REQUIRED_TESTS='["pytest -q", "ruff check ."]'
|
|
WORK_ITEM_MAX_FILE_BYTES=5000000
|
|
WORK_ITEM_ALLOW='[".work-item.env", "tests/fixtures/**"]'
|
|
WORK_ITEM_DENY='["*.env", "*.pem", "data/**", "photos/**"]'
|
|
```
|
|
|
|
The same `WORK_ITEM_*` variables may be exported by the calling process. Process
|
|
environment values override values from any configuration file, which is useful for
|
|
CI or per-machine `tea` login and assignee settings.
|
|
|
|
Only repository slug, login, and assignee are required. Other fields use the defaults
|
|
shown above.
|
|
|
|
## Commands
|
|
|
|
| Command | Behavior |
|
|
|---|---|
|
|
| `next` | Shows the lowest-numbered unassigned story whose dependencies are closed. |
|
|
| `claim` | Claims the next story, or safely resumes the existing in-progress/blocked story. |
|
|
| `status` | Shows local workflow state, current branch, and changed files. |
|
|
| `submit` | Runs tests and safety checks; without `--yes`, stops after the review summary. |
|
|
| `complete` | Verifies CI/merge state, closes the issue, updates main, and removes the branch. |
|
|
| `block` | Records a precise blocking reason without discarding the branch or local work. |
|
|
|
|
`--test` is repeatable. Configured `required_tests` are always included, and duplicate
|
|
commands are run only once.
|
|
|
|
## Kanban / project board position
|
|
|
|
`work-item` tracks each story's stage with the `status/*` **labels**
|
|
(`status/backlog` → `status/in-progress` → `status/review` → `status/done`, plus
|
|
`status/blocked`) and by opening/closing the issue. **These labels are the board.**
|
|
|
|
It does **not** move cards between columns of a Gitea *project* (Kanban) board, and
|
|
this is a Gitea limitation rather than a gap in the helper:
|
|
|
|
- Gitea's REST API (checked against 1.25) exposes **no** project/column/board
|
|
endpoints — the swagger spec has none, and `tea` has no project command.
|
|
- Card movement uses Gitea's internal *web* endpoints, which require a logged-in
|
|
browser session (session cookie + CSRF). An **API token authenticates only
|
|
`/api/*`, not web routes** (a token request to a web page redirects to
|
|
`/user/login`), so the helper — which is token-only by design — cannot reach them.
|
|
- Gitea does not auto-move a closed issue into a "Done" column either. A closed story
|
|
keeps whichever column it was in.
|
|
|
|
**Recommended: use the labels as the board.** Filter the issue list by label to get an
|
|
equivalent view without touching the unreachable board, e.g.:
|
|
|
|
```text
|
|
/domverse/photoanalyzer/issues?labels=status/in-progress
|
|
/domverse/photoanalyzer/issues?labels=status/review
|
|
/domverse/photoanalyzer/issues?q=&type=all&labels=status/done&state=closed
|
|
```
|
|
|
|
If a project board must show card positions, move the cards manually in the web UI, or
|
|
drive the web endpoints from a real browser session (out of scope for this token-only
|
|
helper). Do not store web login credentials in the helper to work around this.
|
|
|
|
## Safety model
|
|
|
|
Before submission, the helper:
|
|
|
|
1. enumerates tracked and untracked changes;
|
|
2. rejects denied paths unless an allow pattern explicitly overrides them;
|
|
3. rejects files larger than `max_file_bytes`;
|
|
4. scans readable text for common credentials, authorization headers, and private keys;
|
|
5. runs `git diff --check` before staging and again against the staged diff;
|
|
6. runs every required and story-specific automated test;
|
|
7. requires a separate `--yes` invocation before commit and push.
|
|
|
|
Keep real media, databases, runtime data, credentials, and generated artifacts in the
|
|
deny list. Allow only deterministic, non-private test fixtures.
|
|
|
|
## Resume and recovery
|
|
|
|
State is stored at `.git/work-item-state.json`, so it is local and never committed.
|
|
|
|
- Re-running `claim` on the story branch preserves dirty implementation work.
|
|
- Re-running `claim` from another clean branch switches back to the claimed branch.
|
|
- Re-running `claim` after `block` returns the same story to `status/in-progress`.
|
|
- If push succeeds but PR creation or review labeling fails, `submit --yes` reuses the
|
|
pushed commit and existing PR instead of committing twice.
|
|
- A story in review cannot be replaced by a new claim; complete or block it first.
|
|
|
|
## Development
|
|
|
|
From this repository:
|
|
|
|
```bash
|
|
ruff check --no-cache work_item/src work_item/tests
|
|
ruff format --check --no-cache work_item/src work_item/tests
|
|
work_item/scripts/python -m unittest discover -s work_item/tests -v
|
|
```
|
|
|
|
The end-to-end tests use temporary Git repositories and a deterministic fake `tea`
|
|
binary. They do not mutate a live Gitea project.
|