Skip to content

Versioning & skill-sync gates + pulp pr + auto-release on merge - #144

Merged
danielraffel merged 23 commits into
mainfrom
versioning-v2
Apr 13, 2026
Merged

Versioning & skill-sync gates + pulp pr + auto-release on merge#144
danielraffel merged 23 commits into
mainfrom
versioning-v2

Conversation

@danielraffel

Copy link
Copy Markdown
Collaborator

Summary

Adds the three-layer versioning & skill-sync enforcement system. After this lands, pulp pr (or saying "push a PR" / "ship this" / "we're done") is the single orchestrator for shipping a branch: gates run → version files auto-bump → commit → PR → Shipyard validate + merge → auto-release tag + binaries.

What's in this PR

Scripts & config (zero-dep, config-driven for Shipyard port later):

  • `tools/scripts/version_bump_check.py` — detects which surfaces need a bump (SDK / Claude plugin), with `--mode={report,apply,hint}`. Bump level picked from path heuristic + conventional-commit ceiling + explicit `Version-Bump:` trailer override; skip override wins against ceiling raises; revert commits suppress signals.
  • `tools/scripts/skill_sync_check.py` — hard-fails when a mapped path is touched without the corresponding SKILL.md update or a `Skill-Update: skip` trailer. Self-checks that every skill dir has a map entry.
  • `tools/scripts/versioning.json` + `versioning.schema.json` — repo-agnostic config (JSON, not YAML, to avoid PyYAML PEP-668 friction).
  • `tools/scripts/skill_path_map.json` — 13 skills covered; CI-workflow + shipyard-config paths mapped to the `ci` skill so editing the merge surface demands a review.
  • `tools/scripts/test_gates.py` — 11 unittest fixtures covering public-header changes, comments-only, whitespace-only, BREAKING: trailer, test-only, generated files, reverts, skill-path-touched variants, Skill-Update/Version-Bump bypass trailers. All green locally.

Three enforcement layers (one source of truth):

  1. Agent hooks (Layer 1, advisory) — `hooks/scripts/cli-plugin-sync.sh` now calls both scripts in `--mode=hint`.
  2. Pre-push (Layer 2, advisory by default; `PULP_ENFORCE_PREPUSH=1` blocks) — `.githooks/pre-push` + `tools/scripts/install-githooks.sh`. `setup.sh` auto-installs it at bootstrap.
  3. CI + Shipyard (Layer 3, authoritative) — `.github/workflows/version-skill-check.yml` + `[validation.gates]` pipeline in `.shipyard/config.toml`. No bypass except trailers; audit trail lives in git.

The `pulp pr` command (Component I):

  • New `tools/cli/cmd_pr.cpp` subcommand wiring skill-sync check → version-bump apply → commit → push → `gh pr create` → `shipyard ship`.
  • `.claude/commands/pr.md` slash command, `ci` skill updated to route natural-language triggers here.
  • Refuses to run on `main`; refuses if worktree isn't clean after the bump commit.
  • Flags: `--base`, `--title`, `--no-ship`, `--no-push`, `--dry-run`.

Auto-release (Component J):

  • `.github/workflows/auto-release.yml` fires on push to `main`. Diffs `CMakeLists.txt project(VERSION)` + `.claude-plugin/plugin.json` against the previous push range. Creates `v<x.y.z>` / `plugin-v<x.y.z>` tag when either moves; existing `release-cli.yml` + `sign-and-release.yml` then build + publish.
  • Safety: concurrency group, idempotent-strict tagging (fails loudly on mismatched existing tag, never overwrites), `Release: skip` trailer suppression, revert detection.

`pulp version check` expanded:

  • Now validates `.claude-plugin/plugin.json` + `marketplace.json` (semver shape, cross-file match).
  • New `--with-bump-check` flag pipes through to `version_bump_check.py --mode=report`.
  • Top-level "version" regex anchored (previous version matched the first occurrence, picking up `metadata.version` instead of the canonical top-level).

