Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "github-release",
"version": "0.2.1",
"version": "0.3.0",
"description": "Create GitHub repositories for Agent Skills and release them right the first time: version gate, deterministic host packages, draft-first publishing, published-asset verification and a per-host install log.",
"author": {
"name": "Jesse Raber",
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "github-release",
"version": "0.2.1",
"version": "0.3.0",
"description": "Create GitHub repositories for Agent Skills and release them right the first time: version gate, deterministic host packages, draft-first publishing, published-asset verification and a per-host install log.",
"skills": "./skills/",
"author": {
Expand Down
19 changes: 11 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# CI template from the github-release skill. Copy to .github/workflows/ci.yml.
# Runs the same checks the tag workflow runs, so problems surface before a tag exists,
# and proves that Windows and Ubuntu builds produce identical archives.
# Pushed release branches get CI before a PR exists; workflow_dispatch reruns it on demand.
# Action pins: full commit SHA + the release tag they were resolved from (checked 2026-10-09).
name: CI

on:
push:
branches: [main]
branches: [main, 'release/**', 'v*']
pull_request:
workflow_dispatch:

permissions:
contents: read
Expand All @@ -22,16 +25,16 @@ jobs:
- {os: ubuntu-22.04, python: '3.8'} # oldest supported Python; drop this leg if the runner image or setup-python retires 3.8
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python }}
- name: Tests
shell: bash
run: if [ -d tests ]; then python -m unittest discover -s tests -v; else echo "no tests directory"; fi
- name: Gate checks (versions agree, notes exist, workflow is draft-first)
- name: Gate checks (versions agree, notes lint, record encoding, workflow is draft-first)
shell: bash
run: |
SKILL=$(python -c "import json;print(json.load(open('packaging/packages.json'))['skill'])")
Expand All @@ -41,18 +44,18 @@ jobs:
- name: Candidate build (byte-verified, not published)
shell: bash
run: python packaging/build_packages.py "$RUNNER_TEMP/candidate" --status "CI candidate, not published"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: sums-${{ matrix.os }}-${{ matrix.python }}
path: ${{ runner.temp }}/candidate/SHA256SUMS.txt

validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.11'
- name: Frontmatter and OpenAI skill validator (pinned)
Expand All @@ -76,7 +79,7 @@ jobs:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
path: sums
- name: Windows and Ubuntu builds must be byte-identical
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# Copy to .github/workflows/release.yml. On a pushed tag vX.Y.Z it gates, builds,
# and creates a DRAFT release. A person publishes the draft after verifying it.
# It never replaces assets of an existing release.
# Action and validator pins are copied from multi-agent-folder-cleanup v1.6.1;
# review them before first use.
# Actions are pinned by full commit SHA with the release tag in a comment (resolved
# 2026-10-09); the validator is pinned by commit. Review pins before first use.
name: Release

on:
Expand All @@ -23,10 +23,10 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false # later steps run downloaded code; do not leave the write token in .git
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.11'
- name: Install validation dependency
Expand All @@ -53,7 +53,7 @@ jobs:
exit 1
fi

- name: Versions agree and notes exist
- name: Versions agree, notes lint (no candidate wording), record encoding
run: |
python3 packaging/check_versions.py --repo . --config packaging/version_files.json \
--version "${{ steps.ver.outputs.version }}" --workflow .github/workflows/release.yml
Expand Down Expand Up @@ -94,7 +94,7 @@ jobs:
if find dist -name __pycache__ | grep -q .; then echo "__pycache__ leaked"; exit 1; fi

- name: Upload build artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: packages-${{ steps.ver.outputs.version }}
path: dist/
Expand Down
30 changes: 27 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
# Changelog

## 0.2.1 (draft, not released)
## 0.3.0

Minor: new gate checks, a notes and record lint, a new helper and changed helper output.

Release process (lessons from the multi-agent-folder-cleanup release history, project rows R-051..R-065):

