A light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.
fix(3749): port strategy-branch switching to SDK commit handler (#3763)
* test(3749): add RED test — SDK commit handler missing strategy-branch port
Structural assertions on sdk/src/query/commit.ts verify the branching-strategy
block (phase/milestone) is present. 9 tests fail today; pass after the fix.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(3749): port PR #1279 strategy-branch logic from CJS to SDK commit handler
sdk/src/query/commit.ts lacked the branching-strategy block that PR #1279
added to commands.cjs:285-320. Pre-execution workflow commits (discuss-phase,
plan-phase) with branching_strategy:"phase" or "milestone" were landing on
whatever branch was active rather than the configured strategy branch.
Changes:
- sdk/src/query/phase.ts: export findPhaseByNumber() so commit.ts can look up
a phase without going through the QueryHandler dispatch stack
- sdk/src/query/commit.ts: import loadConfig, findPhaseByNumber, getMilestoneInfo;
add ensureStrategyBranch() helper (extracts the logic, preserving SRP);
call it between the commit_docs check and the staging step
Semantics match the CJS path exactly: best-effort (errors are swallowed so a
misconfigured branching_strategy never aborts a commit), same phase-number
extraction from file paths, same checkout -b / checkout fallback sequence,
same current-branch guard to avoid repeated checkout calls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(3749): update changeset to reference PR #3763
* fix(3749): validate phase_branch_template before substitution
Before this commit, a missing or empty `phase_branch_template` (or
`milestone_branch_template`) would cause `.replace()` to throw inside
the try-block, which was swallowed by the surrounding catch → silent
strategy-skip with no observable signal.
Exports `validateBranchTemplate(template)` as a pure helper that
checks the template is a non-empty string BEFORE any `.replace()` call
is attempted. Also exports `resolveStrategyBranchName(template,
phaseNumber, phaseSlug)` which validates that no `{placeholder}` tokens
survive substitution.
Both an invalid template and an unresolved-placeholder result now return
`{ ok: false, reason: '...' }` from `ensureStrategyBranch`, surfaced
distinctly — satisfying the no-silent-skip contract (codex finding 3).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(3749): replace source-includes with typed-IR assertions on extracted helpers
The original test file used `source.includes(...)` throughout — grep
theater that verifies text presence, not behavior (codex finding 4 /
test-rigor Contract 1 violation).
This commit upgrades the test suite:
1. Typed-IR unit tests (ts-node, skipped gracefully when unavailable):
- `parsePhasesFromFiles`: empty, single-phase, mixed-phase, dotted
phase numbers, root numeric tokens
- `validateBranchTemplate`: undefined, empty, whitespace, valid
- `resolveStrategyBranchName`: well-formed, unresolved placeholder,
slug fallback
2. Structural assertions (retained and tightened) now verify:
- Named exports of the three helpers exist (enabling typed-IR tests)
- Caller halts on `strategyResult.ok === false` (Finding 2)
- Mixed-phase rejection message contains "single phase" (Finding 1)
- Template validation precedes resolveStrategyBranchName in source
(Finding 3, using lastIndexOf to skip helper definitions)
- `alreadyExists` guard and `branch_switch_failed` reason present
(Finding 2)
Old structural tests that verified implementation tokens
(loadConfig, phase_branch_template, --abbrev-ref, etc.) are
retained as they verify observable contracts, not code style.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(3749): bug-2767 tests skip cleanly when sdk/dist is absent (was hard-fail)
The 4 behavioral tests in bug-2767-gsd-sdk-commit-files-flag.test.cjs
invoke the built SDK CLI (sdk/dist/cli.js) end-to-end. When that dist is
absent, they threw MODULE_NOT_FOUND and were reported as 4 hard failures
rather than observable skips.
Apply the same `if (!existsSync(SDK_CLI)) { t.skip(...); return; }` guard
already used in bug-3019-help-passthrough.test.cjs. When dist is present,
all 4 tests run and pass. When dist is absent, all 4 emit a ﹣ skip line
with an actionable reason ('run `cd sdk && npm run build`'), leaving 0
hard failures and maintaining full observability.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(3749): address pr-review-toolkit + codex review findings
- Add allow-test-rule annotation to satisfy lint-no-source-grep
- Remove dead identifiers (registerScript, tsConfigPath, os import)
- Standardize issue #1278 / PR #1279 references in JSDoc
- Document root-level phase-number false-positive in parsePhasesFromFiles
- Generalize resolveStrategyBranchName parameter naming (phase + milestone reuse)
- Add behavioral integration test for commit handler with branching_strategy: phase
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(3749): normalize extracted phase token in phaseTokenMatches
parsePhasesFromFiles extracts "1" from a path like ".planning/phases/1-setup/PLAN.md".
normalizePhaseName pads this to "01" before passing it to searchPhaseInDir, which calls
phaseTokenMatches("1-setup", "01"). extractPhaseToken("1-setup") returned "1" (raw,
unpadded), so "1" !== "01" and the phase directory was not found — ensureStrategyBranch
fell through with "phase not found" and never switched the branch.
Fix: normalize the extracted token via normalizePhaseName before comparing so that "1"
and "01" both resolve to "01" and match correctly. Applied to both the primary comparison
and the project-code-prefix-stripping retry path.
Verified by bug-3749-sdk-commit-strategy-branch-integration.test.cjs passing:
✔ commit with --files in a phase dir switches to the strategy branch
✔ commit with --files outside a phase dir skips branch switch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> T
Tom Boucher committed
49dcabff264d576471dc840de2f7cc0ed284f414
Parent: f27b107
Committed by GitHub <noreply@github.com>
on 5/21/2026, 3:11:33 AM