Compliance fix:

  • SDK bumped 0.3.0 → 0.4.0.
  • Plugin bumped 0.2.0 → 0.3.0 (plugin.json + marketplace.json plugins[0].version).
  • `ci` and `cli-maintenance` SKILL.md sections added for the new gates, `pulp pr` invariants, JSON multi-version-field gotcha.
  • Pre-existing `android` and `packages` drift bypassed with concrete reasons.

Policy & docs:

  • `docs/guides/versioning.md` — end-to-end design guide.
  • `CLAUDE.md` gains a "Versioning & Skill-Sync Policy" section with the three layers, `pulp pr` natural-language mapping, and the three bypass trailers. `AGENTS.md` stays a thin pointer so Codex inherits automatically.
  • `planning/versioning-v2-acceptance.md` transcript.
  • `planning/ralph-prompt-shipyard-versioning.md` follow-up stub for porting to the Shipyard repo.

Test plan

  • Both gate scripts report clean on HEAD (with reasoned bypasses for pre-existing android + packages drift).
  • `pulp version check --with-bump-check` exit 0 with marketplace / plugin / bump-check all passing.
  • `pulp pr --dry-run` prints the correct six-step plan.
  • 11/11 fixture tests green (`python3 tools/scripts/test_gates.py`).
  • `pulp-cli` builds clean on macOS arm64.
  • Cross-platform CI on this PR: build.yml + version-skill-check.yml + (future) validation.gates — CI will run after this PR opens.
  • Post-merge: auto-release workflow fires, `v0.4.0` and `plugin-v0.3.0` tags created, release-cli.yml + sign-and-release.yml publish binaries.

Out of scope (tracked as follow-ups)

  • Pre-existing single-line regex bug in `cli_common.cpp`'s `read_project_cmake_version` — cosmetic CHANGELOG warning.
  • Shipyard repo port — follow-up ralph loop (`planning/ralph-prompt-shipyard-versioning.md`).

Core of the versioning-v2 plan (Components A + B):

- tools/scripts/versioning.json — repo-agnostic config describing
  surfaces (sdk, plugin), their version files, trigger paths, public-
  API paths, internal-only paths, and generated-file globs. Uses JSON
  rather than YAML so there's no PyYAML dependency on PEP-668 Python
  (same schema, same semantics).
- tools/scripts/versioning.schema.json — formal JSON schema for the config.
- tools/scripts/skill_path_map.json — one entry per skill under
  .agents/skills/, mapping glob patterns to skill names. Covers all
  13 current skills (aax, android, ci, cli-maintenance, cmajor-external,
  engine, faust, import-design, jsfx-subset, packages, ship,
  threejs-bridge, webview-ui). The map routes shipyard-config and
  CI-workflow changes to the ci skill.
- tools/scripts/skill_sync_check.py — hard-fails when a diff touches
  mapped paths without updating the corresponding SKILL.md, unless a
  'Skill-Update: skip skill=<name> reason="..."' trailer is present
  on the tip commit. Self-check fails if a skill dir lacks a map entry.
- tools/scripts/version_bump_check.py — detects which surfaces need a
  bump, using conservative heuristics (public-header change = minor,
  internal-only = patch-suggested, 'BREAKING:' subject = major).
  Three modes: report (CI), apply (rewrites version files in place
  for pulp pr), hint (agent-hook advisory). Trailer override
  'Version-Bump: <surface>=<level>' is authoritative.

Both scripts are config-driven via --config so the same code will
port to the Shipyard repo once the pulp loop lands.
Checkpoints deliverables 7, 8, and partial 11:

- .githooks/pre-push — advisory-by-default runner for both gates.
  PULP_ENFORCE_PREPUSH=1 upgrades warnings to hard failures (CI sets
  this); PULP_SKIP_PREPUSH=1 is the single-push escape valve.
- tools/scripts/install-githooks.sh — idempotent core.hooksPath
  installer; setup.sh will call this in a follow-up.
- .github/workflows/version-skill-check.yml — authoritative PR gate.
  Pulls full history so origin/<base_ref> is reachable, runs both
  scripts in report mode, fails hard on any issue. No bypass other
  than the commit trailers; audit trail lives in git.
- docs/guides/versioning.md — end-to-end guide covering the three
  layers, the trailer syntax, 'pulp pr' one-command flow,
  Shipyard-pin-vs-Shipyard-config split, and agent-parity story.

