diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index e911226..da8614f 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "github-release", - "version": "0.1.0", + "version": "0.2.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", diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 9f6bef7..ba17619 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "github-release", - "version": "0.1.0", + "version": "0.2.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": { diff --git a/.github/social-preview.png b/.github/social-preview.png new file mode 100644 index 0000000..6f9ec11 Binary files /dev/null and b/.github/social-preview.png differ diff --git a/CHANGELOG.md b/CHANGELOG.md index e2e2ab5..2aff8fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 0.2.0 (draft, not released) + +Portfolio and install lessons (R-023..R-035, R-038..R-040; evidence in the project's 2026-10-08 scan and install records): + +- Gate: G0 release-lane-free (one release in flight; expected-main-SHA refusal); fresh clone outside synced folders; G8b executable-bit drift; G8c tag-kind consistency; G11 out-of-repo touch points (`check_versions.py --extra-root`, e.g. a wiki clone); G12 lightweight visual check before public pushes. +- Publish: connector/API pushes can alter bytes — compare pushed blob SHAs with `git hash-object` before the PR; never rename a branch with an open PR (replacement-PR procedure); `--force-with-lease` scope; delete the merged release branch with a recovery SHA after publishing. +- Host matrix: install channels per host (they drift independently; read installed files, never catalog caches); Microsoft Copilot split into Agent Builder vs chat attachment with the ZIP-root question documented open (W-015); Opal/Manus/Copilot sandboxes are review-only — no git work there. +- Record: install procedure (backup with manifest, file-for-file compare incl. zero extra files); `Channel` column in HOST_INSTALL_LOG; new `templates/HOST_INSTALL_CHECK_PROMPT.md` and `templates/NEXT_AGENT_RELEASE_PROMPT.md`. +- 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) - First version: modes Setup, Prepare, Gate, Publish, Verify, Record. diff --git a/HOST_INSTALL_LOG.md b/HOST_INSTALL_LOG.md index 40df2d0..8d54a07 100644 --- a/HOST_INSTALL_LOG.md +++ b/HOST_INSTALL_LOG.md @@ -5,3 +5,7 @@ Evidence: `observed` (agent saw it), `user-reported`, or a path to a screenshot | Date (TZ) | Version | Asset (sha256 first 12) | Host / account | Action | Result | Displayed | Evidence | By | |---|---|---|---|---|---|---|---|---| +| 2026-10-08 13:xx-14:22 EDT | 0.1.0 candidate | github-release-0.1.0-UNIVERSAL-skill.zip (7958b5b64ea5) | Codex desktop / local user skills | Install, fresh-session load and routing smoke test | loaded-smoke-pass; install accepted and 22/22 files matched; two earlier subprocess attempts hit an execution-helper setup error, but the next Codex turn loaded v0.1.0 and correctly routed standalone Codex to Universal | `Loaded GitHub Release v0.1.0` | observed; current chat T003 plus failed subprocess sessions 01a11cb2-4292-78d3-8de0-ea32a8175ed4 and 01a11cb2-e502-7e31-a498-d11fdef07400 | Codex session 185e6066-cbb8-4abd-9d1a-394bd0b4fcb5 | +| 2026-10-08 14:xx-14:28 EDT | 0.1.0 candidate | github-release-0.1.0-claude-code-plugin.zip (2d0bf67303c3) | Claude Code 2.1.223 / local user plugin | Install, enable, validate and routing smoke test | loaded-smoke-pass; install accepted, enabled, 26/26 cached files matched, marketplace validation passed, and authenticated routing smoke passed after an earlier expired-OAuth attempt | `Loaded GitHub Release v0.1.0`; selected `-claude-code-plugin` | observed; local CLI output, session T005 | Codex session 185e6066-cbb8-4abd-9d1a-394bd0b4fcb5 | +| 2026-10-08 ~16:2x EDT | 0.1.0 (published) | github-release-0.1.0-claude-code-plugin.zip (f7d0f6bfed75) | Claude Code / local user plugin via marketplace | Reinstall from published release asset (hash-verified vs SHA256SUMS), routing smoke test | loaded-smoke-pass; supersedes candidate-hash row above | `Loaded GitHub Release v0.1.0`; selected `-claude-code-plugin` | observed; CLI output, Claude session 2e2fbf7d | Claude session 2e2fbf7d-1c2d-47a7-82fd-16374ceac5f0 | +| 2026-10-08 ~16:2x EDT | 0.1.0 (published) | github-release-0.1.0-UNIVERSAL-skill.zip (cfffc4ea2d18) | Codex desktop / local user skills | Reinstall from published release asset (hash-verified vs SHA256SUMS) | accepted; 22/22 installed files match archive; behavioral smoke not run (needs a Codex session); supersedes candidate-hash row above | none shown | observed; file-hash comparison, Claude session 2e2fbf7d | Claude session 2e2fbf7d-1c2d-47a7-82fd-16374ceac5f0 | diff --git a/README.md b/README.md index f16df41..8aa0d72 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,17 @@ An Agent Skill that teaches AI agents to set up skill repositories and release t | Gemini Apps | `…-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] + G -->|owner approves tag| T[Tag pushed\nworkflow builds DRAFT] + T --> V[Verify draft\nbytes == candidate] + V -->|owner approves publish| R[Published\nimmutable] + R --> H[Host installs\nlogged per channel] +``` + ## What it enforces - Creating a repository, pushing, tagging, publishing, changing settings and installing into hosts are separate owner approvals. diff --git a/packaging/INSTALL-microsoft-copilot.md b/packaging/INSTALL-microsoft-copilot.md index 2f55cbc..c0497cc 100644 --- a/packaging/INSTALL-microsoft-copilot.md +++ b/packaging/INSTALL-microsoft-copilot.md @@ -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.1.0**. +Expected loaded version: **GitHub Release v0.2.0**. diff --git a/packaging/INSTALL-universal.md b/packaging/INSTALL-universal.md index ebf9d27..464251c 100644 --- a/packaging/INSTALL-universal.md +++ b/packaging/INSTALL-universal.md @@ -25,7 +25,7 @@ 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.1.0**. +A loaded skill states **Mode: Gate** first and reports **Loaded GitHub Release v0.2.0 (SKILL.md at )**. ## Requirements diff --git a/packaging/RELEASE_NOTES_v0.2.0.md b/packaging/RELEASE_NOTES_v0.2.0.md new file mode 100644 index 0000000..09acf99 --- /dev/null +++ b/packaging/RELEASE_NOTES_v0.2.0.md @@ -0,0 +1,12 @@ +# github-release 0.2.0 + +Hardening release from real multi-agent release history: a release-lane gate (one release in flight, expected-SHA refusal), connector byte-drift checks, fresh-clone and sandbox rules, per-host install channels with an install-check prompt template, an install procedure with backups and zero-extra-file verification, out-of-repo version checks (`--extra-root`), and a loaded line that names its own path. See CHANGELOG.md for the full list. + +Verify downloads with `sha256sum -c SHA256SUMS.txt`. + +## Publication boundary + +Installed host copies are updated separately; this release does not change any host by itself. + +--- +Built by [Jesse Raber](https://jesseraber.net) · [YouTube](https://www.youtube.com/@Jesse_Raber) diff --git a/packaging/check_versions.py b/packaging/check_versions.py index d87442c..e1d45d7 100644 --- a/packaging/check_versions.py +++ b/packaging/check_versions.py @@ -22,7 +22,7 @@ import re import sys -__version__ = '0.1.0' +__version__ = '0.2.0' SEMVER = re.compile(r'^\d+\.\d+\.\d+$') TEXT_SUFFIXES = {'.md', '.py', '.json', '.yml', '.yaml', '.txt', '.ps1', '.toml', '.cfg'} SKIP_DIRS = {'.git', '__pycache__', 'node_modules', 'dist'} @@ -75,17 +75,15 @@ def is_history(rel, patterns): return any(fnmatch.fnmatch(rel, pat) for pat in patterns) -def check_previous(repo, cfg, previous, results): - pat = re.compile(r'(?)**. State the mode in the first line. The path lets install checks detect shadow copies and stale channels. ## Choose the mode diff --git a/skills/github-release/references/host-matrix.md b/skills/github-release/references/host-matrix.md index a886c8d..005fff7 100644 --- a/skills/github-release/references/host-matrix.md +++ b/skills/github-release/references/host-matrix.md @@ -18,21 +18,34 @@ Reference: `multi-agent-folder-cleanup` v1.6.1. On 2026-10-08 the author rebuilt | `-opal-only` | Opal import | `SKILL.md` with only `name` + `description` frontmatter; top-level `references/*.md`; LF endings; **no folder entries** | everything else | observed: confirmed by import (repo CHANGELOG 1.5.0); per-file size rejection above ~48–79 KB observed 2026-10-03, approximate | 2026-10-06 | | `-project-rules-optional` | add-on, any host | only if the skill ships optional add-on material | — | repo | 2026-10-06 | +## Install channels per host + +A host can have several install channels that drift to different versions independently (observed on Codex three times: standalone dir vs Personal Plugin at different versions; the standalone dir vanished twice between turns). Rules: record the **Channel** in every install-log row; a version check reads the **installed files** (`SKILL.md` metadata, helper `--version`), never a catalog cache — if a cache is cited, record its mtime. + +| Host | Channels | +|---|---| +| Claude | app skill upload (Settings → Capabilities → Skills); Claude Code plugin (`/plugin install` or marketplace) | +| Codex / ChatGPT | standalone skills dir (`~/.codex/skills/`); Personal Plugin (manual "Upload new version" in the ChatGPT UI — no API); desktop marketplace (cache `~/.codex/cache/remote_plugin_catalog/*.json` is stale evidence) | +| Antigravity | `~/.gemini/config/skills/` from the repository | +| Gemini Apps, Copilot, Grok, Opal | single upload channel each (UI) | + ## Host notes - **Claude app**: upload the Universal ZIP under Settings → Capabilities → Skills (repo-documented path). - **Claude Code**: plugin ZIP or the repository marketplace (`.claude-plugin/marketplace.json`). - **Codex / ChatGPT**: plugin via Plugin Creator; standalone skill via the Universal ZIP. -- **Microsoft Copilot**: the sandbox has no direct network; packaged scripts must not need it (repo). +- **Microsoft Copilot**: two distinct surfaces — **Agent Builder** (Configure → Skills, takes the agent ZIP) and **chat attachment** (observed 2026-10-07 rejecting the ZIP but accepting Markdown files attached directly). The documented Agent Builder layout is root-level `SKILL.md`, while this package nests it under `/`; whether Agent Builder requires the root layout is an **open question** (pending test, ai-prompting W-015) — do not change the package until that test runs. The sandbox has no direct network; packaged scripts must not need it (repo). - **Grok**: sandbox is POSIX; OneDrive/SharePoint hydration cannot be checked there (repo). - **Gemini Apps**: never rename or obfuscate code to pass the scan; omit and document. - **Opal**: references must be flat for the skill to work there; keep essential instructions in `SKILL.md` and top-level `references/`. - **Antigravity**: installs from the GitHub repository (repo-documented; untested here). - **Local models (Ollama, LM Studio, llama.cpp front-ends)**: point the system prompt or context loader at `SKILL.md`. - **Manus**: not in v1; no package rules recorded. +- **Sandboxed review hosts (Opal, Manus, Copilot)**: review-only for releases — never do git work there. Observed: an Opal workspace reset wiped a clone before it could push (2026-10-07). ## Open questions - Does Gemini accept `.yml` templates? (github-release omits them as a precaution.) - Is the Opal size limit per file or per package, and what is the exact threshold? - Which hosts load an `AGENTS.md` from a project folder? Not a packaging rule; tracked in the project. +- Does Copilot Agent Builder require root-level `SKILL.md` in the ZIP? (See Microsoft Copilot host note; pending W-015 test.) diff --git a/skills/github-release/references/publish.md b/skills/github-release/references/publish.md index 84cefc8..32dce6e 100644 --- a/skills/github-release/references/publish.md +++ b/skills/github-release/references/publish.md @@ -32,6 +32,8 @@ Observed 2026-10-08 (multi-agent-folder-cleanup v1.6.2 pilot): a cloud agent ses When a step is refused from the cloud, do not retry through another route; hand the owner the exact command and wait. The refusal changes where the step runs, never the approval for it. +Prefer `git push` from a real clone. If a connector/API push is the only route, it can **alter file bytes** (observed 2026-10-06: a connector decoded `\u` escapes in a pushed Python file into literal characters; caught only by hash comparison). After any non-git push, before opening the PR, compare every pushed blob with the local file: `gh api repos///contents/?ref= --jq .sha` must equal `git hash-object ` locally. + - Typical flow: feature PR (CI green) → merge → release PR with version bump (CI green) → merge → annotated tag on the merge commit. ```bash @@ -77,10 +79,12 @@ With immutable releases enabled in repository settings, assets and the tag are l - Download the published assets again into the project's local release folder and run `verify_release.py --expect-published --expect-immutable` against the published metadata (`gh api repos///releases/tags/v1.2.0`). - Published releases do not update any host. Each host install is a separate action and a separate install-log row ([verify-and-record.md](verify-and-record.md)). +- Delete the merged release branch (approval), recording its tip SHA for recovery; list any other remote branches whose tips are ancestors of `main` for a separate owner decision. Repositories with "automatically delete head branches" enabled (repo-setup default) skip the first step. - Update the project's records: tracker, quick context, index. ## Never +- Renaming a branch that has an open PR — GitHub closes the PR and it cannot be reopened (observed 2026-10-08). A renumbered candidate gets a new branch and a replacement PR; close the old PR with a comment linking forward. `--force-with-lease` only on your own un-reviewed branch, never on `main`. - `gh release upload --clobber` or deleting and re-uploading a published asset. - `--generate-notes` as a substitute for the reviewed notes file. - Publishing from a session that cannot show the verify result. diff --git a/skills/github-release/references/release-gate.md b/skills/github-release/references/release-gate.md index 99d3776..1322068 100644 --- a/skills/github-release/references/release-gate.md +++ b/skills/github-release/references/release-gate.md @@ -26,6 +26,8 @@ Typical touch points: ## 2. Build the local candidate +Build from a **fresh clone of the canonical repository, outside any synced folder** (`%USERPROFILE%\dev\` or a temp dir, never inside OneDrive/SharePoint), cloned with `core.autocrlf=false`. Observed failures from clones inside synced project folders: `.git/index.lock` conflicts, read-only dirs that cannot be removed, stale copies that no longer match the remote (2026-10-06, folder-cleanup and cabinet-load-optimizer records). A project folder's copy of the repo is documentation, not a build source. + ```bash python packaging/build_packages.py ../candidate-1.2.0 --status "local candidate, not published" ``` @@ -40,7 +42,8 @@ Run in order. Stop at the first fail. | # | Check | How | |---|---|---| -| G1 | Working tree clean; candidate built from the exact commit to be tagged | `git status --porcelain` empty; record `git rev-parse HEAD` | +| G0 | Release lane is free: no other open release PR; no `v-*` branch by another writer; the expected `main` SHA (from the handoff prompt, when one exists) still equals the remote | `gh pr list --state open`; `git ls-remote --heads origin`; `git ls-remote origin main`. One release in flight per repo — observed collision 2026-10-08: two agents built "v1.6.2" from different bases; the loser's PR had to be renumbered | +| G1 | Working tree clean; candidate built from the exact commit to be tagged, in a fresh clone outside synced folders (section 2) | `git status --porcelain` empty; record `git rev-parse HEAD` | | G2 | Versions agree; no unintended stale versions | `check_versions.py` exit 0 | | G3 | Release notes file exists for this version | `check_versions.py` | | G4 | Workflow is draft-first and never clobbers | `check_versions.py --workflow .github/workflows/release.yml` | @@ -49,13 +52,18 @@ Run in order. Stop at the first fail. | G6 | Skill validators pass (frontmatter, name matches folder, description <= 1024 chars; OpenAI skill/plugin validators pinned by commit) | CI or local run | | G7 | Candidate built, every archive byte-verified, packaged scripts smoke-tested | builder output; no warnings left unexplained | | G8 | Line endings: `.md`/`.py` are LF in the repository (`.gitattributes`); no CRLF in the Opal package | builder asserts Opal; `git ls-files --eol` | +| G8b | No unintended executable bits (mode drift between OSes is real: 3 files went 100755 on Linux while Windows kept 100644, folder-cleanup v1.6.1) | `git ls-files -s \| grep ^100755` lists only intended scripts | +| G8c | Tag kind matches the repository's convention — mixed lightweight/annotated tags break `v^{}` peeling and citation | `git cat-file -t v` (expect `tag` for annotated) | | G9 | Each host package matches host-matrix.md rules for this release | compare package listing with Table A | | G10 | Tag does not exist yet locally or remotely | `git ls-remote --tags origin vX.Y.Z` empty | +| G11 | Out-of-repo version touch points: the wiki (`.wiki.git`), marketplace listings and host store descriptions name the new version or are explicitly listed as not-run | `check_versions.py --previous X.Y.Z --extra-root ../wiki-clone`; anything unreachable gets a `not-run` line, never a silent pass | +| G12 | Visual check before a public push: README renders (tables, any diagram), links and image alt text are current, and the social-preview image is not contradicted by this release's content. Update assets only when content changed | open the rendered README on the branch; repository settings → social preview (owner) | ## 4. Windows lessons (observed) - Clone with `core.autocrlf=false`; `git am` failed with `autocrlf=true`. The template `.gitattributes` forces LF for all text so Windows and Linux builds match. -- A local candidate is only comparable to the CI build if both use the same builder version; the builder pins ZIP metadata (`create_system`) so the OS does not change the bytes. Different zlib versions could still change compressed bytes; if a candidate differs only that way, compare member contents and record it. +- A local candidate is only comparable to the CI build if both use the same builder version; the builder pins ZIP metadata (`create_system`) so the OS does not change the bytes. Different zlib versions could still change compressed bytes; if a candidate differs only that way, compare member contents and record it. When the owner's machine and CI run different OSes and bytes still differ, the comparable candidate is the **CI artifact of the gated commit** (download it from the CI run) rather than the laptop build. +- If `git status` reports "dubious ownership" (clones inside tool-owned dirs such as `~/.codex/...`), use `git -c safe.directory=` for that clone only; never set `safe.directory=*`. - Child-process stdout is cp1252 on Windows; helpers must write UTF-8 safely. - Do not write shared text files with Windows PowerShell 5.1 (BOM/ANSI). Preserve a BOM only where a file already needs one. - Do not retype large files through chat tools; copy or apply patches. diff --git a/skills/github-release/references/repo-setup.md b/skills/github-release/references/repo-setup.md index e44523a..ca4eb19 100644 --- a/skills/github-release/references/repo-setup.md +++ b/skills/github-release/references/repo-setup.md @@ -29,6 +29,8 @@ Use in **Setup**. Draft everything locally first; creating the GitHub repository - `.gitattributes`: LF for `.md`, `.py`, `.json`, `.yml`; leave `.ps1` as the repository already has it. - Actions pinned by full commit SHA; external validators downloaded at pinned commits. - Branch protection on `main`: PR required, CI required. +- "Automatically delete head branches" ON (settings change, approval) — stale merged branches otherwise accumulate (9 observed on one repo before a manual tidy). +- Social-preview image (Settings → General, 1280×640) and repository Website field set before wide sharing; both are owner settings actions. - Releases: enable **immutable releases** in repository settings (owner approval; it is a settings change). - CI on Ubuntu, and on Windows when helpers are meant to run on Windows. - README opens with a "which ZIP do I download?" table generated from host-matrix.md. diff --git a/skills/github-release/references/verify-and-record.md b/skills/github-release/references/verify-and-record.md index 9647aab..bde4055 100644 --- a/skills/github-release/references/verify-and-record.md +++ b/skills/github-release/references/verify-and-record.md @@ -17,6 +17,13 @@ Exit 0 only when every requested check ran and passed. Quote its output in the r Spot-check content too: open one packaged `SKILL.md` per host package and confirm the version line. +## Install procedure (replacing an existing host copy) + +1. Back up the currently installed copy with a SHA-256 manifest and `sha256sum -c` the backup before touching anything. +2. Verify the new archive against the release `SHA256SUMS.txt`. +3. Replace, then compare the installed tree to the archive **file for file, including the count of extra files (must be 0)** — a hash-match of present files does not catch leftovers from the old version. +4. Confirm the loaded report line and its path in a fresh host session (`templates/HOST_INSTALL_CHECK_PROMPT.md` generates the read-only check to paste into the host); the path in the line is what exposes shadow copies in plugin caches and stale channels. + ## Record host installs Each skill repository keeps `HOST_INSTALL_LOG.md` (template in `templates/`). Append one row per attempt; never edit old rows except to add a `superseded` row that points forward. @@ -26,6 +33,7 @@ Each skill repository keeps `HOST_INSTALL_LOG.md` (template in `templates/`). Ap | Date | ISO date and time with timezone, when known | | Version / Asset | exact archive name; first 12 of its SHA-256 | | Host / account | e.g. Gemini Apps, personal; Copilot, tenant name | +| Channel | which install channel on that host (see host-matrix.md "Install channels per host") | | Action | upload, update, remove, smoke-test | | Result | `accepted`, `rejected`, `accepted-not-loaded`, `loaded-smoke-pass`, `loaded-smoke-fail`, `superseded` | | Displayed | the name/version the host shows, or `none shown` | diff --git a/skills/github-release/scripts/check_versions.py b/skills/github-release/scripts/check_versions.py index d87442c..e1d45d7 100644 --- a/skills/github-release/scripts/check_versions.py +++ b/skills/github-release/scripts/check_versions.py @@ -22,7 +22,7 @@ import re import sys -__version__ = '0.1.0' +__version__ = '0.2.0' SEMVER = re.compile(r'^\d+\.\d+\.\d+$') TEXT_SUFFIXES = {'.md', '.py', '.json', '.yml', '.yaml', '.txt', '.ps1', '.toml', '.cfg'} SKIP_DIRS = {'.git', '__pycache__', 'node_modules', 'dist'} @@ -75,17 +75,15 @@ def is_history(rel, patterns): return any(fnmatch.fnmatch(rel, pat) for pat in patterns) -def check_previous(repo, cfg, previous, results): - pat = re.compile(r'(? fields, paste into the host, import the report as evidence + +Read-only: the host session must change nothing. Import its report into the install log as `Evidence: user-reported` (or `observed` if you watched it run). A failed check is a log row too. + +```text +Read-only install check for the skill, v. Run these tests and report each as PASS/FAIL with what you saw. Change no files. + +T1 Arrival line: invoke the skill; quote its loaded report line exactly, including the SKILL.md path it names. +T2 Path check: does that path match the expected install location ? Name any OTHER copies of this skill you can see (plugin caches, second channels, older dirs). +T3 Version: quote the `metadata.version` line from the loaded SKILL.md file itself (not from a catalog or cache). +T4 Helpers: run each packaged helper with `--version` and quote the output (skip on hosts without a shell; report SKIPPED). +T5 File count: count files in the installed skill folder; expected for this package. +T6 Behavior smoke: answer from the skill only — . +Report: one line per test, then the loaded line verbatim. +``` + +Expected answers come from the release's host matrix and package listing; fill them before sending, and never mark a test PASS that the host did not actually run. diff --git a/skills/github-release/templates/HOST_INSTALL_LOG.md b/skills/github-release/templates/HOST_INSTALL_LOG.md index a5ccda6..64ebfbd 100644 --- a/skills/github-release/templates/HOST_INSTALL_LOG.md +++ b/skills/github-release/templates/HOST_INSTALL_LOG.md @@ -3,5 +3,5 @@ Append-only. One row per attempt. Never edit an old row; add a `superseded` row instead. Evidence: `observed` (agent saw it), `user-reported`, or a path to a screenshot / error text. -| Date (TZ) | Version | Asset (sha256 first 12) | Host / account | Action | Result | Displayed | Evidence | By | -|---|---|---|---|---|---|---|---|---| +| Date (TZ) | Version | Asset (sha256 first 12) | Host / account | Channel | Action | Result | Displayed | Evidence | By | +|---|---|---|---|---|---|---|---|---|---| diff --git a/skills/github-release/templates/NEXT_AGENT_RELEASE_PROMPT.md b/skills/github-release/templates/NEXT_AGENT_RELEASE_PROMPT.md new file mode 100644 index 0000000..826eb8b --- /dev/null +++ b/skills/github-release/templates/NEXT_AGENT_RELEASE_PROMPT.md @@ -0,0 +1,19 @@ +# Release handoff prompt — fill every field before sending + +Copy this into the next agent's chat to hand off a release. The receiving agent runs gate G0 against these values FIRST and refuses on any mismatch — a handoff without expected hashes has produced stale-base release PRs (observed); with them, drift was caught before any work (observed 2026-10-08). + +```text +Release task: build and gate v in /. One release lane: refuse if any other release PR or v*-branch is open. + +Expected state — verify before any work, stop on mismatch: +- main SHA: +- open release PRs: none (or: # is mine to continue) +- tag v: must not exist +- tracker/register file: sha256 + +Mode: Prepare + Gate only. Tag, draft, publish and settings are separate owner approvals (publish.md). +Candidate goes to: . +Stop points: after the gate report; before anything that changes GitHub. +``` + +Keep the filled copy with the session records; the receiving agent quotes which expected values it verified. diff --git a/tests/test_helpers.py b/tests/test_helpers.py index d18b38d..8319057 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -133,6 +133,21 @@ def test_release_list_json_rejected(self): self.assertEqual(r.returncode, 1) self.assertIn('ONE release object', r.stdout) + def test_extra_root_previous_scan(self): + with tempfile.TemporaryDirectory() as td: + wiki = Path(td) / 'wiki' + wiki.mkdir() + (wiki / 'Home.md').write_text('Current release: v0.0.1\n', encoding='utf-8') + v = json.loads((REPO / '.claude-plugin/plugin.json').read_text())['version'] + r = run(SKILL / 'scripts/check_versions.py', '--repo', REPO, '--version', v, + '--previous', '0.0.1', '--extra-root', wiki) + self.assertNotEqual(r.returncode, 0) + self.assertIn('wiki/Home.md', r.stdout) + r = run(SKILL / 'scripts/check_versions.py', '--repo', REPO, '--version', v, + '--previous', '0.0.1', '--extra-root', Path(td) / 'missing') + self.assertNotEqual(r.returncode, 0) + self.assertIn('extra root not found', r.stdout) + def test_helper_versions(self): for s in ('check_versions.py', 'verify_release.py'): r = run(SKILL / 'scripts' / s, '--version')