fix(#4830): re-land #4768's letter-suffix phase-id fix and its lint-phase-id-drift ratchets on current next (#4873)

* fix(#4830): re-land #4768's letter-suffix phase-id fix and its lint-phase-id-drift ratchets on current next

Commit 740ba0d8a (#4781) removed every change #4768 had merged for #4748:
the first-non-digit split at execute-phase.md's two arithmetic sites, the
init-emitted `padded_phase` the REVIEW.md lookup binds instead of
`printf "%02d"`, the canonical-grammar extractions in autonomous.md and
plan-review-convergence.md, the `.changeset/zesty-wolves-tumble.md`
fragment, and the three lint-phase-id-drift ratchets with their tests.
The guard and the code it guarded left together, so nothing went red.

This is a cherry-pick of 092d9256b onto current `next`, resolved against
the #4683 threat-id fields on execute-phase.md's Parse-JSON line, with the
changeset `pr:` reset to the placeholder and the compact-content benchmark
baseline regenerated against the current base.

(cherry picked from commit 092d9256b8)

Emitted-Drift-Ack-Growth: autonomous.md — restores #4768's canonical-grammar extraction and its explanatory comment for --from/--to/--only
Emitted-Drift-Ack-Growth: execute-phase.md — restores #4768's first-non-digit split at two arithmetic sites and the padded_phase binding for the REVIEW.md lookup
Emitted-Drift-Ack-Growth: plan-review-convergence.md — restores #4768's canonical-grammar phase extraction and its comment
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AX7LXxc3uAkGki6iaYiAMP

* chore(#4830): set changeset fragment pr to 4873

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
0xdhx
2026-09-19 23:01:21 -05:00
committed by GitHub
parent 09e1110e68
commit 8a5166598c
15 changed files with 674 additions and 50 deletions

View File

@@ -1087,6 +1087,34 @@ describe('init commands ROADMAP fallback when phase directory does not exist (#1
assert.strictEqual(output.phase_name, 'Foundation Setup');
assert.strictEqual(output.phase_slug, 'foundation-setup');
assert.strictEqual(output.phase_req_ids, 'R-01, R-02');
// #4748: the ROADMAP fallback hands the workflow an UNPADDED number, and
// execute-phase.md used to re-pad it with `printf "%02d"` — which cannot
// pad a letter id and reads an already-padded `08` as octal. The
// normalized form is emitted here, like the plan-phase sibling above.
assert.strictEqual(output.padded_phase, '01');
});
test('#4748 — init execute-phase emits padded_phase for a letter-suffixed phase, from a directory and from the ROADMAP fallback', () => {
fs.appendFileSync(
path.join(tmpDir, '.planning', 'ROADMAP.md'),
'\n### Phase 3A: Letter Variant\n**Goal:** On disk\n\n### Phase 4B: Roadmap Only\n**Goal:** No directory yet\n',
);
seedPhase(tmpDir, '03A-letter-variant', { '03A-01-PLAN.md': '# Plan' });
const onDisk = JSON.parse(runGsdTools('init execute-phase 3A', tmpDir).output);
assert.strictEqual(onDisk.phase_found, true);
assert.strictEqual(onDisk.phase_number, '03A');
assert.strictEqual(onDisk.padded_phase, '03A');
const roadmapOnly = JSON.parse(runGsdTools('init execute-phase 4B', tmpDir).output);
assert.strictEqual(roadmapOnly.phase_found, true);
assert.strictEqual(roadmapOnly.phase_dir, null);
assert.strictEqual(roadmapOnly.phase_number, '4B');
assert.strictEqual(roadmapOnly.padded_phase, '04B');
const missing = JSON.parse(runGsdTools('init execute-phase 9Z', tmpDir).output);
assert.strictEqual(missing.phase_found, false);
assert.strictEqual(missing.padded_phase, null);
});
test('init verify-work falls back to ROADMAP when no phase directory exists', () => {