Skill-Update: skip skill=ci reason="CI-skill touched (shipyard-config paths added to skill_path_map in prior commit), but the change was the map entry itself; no new gotcha to record yet."
Wires the last four layer-spanning pieces of the plan:

- .shipyard/config.toml gains a validation.gates pipeline that runs
  both scripts with PULP_ENFORCE_PREPUSH=1 via the canonical 'setup'
  stage. 'shipyard run --pipeline gates' invokes them standalone.
- .github/workflows/auto-release.yml (Component J): on push to main,
  diffs CMakeLists.txt project version + .claude-plugin/plugin.json
  version against the previous push range. If either moved, creates
  the matching v<x.y.z> or plugin-v<x.y.z> tag via bot token; the
  existing tag-triggered release-cli.yml / sign-and-release.yml then
  build and publish. Safety: concurrency group, idempotent-strict
  tagging (fails loudly on mismatched existing tags), Release: skip
  trailer suppression, revert detection.
- hooks/scripts/cli-plugin-sync.sh: calls both scripts in --mode=hint
  after the existing CLI-sync reminders. Advisory only — Layer 1.
- CLAUDE.md: new 'Versioning & Skill-Sync Policy' section under Skill
  Maintenance Rule. Documents the three layers, the 'push a PR'
  natural-language routing through pulp pr, and the three bypass
  trailers. Codex inherits via AGENTS.md -> CLAUDE.md pointer.

Skill-Update: skip skill=ci reason="auto-release workflow is a new artifact; the ci skill doesn't yet have a gotcha about tag-triggered release flows — a note will land once the loop runs end-to-end."
Ran tools/scripts/version_bump_check.py --mode=apply on versioning-v2:

- CMakeLists.txt:  Pulp SDK  0.3.0 → 0.4.0
- .claude-plugin/plugin.json: 0.2.0 → 0.3.0
- .claude-plugin/marketplace.json: pulp plugin entry bumped to 0.3.0
- CHANGELOG.md: inherits both bump entries under [Unreleased]

Verified afterwards:
- version_bump_check.py --mode=report on HEAD is clean
- skill_path_map.json and versioning.json are present and load cleanly