- Notes lint in `check_versions.py`: the notes file FAILs on candidate/"not released" status wording, TBD/TODO, template placeholders, empty bullets and pre-written publish dates; WARNs on internal IDs (session UUIDs, tracker rows). HTML comments are ignored. The release workflow runs it before creating the draft. Notes template rewritten to match (R-051, R-060).
- Record lint in `check_versions.py` (and therefore CI): `HOST_INSTALL_LOG.md`, `CHANGELOG.md`, `README.md` and release notes must be UTF-8 without BOM, without NUL bytes, LF only; one install-log header. `--no-records` skips it (R-063).
- `check_versions.py --drift-against <skill dir>`: lists repository copies of helpers, workflows and the install-log header that differ from the skill (gate G14, R-062).
- `verify_release.py`: prints `downloaded N of M listed assets` first; reports release author and title and FAILs a release not created by the workflow or with a title not ending ` v<version>`; `--notes FILE` compares the release body with the notes file and accepts only an appended dated "Known issues" section (R-052, R-059, R-061).
- Gate: version-bump table (patch/minor/major); G0 reads and writes a project's "Release in progress" marker before the first push; G1 checks commit author = owner identity; G1c tag only the release PR's merge commit; G3 notes lint; G3b record lint; G5 test exit code recorded on its own line, `blocked` is not-run with the CI job cited; G13 one-host candidate trial; G14 template drift. Release clones set the owner's git identity; candidates are pushed or bundled before building; synced clones are inspected with `--no-optional-locks` and bridge writes are re-hashed (R-053, R-054, R-057, R-058, R-062, R-064, R-065).
- Publish: approval block states "Immutable releases: ON/OFF" and asks once when OFF; cloud refusals (merge, tag push, publish, branch delete, settings) are expected, from two sources (proxy/API and the session safety classifier) — one combined owner command block; asset download iterates the release JSON and asserts the count; post-publish body edits are append-only; hand-created releases forbidden; web-UI uploads fall under the blob-SHA check (R-052, R-054, R-055, R-056, R-059, R-061, R-062).
- Record: candidate trial counts as the host install when its hash equals the published asset; host replies saved verbatim as evidence files; rows appended with an explicit UTF-8 method and read back; Claude Code directory-marketplace source is the load path; never judge install age by mtime (R-046, R-048, R-057, R-063).
- Host matrix and check prompt: Gemini chat (rejects skills with scripts) vs Gemini Spark (loads them) — the Gemini package targets Spark; install channels and expected load paths for Claude app, ChatGPT web, Grok, Copilot; T3 falls back to the loaded line where frontmatter is hidden; T4 counts a helper version only from shown output (R-042..R-045).
- CI template runs on pushed `release/**` and `v*` branches and on `workflow_dispatch`; actions pinned to current majors by commit SHA: checkout v7.0.1, setup-python v7.0.0, upload-artifact v7.0.2, download-artifact v8.0.2 (R-017, R-047).

Install backups can no longer shadow a new install (R-066; evidence: multi-agent-folder-cleanup 1.7.1 into Codex standalone, 2026-10-09 — all 22 files matched the release and helpers reported 1.7.1, but Codex loaded 1.7.0 from a backup inside `.codex/skills/_backups/`):

- Rule: backups go beside a host's skills folder, never inside it; otherwise rename the backup's `SKILL.md` to `SKILL.backup-not-loaded.md`. All hosts. New hard rule 8 in SKILL.md.
- Install procedure (verify-and-record.md): required duplicate-name check after every install — exactly one `SKILL.md` with the skill's name, at the new version; more than one is a failed install. Owner restarts the host before the in-host check.
- Record: new `Host load` field — `confirmed in host` only after an in-host check (skill view, "reveal in folder", arrival line); otherwise `files verified; host load not checked`. Install-log template gains the column; check prompt T2 fails on a backup path.
- host-matrix.md: scanned folders and backup locations per host (Codex, Antigravity, Claude Code).
- New helper `scripts/check_skill_duplicates.py` (read-only, standard library) with regression tests, including a backup inside the scanned folder.

## 0.2.1

