Files

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, 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:

US01-01 — Short imperative title

The two numeric groups define epic and story order. The helper recognizes these status labels:

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:

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.

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:

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

[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

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:

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/backlogstatus/in-progressstatus/reviewstatus/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.:

/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:

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.