renumber: STATE cursor on a decimal phase is silently shifted onto a nonexistent phase #37

Closed
opened 2026-07-23 14:21:43 +00:00 by crenshawdev · 0 comments
crenshawdev commented 2026-07-23 14:21:43 +00:00 (Migrated from github.com)

Severity: medium · Area: renumber · surfaced in the post-v1.2.0 cross-model review sweep (reproduced with a fixture). Sibling of #36.

What happens

renumber treats decimal insertion phases (e.g. 2.1) as never-shifted everywhere — the ROADMAP tokens (shiftPhaseTokens) and the phase directories both skip them, and they are reported back so the caller re-places them by hand. The STATE cursor update is the one place that carve-out was not applied. When your cursor sits on a decimal phase, renumber applies the integer delta to it anyway, moving the cursor onto a phase number that exists in neither the ROADMAP nor the directory tree — and it does so with no warning.

Mechanism (verified against source)

  • cadence-core/bin/planning.mjs:644: if (cursor.phase >= shiftFrom) newCursor.phase = cursor.phase + delta; applies the delta whether or not cursor.phase is an integer.
  • The existing warn (planning.mjs:645-646) only fires when cursor.phase === at (the exact removed integer phase); the decimal-desync case produces no warn in the output.

Reproduction

STATE cursor on phase 2.1, then renumber remove --n 1 (or insert --at ≤ 2):

  • STATE.md rewritten to Phase: 1.1 of 3.
  • ROADMAP and the directory still say 2.1.
  • The cursor now points at phase 1.1, which exists nowhere. No warning is emitted.

Fix direction

Apply the same decimal carve-out to the cursor: skip the shift when cursor.phase is not an integer, and emit a warn telling the caller to re-point it (mirroring the existing removed-phase warn). Pairs naturally with the fix for #36.

**Severity:** medium · **Area:** renumber · surfaced in the post-v1.2.0 cross-model review sweep (reproduced with a fixture). Sibling of #36. ## What happens `renumber` treats decimal insertion phases (e.g. `2.1`) as never-shifted everywhere — the ROADMAP tokens (`shiftPhaseTokens`) and the phase directories both skip them, and they are reported back so the caller re-places them by hand. The STATE cursor update is the one place that carve-out was not applied. When your cursor sits on a decimal phase, renumber applies the integer delta to it anyway, moving the cursor onto a phase number that exists in neither the ROADMAP nor the directory tree — and it does so with no warning. ## Mechanism (verified against source) - `cadence-core/bin/planning.mjs:644`: `if (cursor.phase >= shiftFrom) newCursor.phase = cursor.phase + delta;` applies the delta whether or not `cursor.phase` is an integer. - The existing warn (`planning.mjs:645-646`) only fires when `cursor.phase === at` (the exact removed integer phase); the decimal-desync case produces no `warn` in the output. ## Reproduction STATE cursor on phase `2.1`, then `renumber remove --n 1` (or `insert --at ≤ 2`): - STATE.md rewritten to `Phase: 1.1 of 3`. - ROADMAP and the directory still say `2.1`. - The cursor now points at phase `1.1`, which exists nowhere. No warning is emitted. ## Fix direction Apply the same decimal carve-out to the cursor: skip the shift when `cursor.phase` is not an integer, and emit a `warn` telling the caller to re-point it (mirroring the existing removed-phase warn). Pairs naturally with the fix for #36.
Sign in to join this conversation.
No description provided.