Hephaestus Skills/ Docs / feature-status GitHub

Docs / Skills / feature-status

feature-status

Core/feature-statusUserPromptSubmitPreToolUseStop / StopFailure

Use before starting work on a ticket or feature in a repo that has a docs/status/ folder or whose AGENTS.md names feature-status, when an AC is verified, the spec changes, work gets blocked, before opening a PR/MR, after a merge, and when asked "nhận task", "pick ticket", "cập nhật status", "status feature", "tiến độ feature", "board tiến độ", "ai đang làm ticket này", "feature status board".

In a repo with docs/status/: claim the ticket on the remote before the worktree, record each AC tier as it passes, set in-review before the PR; the guard hook keeps status files script-only and ticket branches claimed

Also in this skill: references/format.md scripts/status.py scripts/test_status.sh

Many agents work one repo in parallel worktrees, so "who is on this ticket, and what is really verified" needs one answer every checkout can see. Each feature gets its own status file docs/status/<ID>.md, claimed on the remote before any worktree exists and edited only through one script.

  • status.py means python3 <this skill>/scripts/status.py; the feature-status-guard hook's deny messages name the exact path on this machine.
  • Every subcommand has --help, and every writing one takes --dry-run.
  • references/format.md owns the file format, the tokens, and the legal state transitions.
  • <ID> is the ticket id (WEST-123), or a kebab slug when there is no ticket; <int> is the integration branch from worktree-flow section 0.

0. Is the repo opted in?

git fetch origin --prune
git ls-tree -d --name-only origin/<int> docs/status
git show origin/<int>:AGENTS.md 2>/dev/null | grep -c feature-status
  • The first prints docs/status, or the second prints a number above 0: the repo is opted in, and the feature-status-guard hook enforces the steps below.
  • Neither: this skill does not apply; follow worktree-flow alone, and offer the opt-in from project-init.

1. Pick before any worktree exists

Who picks:

  • Ticket work always picks, with the ticket id as <ID>.
  • A no-ticket feature that other agents could collide on picks too, with a kebab slug as <ID>.
  • A small no-ticket change (a typo, a one-file fix) may skip pick and follow worktree-flow alone on a <type>/<slug> branch. The hook judges a slug branch only once a claim/<ID> exists for it.
  • The hook recognizes only ABC-123 ticket ids (hooks/README.md); ticket work under any other id format (west-12, a numeric issue) still picks, by rule rather than by hook.

Run from the checkout you stand in, usually the main checkout; pick never touches its working tree or index:

status.py pick <ID> --title "<title>" --type <feat|fix|refactor|chore|docs|test> --slug <slug>
  • It claims the ID on the remote (claim/<ID> and the feature branch, pushed atomically, so every other checkout sees it at once), creates the worktree, and prints its path as the last line; cd there.
  • It replaces worktree-flow section 1 steps 1-3, existing-work checks included; on --resume still read the worktree's progress file (worktree-flow section 3).
  • Refused because someone else holds it: the message names who; stop and ask, never pick a second branch for the same ID.
  • Refused because the claim names your git identity: parallel agents share one identity, so that alone does not make it yours. Rerun with --resume: it prints the existing worktree path, recreates it when missing locally, and refuses when that worktree changed in the last 30 minutes, naming who holds it and where. On that refusal, ask the user whether the other session is still running; add --takeover only after the user confirms it is dead.
  • Refused because the claim is stale (its pick is merged, dropped or deferred): run the status.py release <ID> the message names, then pick again; never --resume a stale claim.
  • A harness already started you in a clean detached worktree: add --no-worktree, then git switch -c <branch> --track origin/<branch> in place.

2. Update as you go, inside the feature worktree

