Skip to content

Publish the fizzy skill to basecamp/skills on release - #214

Open
jeremy wants to merge 4 commits into
masterfrom
sync-skills
Open

Publish the fizzy skill to basecamp/skills on release#214
jeremy wants to merge 4 commits into
masterfrom
sync-skills

Conversation

@jeremy

@jeremy jeremy commented Sep 13, 2026

Copy link
Copy Markdown
Member

Why

basecamp/skills is being repositioned as the skills repo for all 37signals products (basecamp/skills#6), installable with one npx skills add basecamp/skills. basecamp-cli and hey-cli already publish their skills there on every stable release; Fizzy is the one CLI that does not, so skills/fizzy only ever reaches agents through the binary or this repo's tree.

What

  • scripts/sync-skills.sh — byte-identical to the seed's seed/scripts/sync-skills.sh in Seed the skills sync with per-source manifests and the skills/ layout cli#78 at 5aec9a1 apart from the CLI_NAME default (fizzy, so SYNC_SOURCE is fizzy-cli: bot fizzy-cli[bot], commit Sync skills from fizzy-cli <tag>, provenance basecamp/fizzy-cli@<sha> from GITHUB_REPOSITORY) and the seed's # TODO line. basecamp-cli#708 and hey-cli#434 are converging on the same file, so every publisher runs one script.
  • Per-source manifest. The script records what it published in .managed-skills.fizzy-cli at the target root and only ever removes a skills/<name> that its own manifest lists, the release no longer ships, and no other CLI's manifest claims. A target with no .managed-skills.fizzy-cli yet — which is what the first Fizzy release will find — removes nothing. The manifest is per-source because the shared one let each CLI's release delete the others' skills (The hey-cli bot deleted the Basecamp skills skills#5: hey-cli's sync deleted skills/basecamp and skills/basecamp-doctor, basecamp-cli's deleted skills/hey twice). The legacy .managed-skills is rewritten as a comment-only tombstone so a sibling still on the pre-fix script deletes nothing either.
  • Beyond the first cut (the seed's additions): a name another source's manifest holds is refused before anything is copied; a rejected push (fetch first or non-fast-forward) resets to the remote's new tip, re-applies the sync there with the ownership checks, and pushes again; SYNC_SOURCE and DRY_RUN are validated; a checkout with uncommitted changes is refused; and the token reaches git through a private GIT_CONFIG_GLOBAL insteadOf rewrite instead of the remote URL, so it never appears in argv or the checkout's config.
  • sync-skills job in release.yml — mirrors hey-cli's: stable tags only, needs: [release], continue-on-error: true so a stale skills repo never fails a release that has already shipped, concurrency group sync-skills, release environment, token minted per-run and scoped to basecamp/skills with contents write. On failure it files or comments on a skills-sync-labeled issue here (label-keyed and fail-closed so a retitled issue or an API error cannot spawn duplicates; the label is created idempotently on first use since this repo does not have it yet).
  • sync-skills.yml — hey-cli's manual recovery workflow: workflow_dispatch with a stable tag and an optional dry run, refusing anything but the latest stable release so it cannot roll the distribution repo back, running the sync logic from the dispatching ref against the tag's skills tree.
  • scripts/test-sync-skills.sh — the seed's test, unchanged. Plain bash (BATS is not in this repo's toolchain: make tools and CI install none). It runs the script as two CLIs against one throwaway basecamp/skills checkout seeded with the state The hey-cli bot deleted the Basecamp skills skills#5 left: interleaved A, B, A, B asserting both sources' skills, both manifests and the tombstone after each step; the copy filter dropping *.go, dotfiles and dot-directories; a dropped skill removing only its own directory; a pre-fix sibling's rewrite of the legacy manifest deleting nothing; a name in two manifests surviving with a warning; publishing a sibling-owned name refused with nothing changed; DRY_RUN=remote showing the diff without committing; the offline DRY_RUN=local preview; a real push against a local bare origin that a sibling moved first (retry lands on top of the sibling's commit) and one where the sibling claims the name mid-race (retry refuses, origin untouched); and the dirty-tree, wrong-remote, wrong-pushurl and wrong-branch refusals. Wired as make test-sync-skills (now part of make check) and as a "Test the skills sync" step in test.yml's test job, which release.yml also calls — the same wiring as the seed's Makefile and test.yml.
  • scripts/sync-skills.sh added to the sensitive-change gate's extra patterns alongside publish-aur.sh, since it pushes to another repo with a minted token.
  • RELEASING.md — step 10 in the release list, the prerelease-behavior and distribution-channel tables, the CI Secrets note (the tap and skills tokens are both minted from cli-release-bot, each scoped to its one repo), and a new "Skills sync" section covering ownership, the offline preview, the failure issue and the recovery workflow. AGENTS.md's tree comment notes the mirror.

What gets published

DRY_RUN=local from this branch's root:

Found 1 skill(s) in skills/: fizzy

=== Skills copied ===
  skills/fizzy/SKILL.md

=== Diff (against empty baseline) ===
 skills/fizzy/SKILL.md | 1159 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1159 insertions(+)

skills/ holds only fizzy/SKILL.md and embed.go; the copy filter drops *.go and dotfiles, so SKILL.md is the whole of it.

For the maintainer to confirm: the app on basecamp/skills

The job mints its token from vars.RELEASE_CLIENT_ID / secrets.RELEASE_APP_PRIVATE_KEY in this repo's release environment, exactly as the existing tap push does, but asks for repositories: skills. That only works if the app behind those credentials is installed on basecamp/skills. What I could verify: RELEASE_CLIENT_ID is the same value (Iv23liAbERu6z8d7t5A8, cli-release-bot) in the release environments of fizzy-cli, basecamp-cli and hey-cli, and basecamp/skills' history carries pushes from both basecamp-cli[bot] and hey-cli[bot], so that app is installed there with contents write. This repo's RELEASE_APP_PRIVATE_KEY already mints a working token for homebrew-tap on every release, so the key is that app's. What I cannot exercise from here is the mint for repositories: skills from this repo's environment, so please confirm it on the first stable release. The job is continue-on-error, so if the mint fails the release still ships; the skills-sync issue and the Sync skills workflow are the way back.

Testing

  • diff scripts/sync-skills.sh <seed>/seed/scripts/sync-skills.sh at 5aec9a1: only the # TODO line and the CLI_NAME default differ
  • scripts/test-sync-skills.sh: all assertions passed (locally and in CI's test job)
  • shellcheck scripts/sync-skills.sh scripts/test-sync-skills.sh: clean
  • actionlint and zizmor . (what make lint-actions and the lint-actions job run): no findings
  • make check passes (fmt, vet, lint, tidy, race-test, test-sync-skills)

basecamp/skills is becoming the skills repo for every 37signals product,
and Fizzy was the one CLI not publishing there. Stable releases now mirror
skills/ into it with the same sync-skills.sh basecamp-cli and hey-cli run,
which owns only this CLI's skills via a per-source manifest
(.managed-skills.fizzy-cli) and never touches a sibling's
(basecamp/skills#5). The job is continue-on-error and files a
skills-sync issue on failure; sync-skills.yml is the manual recovery.

The sibling repos' BATS test is ported to plain bash, since this repo's
toolchain has no BATS, and runs as make test-scripts (part of make check)
and in CI.
Copilot AI balanced review requested due to automatic review settings September 13, 2026 00:28
@github-actions

Copy link
Copy Markdown

Sensitive Change Detection (shadow mode)

This PR modifies control-plane files:

  • .github/workflows/release.yml
  • .github/workflows/sensitive-change-gate.yml
  • .github/workflows/sync-skills.yml
  • .github/workflows/test.yml

Shadow mode — this check is informational only. When activated, changes to these paths will require approval from a maintainer.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 13, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-13T03:18:43.295111Z ed326de New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Invalid DRY_RUN values can unexpectedly execute the production push path instead of failing safely.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Publishes Fizzy’s agent skill to basecamp/skills during stable releases, with safe ownership tracking and manual recovery.

Changes:

  • Adds skill synchronization and release workflows.
  • Adds shell-based ownership and safety tests to CI.
  • Documents the publishing and recovery process.

[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

File summaries
File Description
scripts/sync-skills.sh Implements synchronization and ownership manifests.
e2e/sync_skills_test.sh Tests synchronization behavior.
.github/workflows/release.yml Publishes skills after stable releases.
.github/workflows/sync-skills.yml Adds manual recovery workflow.
.github/workflows/test.yml Runs shell tests in CI.
.github/workflows/sensitive-change-gate.yml Protects the publishing script.
Makefile Adds script-test targets.
RELEASING.md Documents publishing and recovery.
AGENTS.md Notes the external skills mirror.
Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 2
  • Review effort level: Balanced (auto)

Note

Copilot is running an experiment and ran this review at Balanced.


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread scripts/sync-skills.sh
Comment thread e2e/sync_skills_test.sh Outdated

@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: 5ed19d80c5

ℹ️ 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".

Comment thread .github/workflows/release.yml
Comment thread scripts/sync-skills.sh Outdated
Comment thread scripts/sync-skills.sh Outdated
The seed (basecamp/cli#78) went further than the hey-cli copy taken
first: a rejected push re-applies the sync from the remote's new tip
(matching git's "fetch first" as well as "non-fast-forward"), a name
another source's manifest holds is refused before anything is copied,
SYNC_SOURCE and DRY_RUN are validated, a dirty checkout is refused, and
the token reaches git through a private config rather than the remote
URL. basecamp-cli and hey-cli are converging on the same file, so every
publisher runs one script; only the CLI_NAME default differs here.

The seed's test-sync-skills.sh replaces the bash port of the BATS file:
it covers the same ownership cases plus the racing publisher, the
publish-side refusal, DRY_RUN=remote and the dirty-tree guard.
…ards

Mirrors basecamp/cli@966966e (the seed is the source of truth; only the
CLI_NAME default differs here), and picks up the two seed commits this copy
was behind: the remote-URL assert that read every configured URL, and two
comment rewordings — both now moot, since the assert is gone.

SKILLS_TARGET let the script adopt an existing checkout of basecamp/skills.
Every release path clones its own target, and each review round found
another corner of "any checkout" to guard. The script now always clones
into a temp directory from SKILLS_REPO_URL (default
https://github.com/basecamp/skills.git, the token carried through the same
insteadOf rewrite as before), applies, pushes and cleans up, so the only
commit it can push is the one it made. The remote-URL, branch and
clean-tree asserts are gone with the knob; the retry from the fetched tip,
the per-source manifests, the collision guard, the tombstone and DRY_RUN
validation are unchanged.

The test points SKILLS_REPO_URL at a local bare repository and reads every
result back from a clone of its own; the race is staged with a post-commit
hook that pushes the sibling's commit between the script's clone and push.

@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: ed326de328

ℹ️ 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".

sync-skills:
name: Sync skills
needs: [release]
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-')

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 Prevent older releases from overwriting newer skills

When an older stable release finishes after a newer one—or its workflow is rerun later—this condition still runs the sync for the older tag, and scripts/sync-skills.sh replaces the published skill wholesale. The release workflow permits different tags to run concurrently via the per-tag group at lines 8-10, while this job's sync-skills concurrency group only serializes jobs and does not enforce version order, so the older job can permanently roll basecamp/skills back after the newer job. Add the same latest-stable-release guard used by the manual workflow before publishing.

AGENTS.md reference: AGENTS.md:L16-L16

Useful? React with 👍 / 👎.

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.

2 participants