- Gemini Apps package: leave out files without an extension. Gemini's skill uploader rejected 0.2.0 ("The skill folder contains a file with an unsupported file type") because of `templates/gitattributes`; the same package without it was accepted (2026-10-08).
- Builder 1.0.1: new `exclude_no_suffix` key in `packages.json`, plus a host invariant that refuses extensionless files in `gemini-apps-only` regardless of config. Regression test added.
- host-matrix, packaging and INSTALL docs record what Gemini accepted (`.py`, `.json`, `.md`, `.txt`) and the open question of whether scripts run there.

## 0.2.0 (draft, not released)
## 0.2.0

Portfolio and install lessons (R-023..R-035, R-038..R-040; evidence in the project's 2026-10-08 scan and install records):

Expand All @@ -17,7 +41,7 @@ Portfolio and install lessons (R-023..R-035, R-038..R-040; evidence in the proje
- Loaded line now names the SKILL.md path, so checks can spot shadow copies.
- Repo setup: auto-delete head branches and social-preview/Website fields as defaults; `safe.directory` note for tool-owned clones.

## 0.1.0 (draft, not released)
## 0.1.0

- First version: modes Setup, Prepare, Gate, Publish, Verify, Record.
- Draft-first publishing; never replace published bytes; separate owner approvals for each GitHub action.
Expand Down
5 changes: 5 additions & 0 deletions HOST_INSTALL_LOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ Evidence: `observed` (agent saw it), `user-reported`, or a path to a screenshot
| 2026-10-08 23:48-23:50 EDT | 0.2.0 (published) | github-release-0.2.0-claude-code-plugin.zip (4c1a598e1bdf) | Claude Code / owner PC, user scope | Claude Code plugin via directory marketplace `jesseraber-github-release`, source moved to `%USERPROFILE%\.claude\local-marketplaces\github-release` (outside OneDrive; R-041) | Re-point marketplace: hash-verified ZIP extracted to new path, marketplace remove/add, reinstall, file compare, fresh-session load | loaded-smoke-pass; supersedes the 23:31 row's source path; marketplace source 28/28 and cache 0.2.0 28/28 vs ZIP, 0 missing, 0 extra | `Loaded GitHub Release v0.2.0 (SKILL.md at C:\Users\JesseRaber\.claude\local-marketplaces\github-release\skills\github-release\SKILL.md)` | observed; `claude -p` fresh session; session dc8fe785 T005 | Claude session dc8fe785-5fdc-4d28-8476-654133a6caf2 |
| 2026-10-08 ~23:5x EDT | 0.2.0 (published) | github-release-0.2.0-gemini-apps-only.zip (published asset) | Gemini Apps / owner account | Gemini skill upload (UI) | Upload | rejected: "The skill folder contains a file with an unsupported file type. Please remove it and try again." | none | user-reported error text | owner; diagnosed by Claude session 01J7MzndUA7hmG1BaegLPWa3 |
| 2026-10-09 ~00:00 EDT | 0.2.0 test build (not a release asset) | test-A-no-gitattributes.zip (71495d265da0) = 0.2.0 Gemini ZIP minus templates/gitattributes | Gemini Apps / owner account | Gemini skill upload (UI) | Upload | accepted; left installed; behavioral/script-run check not done; to be superseded by the 0.2.1 Gemini asset | not reported | user-reported | owner; test ZIP built by Claude session 01J7MzndUA7hmG1BaegLPWa3 |

## From 0.3.0 (rows with Host load column, per verify-and-record.md)

| Date (TZ) | Version | Asset (sha256 first 12) | Host / account | Channel | Action | Result | Displayed | Evidence | Host load | By |
|---|---|---|---|---|---|---|---|---|---|---|
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ An Agent Skill that teaches AI agents to set up skill repositories and release t
| Claude Code `/plugin install` | `…-claude-code-plugin.zip` |
| Codex / ChatGPT plugin | `…-codex-chatgpt-plugin.zip` |
| Microsoft Copilot agent, Grok | `…-microsoft-copilot-agent-only.zip` |
| Gemini Apps | `…-gemini-apps-only.zip` |
| Gemini Spark (not regular Gemini chat, which rejects skills with scripts) | `…-gemini-apps-only.zip` |
| Opal | `…-opal-only.zip` |

## The flow it enforces

```mermaid
flowchart LR
P[Prepare\ncandidate + checks] --> G[Gate\nG0-G12, read-only]
P[Prepare\ncandidate + checks] --> G[Gate\nG0-G14, read-only]
G -->|owner approves tag| T[Tag pushed\nworkflow builds DRAFT]
T --> V[Verify draft\nbytes == candidate]
V -->|owner approves publish| R[Published\nimmutable]
Expand Down
2 changes: 2 additions & 0 deletions packaging/INSTALL-claude-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ Extract the outer `github-release/` folder to a permanent location, then in Clau
```

Or add the GitHub repository as a marketplace once it exists.

When replacing an older copy, keep the backup outside every folder the app scans for skills (or rename its `SKILL.md` to `SKILL.backup-not-loaded.md`), then restart the app before checking which version it loaded.
2 changes: 2 additions & 0 deletions packaging/INSTALL-codex-chatgpt.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
For a standalone skill upload use `-UNIVERSAL-skill.zip`.

This archive is a skills-only OpenAI plugin: `.codex-plugin/plugin.json` plus `skills/github-release/`. Extract the outer `github-release/` folder to a permanent location and add it to a local plugin marketplace with Plugin Creator in ChatGPT Work mode or Codex.

When replacing an older copy, keep the backup outside every folder the app scans for skills (or rename its `SKILL.md` to `SKILL.backup-not-loaded.md`), then restart the app before checking which version it loaded.
2 changes: 1 addition & 1 deletion packaging/INSTALL-microsoft-copilot.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ This package has no PowerShell files. The Python helpers need only the standard

A successful upload proves only package acceptance. Test with a read-only request such as "Is this repository ready to tag?".

Expected loaded version: **GitHub Release v0.2.1**.
Expected loaded version: **GitHub Release v0.3.0**.
14 changes: 11 additions & 3 deletions packaging/INSTALL-universal.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,21 @@
| Claude Code `/plugin install` (not the Claude app uploader) | `…-claude-code-plugin.zip` |
| Codex / ChatGPT plugin | `…-codex-chatgpt-plugin.zip` |
| Microsoft Copilot agent skill upload, **Grok** | `…-microsoft-copilot-agent-only.zip` |
| Gemini Apps skill upload | `…-gemini-apps-only.zip` |
| Gemini Spark skill upload (regular Gemini chat rejects skills with scripts) | `…-gemini-apps-only.zip` |
| Opal skill import | `…-opal-only.zip` |

The folder `github-release/` **is** the skill. Keep `SKILL.md`, `references/`, `scripts/` and `templates/` together.

Host-specific packages leave some files out: Gemini Apps omits the `.yml` workflow templates (precaution) and `templates/gitattributes` (Gemini rejects files without an extension); Opal keeps only `SKILL.md` and `references/*.md`, so the helper scripts and templates are not available there.

## Updating an installed copy

Many apps (Codex observed) load **every** `SKILL.md` anywhere under their skills folder. Never keep the old copy inside that folder: move it beside it (for example `~/.codex/skill-backups/`), or rename its `SKILL.md` to `SKILL.backup-not-loaded.md`. Then check that only one copy carries the name and restart the app:

```bash
python github-release/scripts/check_skill_duplicates.py ~/.codex/skills --name github-release --expect-version 0.3.0
```

## Claude app

Settings -> Capabilities -> Skills -> Upload skill, with the Universal ZIP.
Expand All @@ -25,11 +33,11 @@ Point the system prompt or context loader at `github-release/SKILL.md`.

> Is my skill repository ready to tag v1.0.0?

A loaded skill states **Mode: Gate** first and reports **Loaded GitHub Release v0.2.1 (SKILL.md at <path>)**.
A loaded skill states **Mode: Gate** first and reports **Loaded GitHub Release v0.3.0 (SKILL.md at <path>)**.

## Requirements

Python 3.8+, standard library only, for `scripts/check_versions.py` and `scripts/verify_release.py`. Both are read-only and need no network.
Python 3.8+, standard library only, for `scripts/check_versions.py`, `scripts/verify_release.py` and `scripts/check_skill_duplicates.py`. All are read-only and need no network.

## Verify what you downloaded

Expand Down
Loading
Loading