WhenCommand
Work starts (exploration done, at the latest with the first commit)status.py set <ID> in-progress
ACs are known`status.py ac <ID> AC<n> --text "<AC text>" --tier <ut\it\e2e> --result todo, once per tier the AC needs, and --result na` for tiers it does not
A tier of an AC passes or fails`status.py ac <ID> AC<n> --tier <tier> --result <pass\fail>`
An E2E passstatus.py ac <ID> AC<n> --tier e2e --result pass --evidence <e2e-verify JSON report>; only that report is accepted (references/format.md), and no flag skips it
The spec changes under verified ACs (before the merge)status.py stale <ID> --acs AC1,AC3 --note "<what changed, link>", then status.py ac <ID> AC3 --text "<new AC text>" for each reworded AC (this resets all its tiers to todo, so record each tier again, na included), add new ACs as above, and re-verify
Something is cut from scopestatus.py defer <ID> --what "<what>" --why "<why>" --owner "<who>"
Blockedstatus.py set <ID> blocked --note "<one line, link>", and open a draft PR/MR per worktree-flow
A fact worth keepingstatus.py note <ID> "<one line, link>"; longer reasoning goes in the PR/MR body or a flow-trace file
  • Commit each status change with the code it describes, so the file on the branch is always true for that commit.
  • set, ac, stale, defer, and note refuse to run in the main checkout.

3. PR/MR, merge, release

The merged-state rule: the PR/MR opens in in-review, and its last push before merging sets merged, so the file on <int> is true the moment it lands, and a PR/MR closed unmerged never reaches <int>.

  1. Before a non-draft PR/MR, in this order: status.py set <ID> in-review, status.py check <ID>, commit, push, status.py check <ID> --ref HEAD, then open it. The last check reads the committed file, so it fails when the state change or an E2E evidence file is not committed; the hook runs it too, and denies while the branch has unpushed commits. A draft PR/MR only needs the file to exist.
  2. When the user approves the merge (or asks you to merge): status.py set <ID> merged --pr <url>, commit, push, and only then merge. check refuses merged while any AC tier is todo, fail, or stale, or an E2E pass lacks its evidence; fix those, do not argue with it.
  3. After the merge: status.py release <ID> deletes the remote claim. It judges only that pick's own file: merged must be on origin/<int> (so it cannot run while the PR/MR is open), and an earlier pick's merged file never releases a re-pick.
  4. Dropped: status.py set <ID> dropped --note "<why>", commit, push, status.py release <ID>, then merge or close per the user. Deferring the whole feature: the same with set <ID> deferred --note "<why, link>", so anyone can pick it again later. The old worktree can no longer reopen it after the release; resuming means a new pick <ID> --slug <new-slug>, which keeps the deferred file's ACs, and merging the given-up branch in when its work is still needed.
  5. Failed QA or a spec change after merge has one path: status.py release <ID> (when the claim still stands), then status.py pick <ID> --slug <new-slug> from the main checkout (it keeps the ACs and resets the state to picked), then in the new worktree stale the affected ACs and set <ID> in-progress --note "<why, link>". stale and set refuse a merged file, so the old worktree cannot reopen it; --slug is needed only while the merged branch still exists, and pick says so.

Recovery, when the user merged the PR/MR while its file still said in-review (release and pick say so, and board --live shows in-review (PR merged)): in the feature worktree (recreate it with pick <ID> --resume while the claim stands), run git fetch origin && git merge origin/<int>, status.py set <ID> merged --pr <url>, commit, push, open a PR/MR, and release once it merges. Merging origin/<int> first makes the push a fast-forward, also after a squash merge.

4. Board

status.py board --live          # --out <file> writes it

It merges docs/status/*.md on <int> with every claim/<ID> branch, so unmerged work shows up; --live adds PR/MR and deploy state when gh/glab and the environments version status are available. Print it or write it to a scratch file; never commit it, since parallel PRs/MRs would conflict on it.

Never

  • Never edit docs/status/*.md by hand, with an edit tool or with shell (>, sed -i, tee, cp, ln, git checkout <ref> -- <file>, git restore --source); the hook denies the common forms on a best-effort basis, and the script is the only way edits stay surgical and validated.
  • Never create a branch or worktree for ticket work, or for an ID that has a claim, before pick; the hook denies it, and also denies a second branch for a claimed ID. "It is a tiny change" is not an exception for a ticket: a tiny change still collides with another agent on the same ticket.
  • Never record a pass you did not run, and never copy Jira or deploy state into the file; the board derives deploy state.
  • Never leave a claim/<ID> behind after merged, dropped, or deferred; pick refuses everyone while it exists.

Report

The status file's state, AC table, and deferred items; the PR/MR URL; and whether the claim was released.