dwarvesf / dwarvesf/dwarves-kit
board promote: consumer shim's --backlog-file breaks numeric selection and silently retargets the wrong repo
- Dominant language
- Shell
- Stars
- 11
- Forks
- 2
- Avg merge
- 42m
- Merged PRs (30d)
- 247
Description
## Summary
`board promote ` is broken for every consumer using the shim `board init` scaffolds. Worse, the `--backlog-file` that shim passes is silently discarded, so `promote` can write to a **different repo's** BACKLOG.md than the one the operator addressed.
`promote list` and `promote all` work, `promote ` and `promote reject ` do not — which quietly degrades the human review gate into all-or-nothing.
## Root cause
Two independent defects that compound.
**1. Argv is forwarded verbatim, so the flag reaches an int() parse.**
`lib/board/board.sh:948` forwards everything, unlike its siblings:
```bash
promote) shift; exec "$BOARD_DIR/bin/add-backlog" "$@" ;;
```
The shim `cmd_init` scaffolds (`lib/board/board.sh`, the `SHIM` heredoc) always appends the flag:
```bash
exec bash "${DWARVES_KIT:-$HOME/.claude/dwarves-kit}/bin/board" "${@:-board}" --backlog-file "$here/BACKLOG.md"
```
So `board promote 1` arrives at `add-backlog` as `1 --backlog-file /path/to/_meta/BACKLOG.md`, and `lib/board/bin/add-backlog:141` does:
```python
picks = sorted(set(int(x) for x in sel)) # sel includes "--backlog-file" and the path
```
`int("--backlog-file")` raises `ValueError` → usage message → exit 2.
`list` returns at line 126 before that parse, and `all` short-circuits at line 137, which is why exactly those two survive.
**2. `add-backlog` has no concept of `--backlog-file` at all.**
`grep -c backlog-file lib/board/bin/add-backlog` → `0`. It resolves its targets from env with a `_repo_root()` fallback (lines 46-47):
```python
BACKLOG = os.environ.get("BACKLOG_STAGE_BACKLOG", os.path.join(_ROOT, "_meta/BACKLOG.md"))
STAGING = os.environ.get("BACKLOG_STAGE_STAGING", os.path.join(_ROOT, "_meta/backlog-staging.md"))
```
So even once the arg parse is fixed, the operator's explicit target is ignored and **cwd wins**.
`cmd_sync` already documents this exact hazard and handles it (`lib/board/board.sh:735-737`): *"Consumer shims append `--backlog-file`; translate it to the engine's `--backlog`."* `promote` never got that treatment.
## Reproduction
Two throwaway repos, each with the scaffolded shim and one staged candidate.
```
$ bash repoA/_meta/board promote list
1. Repro candidate [#u-lo #f-lo]
$ bash repoA/_meta/board promote 1
usage: board promote [list | ... | all | reject ...] # exit 2
$ bash repoA/_meta/board promote all
promoted ID-001: Repro candidate # works
```
Cross-repo, the serious one — run **repoA's** shim while cwd is repoB:
```
$ cd repoB && bash /abs/path/repoA/_meta/board promote all
promoted ID-001: repoB own candidate
$ grep -c '^| ID-' repoA/_meta/BACKLOG.md # 1 (unchanged, pre-existing row)
$ grep -c '^| ID-' repoB/_meta/BACKLOG.md # 1 (repoB's candidate landed here)
```
repoA's staged candidate was untouched; repoB's was promoted into repoB. The `--backlog-file` naming repoA was discarded without a warning.
## Why this matters beyond the usage error
The kit's stated purpose includes a cross-repo cockpit (`boards.txt`, `board all`, `board mirror`). A promote that resolves its write target from cwd rather than the flag it was handed can silently act on the wrong board in exactly the multi-repo setup the tool is for.
And `promote` is documented as *"the human gate"*. With numeric selection broken, the only working promote path is `all` — an operator who wanted to promote 2 of 5 candidates has no working way to say so, and the obvious workaround promotes the three they meant to leave staged.
## Suggested fix
Give `promote` the same translation `sync` already has: parse `--backlog-file` out of argv in `board.sh`, export `BACKLOG_STAGE_BACKLOG` and derive `BACKLOG_STAGE_STAGING` as its sibling, then exec `add-backlog` with the remaining args. Defence in depth: have `add-backlog` reject unrecognised flags with a clear message instead of feeding them to `int()`.
## Environment
- kit at `8eeb122` (`docs: close SPEC-231 audit gaps (changelog, label, backrefs) (#399)`)
- macOS / bash 3.2, python3
- Found while promoting real work onto a consumer cockpit; worked around by calling the engine directly with `BACKLOG_STAGE_BACKLOG` / `BACKLOG_STAGE_STAGING` set.
Contributor guide
Research direction
Start in lib/board/board.sh at the promote dispatch and compare its argument handling with cmd_sync around lines 735-737, then inspect lib/board/bin/add-backlog around lines 46-47 and 126-141. Reproduce the numeric and cross-repo cases, then verify that promote selections and reject honor the shim's --backlog-file target and that unexpected flags produce a clear error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, shell
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100