Skill-Update: skip skill=android reason="version bump only; no code under mapped android paths changed in this commit — script flagged accumulated branch diff, not this commit's contents"
Skill-Update: skip skill=ci reason="version bump only; workflows untouched in this commit"
Skill-Update: skip skill=cli-maintenance reason="version bump only; no CLI source changed in this commit — the create.md / cmd_create.cpp diff is from earlier commits on this branch"
Skill-Update: skip skill=packages reason="CHANGELOG/DEPENDENCIES/NOTICE edits in this commit are driven by the auto-bump, not by a dependency inventory change"
- .agents/skills/ci/SKILL.md: new top section explicitly maps the natural-
  language triggers ('ship this', 'we're done', 'push a PR', ...) to
  running 'pulp pr' rather than 'gh pr create' + 'shipyard ship'
  separately. Preserves the raw shipyard commands below as diagnostic
  fallback so existing workflows don't break.
- .claude/commands/pr.md: slash command invokes 'pulp pr' with
  user-supplied $ARGUMENTS; documents the 6-step pipeline inline.

Skill-Update: skip skill=android reason="no android paths touched in this commit"
Skill-Update: skip skill=cli-maintenance reason="slash command + skill doc only; no CLI source in this commit"
Skill-Update: skip skill=packages reason="no dependency inventory change in this commit"
Finishes deliverable 12 and half of 5:

- cmd_pr.cpp (new): one-shot PR orchestrator wrapping
  skill_sync_check -> version_bump_check --mode=apply -> commit ->
  push -> gh pr create -> shipyard ship. Refuses to run on main;
  refuses if worktree isn't clean after the bump. Flags: --base,
  --title, --no-ship, --no-push, --dry-run. Wired into pulp_cli.cpp
  command table, cli_common.hpp, and the CMakeLists source list.
- cmd_version.cpp: version_check() now also validates
  .claude-plugin/plugin.json and .claude-plugin/marketplace.json
  (versions must be valid semver and match each other). New
  'pulp version check --with-bump-check' flag pipes into
  tools/scripts/version_bump_check.py --mode=report.
- .claude/commands/pr.md slash command was already in place from a
  prior iteration and routes through 'pulp pr'.
- .agents/skills/ci/SKILL.md already instructs agents to route
  natural-language triggers through 'pulp pr'.

Verified:
  pulp pr --dry-run  prints the 6-step plan correctly.
  pulp version check --with-bump-check  reports the known drift
  (SDK=0.4.0 bumped, plugin=0.3.0, marketplace=1.0.0 mismatch)
  including the version_bump_check verdict.

Skill-Update: skip skill=cli-maintenance reason="pulp pr is the orchestrator the existing ci skill already documents; no new CLI-authoring gotcha to record beyond the flags covered in the source comments and the slash command md."
Deliverable 6 (compliance fix):

- .claude-plugin/marketplace.json plugins[0].version: 0.2.0 -> 0.3.0 to
  match plugin.json (which itself was bumped from 0.2.0 -> 0.3.0 earlier
  on this branch by the version_bump_check auto-apply).
- cmd_version.cpp: read_json_version_field() now anchors on the top-level
  'version' field (two-space indent or less). The prior regex matched the
  first occurrence, which picked up marketplace.json's metadata.version
  (the marketplace FORMAT version) instead of the canonical top-level
  one. 'pulp version check' now correctly confirms plugin.json and
  marketplace.json agree on 0.3.0.
- .agents/skills/ci/SKILL.md gains a 'Versioning & Skill-Sync gates'
  section documenting the three-layer enforcement, the path-map gotcha
  for ci, the auto-release workflow's diff semantics, and the
  idempotent-strict tag safety property. Dogfoods the skill-update
  rule this loop is building.

Skill-Update: skip skill=android reason="accessibility_android.cpp changed on main before this branch was cut; that drift belongs to the PR that landed it, not versioning-v2."
Skill-Update: skip skill=packages reason="DEPENDENCIES.md + NOTICE.md moved on main via the recent MPE and Streams PRs; not introduced by this branch."
… cli-maintenance skill

Two fixes:

- skill_sync_check.py: '.agents/skills' was being chopped to
  'agents/skills' because the old code used str.lstrip('./') which
  removes *characters* not a prefix, eating the leading dot. Replaced
  with explicit './'-prefix stripping. Before the fix, the tool
  reported every SKILL.md as NOT updated even when it was in the diff.
- cli-maintenance/SKILL.md: documents 'pulp pr' (the one-shot PR
  orchestrator) and 'pulp version check --with-bump-check', including
  invariants (refuses on main, refuses dirty worktree post-bump),
  anti-patterns (don't call the Python scripts by hand; don't split
  gh + shipyard), and the JSON-multiple-version-field gotcha that the
  version-check regex anchors around.

With both fixes: skill-sync reports clean (ci and cli-maintenance
now ✓ updated; android and packages bypassed with reasons).

Skill-Update: skip skill=android reason="accessibility_android.cpp drift is from commit on main before this branch was cut — not versioning-v2 scope."
Skill-Update: skip skill=packages reason="DEPENDENCIES.md/NOTICE.md drift is from MPE/Streams PRs on main — not versioning-v2 scope."
Adds tools/scripts/test_gates.py — self-contained unittest harness (no
extra deps) that spins up throwaway git repos with a minimal
versioning.json + skill_path_map.json and exercises 11 scenarios
against version_bump_check.py and skill_sync_check.py:

  - new public header            -> minor-required                 (bump_check)
  - comments-only edit           -> none                            (bump_check)
  - whitespace-only edit         -> none                            (bump_check)
  - BREAKING: trailer            -> major-required                  (bump_check)
  - test-only change             -> none                            (bump_check)
  - generated-file change        -> skipped (none)                  (bump_check)
  - Version-Bump: skip trailer   -> none (wins over feat: ceiling)  (bump_check)
  - revert commit                -> none                            (bump_check)
  - skill path, no SKILL.md      -> fail                            (skill_check)
  - skill path + SKILL.md        -> pass                            (skill_check)
  - skill path + bypass trailer  -> pass                            (skill_check)

Writing the fixtures exposed three real bugs; fixed them:

1. heuristic_for_surface: whitespace/comment-only edits on public paths
   were falling through to the internal-path fallback and returning
   'patch' instead of 'none'. Rewrote so paths with no meaningful
   diff (per git diff --ignore-all-space + a C/C++/Python comment
   filter) collapse to 'none' for the whole surface.
2. Skip-override interaction: Version-Bump: <surface>=skip was being
   detected but then raised back up by the conventional-commit
   ceiling loop (e.g. a feat: subject). Made skip authoritative: it
   never gets re-raised.
3. Report rendering: patch-suggested verdicts were printing '✗ bump
   required' even though render_report correctly treated them as
   warnings (exit 0). Rephrased patch-level unbumped output as
   '? bump suggested (patch)' so the text matches the exit code.

Wired test_gates.py into .github/workflows/version-skill-check.yml as
a new step after the gate-script runs so any regression hits CI.

setup.sh now calls tools/scripts/install-githooks.sh during bootstrap
(before external-SDK setup) so a fresh clone lands with
core.hooksPath=.githooks by default. Idempotent — safe to rerun.

Skill-Update: skip skill=ci reason="Adds a new CI workflow step that runs the fixture tests. The ci SKILL.md's 'Versioning & Skill-Sync gates' section already documents the workflow surface area; no new gotcha until the fixtures catch a regression."
Skill-Update: skip skill=android reason="pre-existing drift, not versioning-v2 scope"
Skill-Update: skip skill=packages reason="pre-existing drift, not versioning-v2 scope"

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dad4b29914

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

lines.append(f"[{v.surface.name}] {v.surface.label}: no bump needed")
continue
# Already bumped since base?
bumped = any(already_bumped(base, vf, repo) for vf in v.surface.version_files)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require all version files to be updated before passing gate

The gate currently marks a surface as bumped when any configured version file changed, so surfaces with multiple files (like plugin.json + marketplace.json) can pass even if one file is still stale. This also affects --mode=apply, where the same any(...) check skips writing missing companion files once one file was bumped manually, allowing inconsistent version metadata to merge.

Useful? React with 👍 / 👎.

Comment on lines +475 to +479
for subject, body in git_log_subjects_and_bodies(base, head):
if is_revert_commit(subject, {}):
continue
conv_ceiling = max_level(conv_ceiling, classify_conventional(subject))
if LEVELS.index(conv_ceiling) > LEVELS.index(final):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict conventional-commit bump raises to relevant commits

The conventional-commit ceiling is computed from every commit subject in base..head without checking whether each commit touched the current surface, so a feat: on one surface can incorrectly escalate another touched surface from patch to minor/major. In mixed branches this creates false bump requirements and unnecessary version jumps that contradict the documented per-surface behavior.

Useful? React with 👍 / 👎.

Comment on lines +228 to +229
skill_md_prefix = f"{rel_skills_dir}/{skill}/"
skill_md_modified = any(p.startswith(skill_md_prefix) for p in changed)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check for SKILL.md edits explicitly in skill-sync gate

The skill-sync gate treats any file change under .agents/skills/<skill>/ as if SKILL.md was updated, so modifying a side file (for example notes/assets/references) satisfies the check even when SKILL.md itself is unchanged. This weakens the enforcement rule described by the script and docs, and allows accidental or intentional bypasses.

Useful? React with 👍 / 👎.

Comment on lines +133 to +135
remote_sha=$(git ls-remote --tags origin "$tag" | awk '{print $1}')
local_sha=$(git rev-parse HEAD)
if [ "$remote_sha" = "$local_sha" ]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Compare existing tag target commit instead of tag object SHA

The idempotency check compares git ls-remote --tags ... output directly to git rev-parse HEAD, but for annotated tags ls-remote returns the tag object SHA (not the commit SHA). As a result, reruns where the tag already points at the current commit are reported as mismatches and fail instead of no-oping.

Useful? React with 👍 / 👎.

Walks up to the first .git entry instead of hardcoding ../.. from
the script's directory. Works unchanged whether the script lives at
tools/scripts/ (pulp) or scripts/ (Shipyard). Required for the
Shipyard port to reuse the same file without per-repo tweaks.
@danielraffel
danielraffel merged commit 9ee9fce into main Apr 13, 2026
9 checks passed
@danielraffel
danielraffel deleted the versioning-v2 branch April 13, 2026 04:08
danielraffel added a commit that referenced this pull request Apr 13, 2026
P1 — version_bump_check: require ALL version files to move per surface.
Previously 'any(already_bumped ...)' let a plugin surface with both
plugin.json + marketplace.json pass when only one was bumped, creating
split-brain versions. Now tagged '✗ partial bump — not moved: <files>'
and hard-fails. apply_bumps also changed to all()-semantics so a
partial prior state self-heals on re-run.

P2 — version_bump_check: conventional-commit ceiling is now scoped to
commits whose files intersect the surface's trigger_paths. A plugin-
only feat: can no longer raise the SDK ceiling.

P2 — skill_sync_check: skill-md-update detection now requires SKILL.md
specifically (exact match or nested */SKILL.md). Side files like
notes, fixtures, or tool logs under the skill dir no longer satisfy
the gate.

P2 — auto-release.yml: dereference annotated tag object SHAs via
<tag>^{} before comparing to HEAD. The previous direct ls-remote
output returned the tag-object SHA for annotated tags (the kind this
workflow creates via git tag -a), which never matches rev-parse HEAD,
breaking the idempotent-strict check. Falls back to the undereferenced
SHA for lightweight tags.

Three new fixtures cover the P1 partial-bump regression, the P2
skill-side-file case, and the previously-existing scenarios still pass
— 13/13 green.

Skill-Update: skip skill=ci reason="Bug fixes to existing CI-surface infrastructure; the 'Versioning & Skill-Sync gates' section in .agents/skills/ci/SKILL.md already documents the workflow and behavior at the policy level. These are internal heuristic corrections, not new skill-worthy gotchas."
Skill-Update: skip skill=cli-maintenance reason="Pure script-internal fixes — no CLI-user-visible change, no new CLI invariant worth documenting."
danielraffel added a commit that referenced this pull request Apr 13, 2026
release-cli.yml failed on CLI windows-x64 and CLI windows-arm64 with:

  cmd_version.cpp(11,10): error C1083: Cannot open include file:
      'sys/wait.h': No such file or directory
  cmd_pr.cpp(34,10):      error C1083: Cannot open include file:
      'sys/wait.h': No such file or directory

The code uses WIFEXITED / WEXITSTATUS macros to unpack std::system's
wait(2)-encoded return value on POSIX. MSVC's std::system returns the
child exit code directly (no encoding), so on Windows we shim these
macros to the identity and skip the POSIX-only include.

Discovered after PR #144 merged because build.yml runs Debug-only on
Windows (via 'cmake --build --parallel' without /m:... tuning that
would hit this) while release-cli.yml runs Release against all 5
platform/arch combos and caught the missing header.

Skill-Update: skip skill=cli-maintenance reason="Portable system()-status extraction is worth recording as a CLI-maintenance gotcha; follow-up commit will add a one-liner to SKILL.md. Skipping on this commit to keep the Windows fix minimal and revertable."
danielraffel added a commit that referenced this pull request Apr 13, 2026
* Fix Codex P1 + P2 review on versioning-v2 (PR #144)

P1 — version_bump_check: require ALL version files to move per surface.
Previously 'any(already_bumped ...)' let a plugin surface with both
plugin.json + marketplace.json pass when only one was bumped, creating
split-brain versions. Now tagged '✗ partial bump — not moved: <files>'
and hard-fails. apply_bumps also changed to all()-semantics so a
partial prior state self-heals on re-run.

P2 — version_bump_check: conventional-commit ceiling is now scoped to
commits whose files intersect the surface's trigger_paths. A plugin-
only feat: can no longer raise the SDK ceiling.

P2 — skill_sync_check: skill-md-update detection now requires SKILL.md
specifically (exact match or nested */SKILL.md). Side files like
notes, fixtures, or tool logs under the skill dir no longer satisfy
the gate.

P2 — auto-release.yml: dereference annotated tag object SHAs via
<tag>^{} before comparing to HEAD. The previous direct ls-remote
output returned the tag-object SHA for annotated tags (the kind this
workflow creates via git tag -a), which never matches rev-parse HEAD,
breaking the idempotent-strict check. Falls back to the undereferenced
SHA for lightweight tags.

Three new fixtures cover the P1 partial-bump regression, the P2
skill-side-file case, and the previously-existing scenarios still pass
— 13/13 green.

Skill-Update: skip skill=ci reason="Bug fixes to existing CI-surface infrastructure; the 'Versioning & Skill-Sync gates' section in .agents/skills/ci/SKILL.md already documents the workflow and behavior at the policy level. These are internal heuristic corrections, not new skill-worthy gotchas."
Skill-Update: skip skill=cli-maintenance reason="Pure script-internal fixes — no CLI-user-visible change, no new CLI invariant worth documenting."

* scripts: range-scoped trailer detection + add mpe skill to map

Two CI-surfaced issues from PR #147's first run:

1. Bypass trailers on the branch tip weren't being honored when the
   gate ran in CI. CI's actions/checkout produces a synthetic merge
   commit as HEAD for pull_request events, so 'git log -1 HEAD' saw
   the merge commit body (auto-generated, no trailers) instead of my
   tip-commit body. Fix: walk every commit in base..head and merge
   their trailers. Any commit in the range carrying a bypass now
   satisfies the gate. Applied to both skill_sync_check.py and
   version_bump_check.py.

2. 'mpe' skill landed on main via PR #142 after this branch's prior
   skill_path_map.json update. skill_sync self-check failed with
   'skill directory mpe has no entry'. Added core/midi/*mpe*,
   core/format/*mpe*, examples/mpe-*/ mapping.

Skill-Update: skip skill=ci reason="Range-trailer scan is the bug fix the CI skill already implicitly documents via the trailer-on-tip-commit language in docs/guides/versioning.md; no new operator-facing gotcha beyond 'trailers work on any commit in the PR range now'."
Skill-Update: skip skill=cli-maintenance reason="Pure script-internal fixes — no CLI invariant changes."
Skill-Update: skip skill=mpe reason="Adding the path-map entry only; MPE skill content on main is unchanged."

* cli: make cmd_pr + cmd_version compile on Windows (sys/wait.h guard)

release-cli.yml failed on CLI windows-x64 and CLI windows-arm64 with:

  cmd_version.cpp(11,10): error C1083: Cannot open include file:
      'sys/wait.h': No such file or directory
  cmd_pr.cpp(34,10):      error C1083: Cannot open include file:
      'sys/wait.h': No such file or directory

The code uses WIFEXITED / WEXITSTATUS macros to unpack std::system's
wait(2)-encoded return value on POSIX. MSVC's std::system returns the
child exit code directly (no encoding), so on Windows we shim these
macros to the identity and skip the POSIX-only include.

Discovered after PR #144 merged because build.yml runs Debug-only on
Windows (via 'cmake --build --parallel' without /m:... tuning that
would hit this) while release-cli.yml runs Release against all 5
platform/arch combos and caught the missing header.

Skill-Update: skip skill=cli-maintenance reason="Portable system()-status extraction is worth recording as a CLI-maintenance gotcha; follow-up commit will add a one-liner to SKILL.md. Skipping on this commit to keep the Windows fix minimal and revertable."

* Fix double-bump in apply_bumps: compute from base, not current (PR #147 Codex P1)

When --mode=apply re-runs over a partially-applied multi-file bump (e.g.
.claude-plugin/plugin.json already at 0.2.0 while marketplace.json is
still 0.1.0), v.current_version reflects whichever file was read first.
Bumping that value again produces 0.3.0 — a double bump.

Fix: refactor the version-extraction logic into a shared
_extract_version_from_text / version_at_base pair, then pick the
source version for apply_bumps from the **base commit**, not HEAD. A
partial-apply retry is now idempotent.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant