spec-kitty / spec-kitty/spec-kitty
The requirement-extraction advisory is invisible in `spec-kitty next --json` — the documented agent flow
- Dominant language
- Python
- Stars
- 1.6k
- Forks
- 165
- Avg merge
- 14h 52m
- Merged PRs (30d)
- 303
Description
Found by an adversarial review squad during the landing pass on #3395, and confirmed by the implementer who assessed folding it. Deliberately **not** folded — it needs a pinned-contract change, which a landing pass should not make.
Parent epic: #2720 (mission-state discovery & diagnostic-command output fidelity).
## The defect
#3395 added a non-blocking `requirement_extraction_warnings` advisory: it tells an operator that `spec.md` contains requirement-shaped tokens matching none of the recognised declaring shapes, so those requirements are invisible to coverage mapping. On the `spec-kitty next` readiness path the advisory is emitted with `logger.warning`, which is its only channel.
`src/specify_cli/__init__.py:376` installs JSON-mode logging that raises every root handler to `CRITICAL+1` and attaches a `NullHandler` so `lastResort` cannot fire (`logging_bootstrap.py:148-160`).
Measured:
```
json_mode=False stderr = "WARNING [my-mission] "
json_mode=True stderr = ""
```
`docs/guides/how-to/governance/run-governed-mission.md:40` documents the flow as:
```
spec-kitty next --agent claude --mission --json
```
So in the documented agent flow the advisory produces nothing at all, and it is not carried in the decision payload either — `requirement_extraction_warnings` appears nowhere in `runtime_bridge_io.py`. The `spec-kitty next` leg of #3395 is inert for its primary consumer.
The two CLI surfaces (`finalize-tasks`, `map-requirements`) are unaffected — they carry the field in JSON, including on the blocked-operator failure paths as of the #3395 landing folds.
## Why it was not folded into #3395
`Decision` (`src/runtime/next/decision.py`) is explicitly documented as the public JSON contract, with a hand-maintained `to_dict()` and no advisory channel — its only list is `guard_failures`, which is hard-failure-only and must not gain soft entries. Surfacing the advisory means:
1. a new `requirement_extraction_warnings: list[str]` field on `Decision` and `to_dict()`;
2. threading it through `DecisionEnvelope` in `runtime_bridge_cores.py`;
3. the print path in `next_cmd.py`;
4. classifying the new field in the parity-oracle STABLE-field ledger (`tests/runtime/_bridge_oracle.py`).
That is a pinned-contract change across four files. The landing runbook folds squad findings by default but carves out changes needing their own design pass; this is one.
## Acceptance
- `spec-kitty next --json` carries the advisory as plain data, distinct from `guard_failures`, and it never influences a gate verdict or exit code.
- The parity oracle classifies the new field explicitly rather than inheriting a default.
- A test asserts the advisory survives `--json` (the current gap: the only coverage is human-mode logging).
Contributor guide
Research direction
Read src/runtime/next/decision.py and its to_dict(), then trace DecisionEnvelope in runtime_bridge_cores.py and the JSON print path in next_cmd.py. Check tests/runtime/_bridge_oracle.py and the documented next --json flow in docs/guides/how-to/governance/run-governed-mission.md. Done means the advisory appears as separate JSON data, does not affect verdicts or exit codes, is classified by the oracle, and is covered by a test.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 52/100