feat(kanban): promote verb for manual todo->ready recovery (salvage #29464 + bulk --ids)#31334
Merged
Conversation
Adds `hermes kanban promote <task_id>` for manual lifecycle recovery when an auto-promote daemon misses the parent-done transition (issue #28822). Refuses promotion unless every parent dep is done/archived (override with --force). Emits a `promoted_manual` audit event distinct from the automatic `promoted` kind, so audit consumers can filter human-driven from system-driven promotions. Supports --dry-run and --json for orchestration. Does not mutate assignee/claim state — the dispatcher picks the card up via its normal ready polling path. Closes #28822.
Adds an --ids flag to 'hermes kanban promote' mirroring the existing block/schedule convention, so the marquee use case from issue #28822 (promote all children of a closed organizational parent in one shot) doesn't require a shell loop. Single-id JSON output stays a flat object for back-compat; bulk emits a list. Dedupes positional + --ids so the same id can't be promoted twice in one call. 5 new CLI-level tests cover bulk happy path, partial-failure exit code, JSON shapes, and dedup. Also adds the thedavidmurray noreply-email -> github-login mapping in scripts/release.py so the salvage cherry-pick passes the AUTHOR_MAP contributor-credit check.
Contributor
🔎 Lint report:
|
| Rule | Count |
|---|---|
unsupported-operator |
2 |
unresolved-attribute |
2 |
unresolved-import |
1 |
First entries
tests/hermes_cli/test_kanban_promote.py:143: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["'ready'"]` and `str | None`
tests/hermes_cli/test_kanban_promote.py:15: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/hermes_cli/test_kanban_promote.py:143: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["promote only applies"]` and `str | None`
tests/hermes_cli/test_kanban_promote.py:210: [unresolved-attribute] unresolved-attribute: Attribute `status` is not defined on `None` in union `Task | None`
tests/hermes_cli/test_kanban_promote.py:114: [unresolved-attribute] unresolved-attribute: Attribute `assignee` is not defined on `None` in union `Task | None`
✅ Fixed issues: none
Unchanged: 4816 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
This was referenced May 24, 2026
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
hermes kanban promote <task_id> [--ids ...]so operators can recover children stuck intodo/blockedafter their parent closed outside the auto-promote path — without raw SQL.Salvages @thedavidmurray's #29464 onto current main (169 commits behind, clean cherry-pick), and extends it with the bulk
--idsflag that mirrors the existingblock/scheduleconvention. The marquee #28822 use case is "promote all children of a closed organizational parent in one command," so bulk is the value-add that turns a recovery primitive into an actual one-shot fix.Closes #28822. Supersedes #29464.
Behavior
done/archived(override with--force)promoted_manualaudit rows with{actor, reason, forced}— distinct from the auto-firedpromotedkind thatrecompute_readywritesassignee— dispatcher picks the card up via normalreadypolling--idsadds bulk mode (same convention asblock --ids/schedule --ids)task_id+--idsare deduped so the same id can't be promoted twice--dry-runvalidates without mutating;--jsonfor orchestration (single-id stays a flat object for back-compat, bulk emits a list)Changes
hermes_cli/kanban_db.py— newpromote_task()(from feat(cli): kanban promote verb for manual todo->ready recovery #29464, unchanged)hermes_cli/kanban.py—promotesubparser +_cmd_promotehandler, extended with--idsbulk loop and JSON list outputtests/hermes_cli/test_kanban_promote.py— 11 library-level tests from feat(cli): kanban promote verb for manual todo->ready recovery #29464 + 5 new CLI bulk tests (bulk happy path, partial-failure exit code, JSON list shape, single-id back-compat, dedup)scripts/release.py—AUTHOR_MAPentry for thedavidmurrayValidation
hermes kanban promote <id> --forcepromote a --ids b c d --forcepromoted_manualevent with actor/reason/forcedE2E live test (isolated
HERMES_HOME):hermes kanban promote child0 --ids child1 child2 --json→ all 3 ready, none assigned, 3promoted_manualaudit events recorded--dry-runon unsatisfied dep correctly reports refusal in JSON without mutating--forcecorrectly overridesAuthorship
--idsbulk + CLI tests + AUTHOR_MAP: this PROut of scope
Arbitrary
--from/--totransitions. Issue mentioned them but every described use case istodo/blocked→ready; a general state-machine verb would expand the review surface without solving any reported gap.Infographic