# 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.