chore: make work-item a reusable subproject
This commit is contained in:
227
work_item/README.md
Normal file
227
work_item/README.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# 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.
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user