diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index da8614f..17f6f86 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "github-release", - "version": "0.2.0", + "version": "0.2.1", "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 ba17619..4d599f2 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "github-release", - "version": "0.2.0", + "version": "0.2.1", "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/CHANGELOG.md b/CHANGELOG.md index 2aff8fc..962b916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.2.1 (draft, not released) + +- 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) Portfolio and install lessons (R-023..R-035, R-038..R-040; evidence in the project's 2026-10-08 scan and install records): diff --git a/HOST_INSTALL_LOG.md b/HOST_INSTALL_LOG.md index 8d54a07..dda23a9 100644 --- a/HOST_INSTALL_LOG.md +++ b/HOST_INSTALL_LOG.md @@ -9,3 +9,13 @@ Evidence: `observed` (agent saw it), `user-reported`, or a path to a screenshot | 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 | + +## From 0.2.0 (rows with Channel column, per verify-and-record.md) + +| Date (TZ) | Version | Asset (sha256 first 12) | Host / account | Channel | Action | Result | Displayed | Evidence | By | +|---|---|---|---|---|---|---|---|---|---| +| 2026-10-08 23:31-23:36 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` (re-pointed to session dc8fe785 scratch `claude-plugin-stage-0.2.0`) | Update 0.1.0 → 0.2.0: backup w/ manifest (26 files, 0 mismatch), hash-verify vs SHA256SUMS, marketplace remove/add, install, file compare, fresh-session load | loaded-smoke-pass; supersedes 0.1.0 (published) Claude Code row; cache 0.2.0 28/28 match, 0 missing, 0 extra; staging dir identical to cache (28/28); old cache dir 0.1.0 still present (not deleted) | `Loaded GitHub Release v0.2.0 (SKILL.md at …\scratch\dc8fe785-…\claude-plugin-stage-0.2.0\github-release\skills\github-release\SKILL.md)` | observed; `claude -p` fresh session; session dc8fe785 T002 | Claude session dc8fe785-5fdc-4d28-8476-654133a6caf2 | +| 2026-10-08 23:42 EDT | 0.2.0 (published) | github-release-0.2.0-UNIVERSAL-skill.zip (657833803ab8) | Codex desktop / owner PC | standalone skills dir `~/.codex/skills/github-release` | Update 0.1.0 → 0.2.0: backup w/ manifest (22 files, 0 mismatch); old dir moved to session dc8fe785 scratch/replaced; hash-verified ZIP extracted; file compare | accepted; 24/24 match ZIP, 0 missing, 0 extra; only github-release SKILL.md under ~/.codex; fresh-session load NOT RUN (no `codex` CLI on PATH) — owner check prompt in Handoffs/2026-10-08_github-release-0.2.0-host-install-checklist.md; supersedes 0.1.0 (published) Codex row | none shown (pending owner check) | observed; file-hash comparison, session dc8fe785 T003 | Claude session dc8fe785-5fdc-4d28-8476-654133a6caf2 | +| 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 | diff --git a/packaging/INSTALL-microsoft-copilot.md b/packaging/INSTALL-microsoft-copilot.md index c0497cc..ff1b799 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.2.0**. +Expected loaded version: **GitHub Release v0.2.1**. diff --git a/packaging/INSTALL-universal.md b/packaging/INSTALL-universal.md index 464251c..a624c12 100644 --- a/packaging/INSTALL-universal.md +++ b/packaging/INSTALL-universal.md @@ -11,7 +11,7 @@ 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); Opal keeps only `SKILL.md` and `references/*.md`, so the helper scripts and templates are not available there. +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. ## Claude app @@ -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.2.0 (SKILL.md at )**. +A loaded skill states **Mode: Gate** first and reports **Loaded GitHub Release v0.2.1 (SKILL.md at )**. ## Requirements diff --git a/packaging/RELEASE_NOTES_v0.2.1.md b/packaging/RELEASE_NOTES_v0.2.1.md new file mode 100644 index 0000000..1a9268d --- /dev/null +++ b/packaging/RELEASE_NOTES_v0.2.1.md @@ -0,0 +1,12 @@ +# github-release 0.2.1 + +Patch: the Gemini Apps package no longer contains files without an extension. Gemini's skill uploader rejected 0.2.0 with "The skill folder contains a file with an unsupported file type" (`templates/gitattributes`). The builder now refuses extensionless files in Gemini packages regardless of `packages.json`. Other packages are unchanged apart from the version number. + +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/build_packages.py b/packaging/build_packages.py index 96df86b..63ef4a2 100644 --- a/packaging/build_packages.py +++ b/packaging/build_packages.py @@ -25,7 +25,7 @@ import tempfile import zipfile -__version__ = '1.0.0' +__version__ = '1.0.1' STAMP = (2026, 1, 1, 0, 0, 0) REPO = Path(__file__).resolve().parents[1] OPAL_SIZE_WARN = 48 * 1024 # observed Opal per-file rejection ~48-79 KB (approximate, 2026-10-03) @@ -34,6 +34,9 @@ 'microsoft-copilot-agent-only': ('.ps1',), 'gemini-apps-only': ('.ps1', '.yml', '.yaml'), } +# Hosts whose uploader rejects files without an extension (Gemini Apps: observed 2026-10-08, +# "unsupported file type" on templates/gitattributes; same package without it was accepted). +HOST_NO_EXTENSIONLESS = {'gemini-apps-only'} # Package names that may appear in packages.json. A renamed package must be added here # deliberately, so it cannot silently escape its host invariants. KNOWN_PACKAGES = {'UNIVERSAL-skill', 'claude-code-plugin', 'codex-chatgpt-plugin', @@ -83,7 +86,8 @@ def source_files(skill_dir): def excluded(rel, spec): return (rel.startswith(tuple(spec.get('exclude_prefix', []))) or rel.endswith(tuple(spec.get('exclude_suffix', []))) - or rel in spec.get('exclude_files', [])) + or rel in spec.get('exclude_files', []) + or (spec.get('exclude_no_suffix') and '.' not in rel.rsplit('/', 1)[-1])) def opal_skill_md(data): @@ -163,6 +167,9 @@ def verify(archive, flavor, spec, items, dirs, version, smoke, warnings, smoked) if flavor == suffix: bad = [n for n in files_only if n.lower().endswith(banned)] check(not bad, ('host invariant: %s must not contain %s' % (flavor, banned), bad)) + if flavor in HOST_NO_EXTENSIONLESS: + bad = [n for n in files_only if '.' not in n.rsplit('/', 1)[-1]] + check(not bad, ('host invariant: %s must not contain files without an extension' % flavor, bad)) if spec['layout'] == 'opal': check(all(n.endswith('.md') for n in files_only)) check(not any(n.endswith('/') for n in listed)) diff --git a/packaging/check_versions.py b/packaging/check_versions.py index e1d45d7..0ade3e6 100644 --- a/packaging/check_versions.py +++ b/packaging/check_versions.py @@ -22,7 +22,7 @@ import re import sys -__version__ = '0.2.0' +__version__ = '0.2.1' 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'} diff --git a/packaging/packages.json b/packaging/packages.json index 14f7881..51475b8 100644 --- a/packaging/packages.json +++ b/packaging/packages.json @@ -6,7 +6,7 @@ "claude-code-plugin": {"layout": "plugin", "metadata": ".claude-plugin", "add": {"LICENSE": "LICENSE", "README.md": "README.md", "CHANGELOG.md": "CHANGELOG.md", "INSTALL.md": "packaging/INSTALL-claude-code.md"}}, "codex-chatgpt-plugin": {"layout": "plugin", "metadata": ".codex-plugin", "add": {"LICENSE": "LICENSE", "README.md": "README.md", "CHANGELOG.md": "CHANGELOG.md", "INSTALL.md": "packaging/INSTALL-codex-chatgpt.md"}}, "microsoft-copilot-agent-only": {"layout": "skill", "exclude_suffix": [".ps1"], "add": {"LICENSE.txt": "LICENSE", "INSTALL.md": "packaging/INSTALL-microsoft-copilot.md"}}, - "gemini-apps-only": {"layout": "skill", "exclude_prefix": ["agents/"], "exclude_suffix": [".ps1", ".yaml", ".yml"], "add": {"LICENSE.txt": "LICENSE"}}, + "gemini-apps-only": {"layout": "skill", "exclude_prefix": ["agents/"], "exclude_suffix": [".ps1", ".yaml", ".yml"], "exclude_no_suffix": true, "add": {"LICENSE.txt": "LICENSE"}}, "opal-only": {"layout": "opal"} } } diff --git a/packaging/verify_release.py b/packaging/verify_release.py index efa37ab..4a4845b 100644 --- a/packaging/verify_release.py +++ b/packaging/verify_release.py @@ -19,7 +19,7 @@ import sys import zipfile -__version__ = '0.2.0' +__version__ = '0.2.1' SUMS = 'SHA256SUMS.txt' diff --git a/skills/github-release/SKILL.md b/skills/github-release/SKILL.md index 8cf605c..f868dbf 100644 --- a/skills/github-release/SKILL.md +++ b/skills/github-release/SKILL.md @@ -3,7 +3,7 @@ name: github-release description: Create GitHub repositories for Agent Skills and release them right the first time - version agreement, a pre-tag gate, deterministic host-specific packages (Claude app, Claude Code, Codex/ChatGPT, Microsoft Copilot, Grok, Gemini Apps, Opal, Antigravity, local models), draft-first publishing, verification of published assets against the local build, and a dated per-host install log. Use whenever an agent prepares, tags, publishes, re-packages or verifies a skill release, sets up a skill repository or release workflow, decides which ZIP a host needs, or records that a skill was uploaded or installed somewhere. license: MIT metadata: - version: "0.2.0" + version: "0.2.1" repository: https://github.com/JesseRaber/github-release --- @@ -11,7 +11,7 @@ metadata: A release is right the first time when the bytes you tested are the bytes people download, every host gets a package it accepts, and the record says which host has which version and how anyone knows. -Report as: **Loaded GitHub Release v0.2.0 (SKILL.md at )**. State the mode in the first line. The path lets install checks detect shadow copies and stale channels. +Report as: **Loaded GitHub Release v0.2.1 (SKILL.md at )**. 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 005fff7..5e9de1d 100644 --- a/skills/github-release/references/host-matrix.md +++ b/skills/github-release/references/host-matrix.md @@ -36,7 +36,7 @@ A host can have several install channels that drift to different versions indepe - **Codex / ChatGPT**: plugin via Plugin Creator; standalone skill via the Universal ZIP. - **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. +- **Gemini Apps**: the skill uploader rejects a folder containing a file without an extension ("The skill folder contains a file with an unsupported file type", observed 2026-10-08 on `templates/gitattributes`); the same package without that file was accepted, including `.py`, `.json`, `.md` and `.txt`. Whether packaged `.py` scripts can run there is untested. Never rename or obfuscate code to pass the scan; omit and document. Add-on files must keep an extension (`LICENSE.txt`, not `LICENSE`). - **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`. @@ -45,7 +45,8 @@ A host can have several install channels that drift to different versions indepe ## Open questions -- Does Gemini accept `.yml` templates? (github-release omits them as a precaution.) +- Does Gemini accept `.yml` templates? (github-release omits them as a precaution; `.py`/`.json`/`.md`/`.txt` were accepted 2026-10-08.) +- Can packaged `.py` scripts execute inside Gemini Apps? - 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/packaging.md b/skills/github-release/references/packaging.md index 9692016..1ae8ee2 100644 --- a/skills/github-release/references/packaging.md +++ b/skills/github-release/references/packaging.md @@ -11,7 +11,7 @@ Copy `templates/build_packages.py` to `packaging/build_packages.py` and `templat Fidelity check (2026-10-08, by the author, repeatable): with a `packages.json` describing multi-agent-folder-cleanup's seven packages (six like `templates/packages.json` plus a `subtree` add-on), this builder reproduced all seven published v1.6.1 ZIPs byte-for-byte against GitHub's asset digests. -Independent of `packages.json`, the builder also refuses `.ps1` in Copilot packages, `.ps1`/`.yml`/`.yaml` in Gemini packages and non-Markdown in Opal packages, and fails if a script named in `smoke_version_scripts` is never packaged. +Independent of `packages.json`, the builder also refuses `.ps1` in Copilot packages, `.ps1`/`.yml`/`.yaml` and files without an extension (e.g. `gitattributes`, `LICENSE`) in Gemini packages and non-Markdown in Opal packages, and fails if a script named in `smoke_version_scripts` is never packaged. ## packages.json @@ -21,7 +21,7 @@ Independent of `packages.json`, the builder also refuses `.ps1` in Copilot packa "smoke_version_scripts": ["my_helper.py"], "packages": { "UNIVERSAL-skill": {"layout": "skill", "add": {"LICENSE.txt": "LICENSE", "INSTALL.md": "packaging/INSTALL-universal.md"}}, - "gemini-apps-only": {"layout": "skill", "exclude_suffix": [".ps1", ".yaml", ".yml"], "exclude_prefix": ["agents/"]} + "gemini-apps-only": {"layout": "skill", "exclude_suffix": [".ps1", ".yaml", ".yml"], "exclude_no_suffix": true, "exclude_prefix": ["agents/"]} } } ``` @@ -30,6 +30,7 @@ Independent of `packages.json`, the builder also refuses `.ps1` in Copilot packa |---|---| | `layout` | `skill` (folder `/…`), `plugin` (`/skills//…` + `metadata` manifests), `opal` (flat Markdown, no folder entries), `subtree` (`from` → `to`, for add-ons) | | `exclude_prefix` / `exclude_suffix` / `exclude_files` | paths relative to the skill folder to leave out | +| `exclude_no_suffix` | `true` leaves out skill files whose name has no extension (required for Gemini Apps) | | `add` | extra files: archive path inside `/` → repository path | | `metadata` | plugin manifest folder (`.claude-plugin`, `.codex-plugin`) | | `folder_entries` | override; default true except `opal` | diff --git a/skills/github-release/scripts/check_versions.py b/skills/github-release/scripts/check_versions.py index e1d45d7..0ade3e6 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.2.0' +__version__ = '0.2.1' 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'} diff --git a/skills/github-release/scripts/verify_release.py b/skills/github-release/scripts/verify_release.py index efa37ab..4a4845b 100644 --- a/skills/github-release/scripts/verify_release.py +++ b/skills/github-release/scripts/verify_release.py @@ -19,7 +19,7 @@ import sys import zipfile -__version__ = '0.2.0' +__version__ = '0.2.1' SUMS = 'SHA256SUMS.txt' diff --git a/skills/github-release/templates/build_packages.py b/skills/github-release/templates/build_packages.py index 96df86b..63ef4a2 100644 --- a/skills/github-release/templates/build_packages.py +++ b/skills/github-release/templates/build_packages.py @@ -25,7 +25,7 @@ import tempfile import zipfile -__version__ = '1.0.0' +__version__ = '1.0.1' STAMP = (2026, 1, 1, 0, 0, 0) REPO = Path(__file__).resolve().parents[1] OPAL_SIZE_WARN = 48 * 1024 # observed Opal per-file rejection ~48-79 KB (approximate, 2026-10-03) @@ -34,6 +34,9 @@ 'microsoft-copilot-agent-only': ('.ps1',), 'gemini-apps-only': ('.ps1', '.yml', '.yaml'), } +# Hosts whose uploader rejects files without an extension (Gemini Apps: observed 2026-10-08, +# "unsupported file type" on templates/gitattributes; same package without it was accepted). +HOST_NO_EXTENSIONLESS = {'gemini-apps-only'} # Package names that may appear in packages.json. A renamed package must be added here # deliberately, so it cannot silently escape its host invariants. KNOWN_PACKAGES = {'UNIVERSAL-skill', 'claude-code-plugin', 'codex-chatgpt-plugin', @@ -83,7 +86,8 @@ def source_files(skill_dir): def excluded(rel, spec): return (rel.startswith(tuple(spec.get('exclude_prefix', []))) or rel.endswith(tuple(spec.get('exclude_suffix', []))) - or rel in spec.get('exclude_files', [])) + or rel in spec.get('exclude_files', []) + or (spec.get('exclude_no_suffix') and '.' not in rel.rsplit('/', 1)[-1])) def opal_skill_md(data): @@ -163,6 +167,9 @@ def verify(archive, flavor, spec, items, dirs, version, smoke, warnings, smoked) if flavor == suffix: bad = [n for n in files_only if n.lower().endswith(banned)] check(not bad, ('host invariant: %s must not contain %s' % (flavor, banned), bad)) + if flavor in HOST_NO_EXTENSIONLESS: + bad = [n for n in files_only if '.' not in n.rsplit('/', 1)[-1]] + check(not bad, ('host invariant: %s must not contain files without an extension' % flavor, bad)) if spec['layout'] == 'opal': check(all(n.endswith('.md') for n in files_only)) check(not any(n.endswith('/') for n in listed)) diff --git a/skills/github-release/templates/packages.json b/skills/github-release/templates/packages.json index 8fc32a2..b444477 100644 --- a/skills/github-release/templates/packages.json +++ b/skills/github-release/templates/packages.json @@ -6,7 +6,7 @@ "claude-code-plugin": {"layout": "plugin", "metadata": ".claude-plugin", "add": {"LICENSE": "LICENSE", "README.md": "README.md", "CHANGELOG.md": "CHANGELOG.md", "INSTALL.md": "packaging/INSTALL-claude-code.md"}}, "codex-chatgpt-plugin": {"layout": "plugin", "metadata": ".codex-plugin", "add": {"LICENSE": "LICENSE", "README.md": "README.md", "CHANGELOG.md": "CHANGELOG.md", "INSTALL.md": "packaging/INSTALL-codex-chatgpt.md"}}, "microsoft-copilot-agent-only": {"layout": "skill", "exclude_suffix": [".ps1"], "add": {"LICENSE.txt": "LICENSE", "INSTALL.md": "packaging/INSTALL-microsoft-copilot.md"}}, - "gemini-apps-only": {"layout": "skill", "exclude_prefix": ["agents/"], "exclude_suffix": [".ps1", ".yaml", ".yml"], "exclude_files": [], "add": {"LICENSE.txt": "LICENSE"}}, + "gemini-apps-only": {"layout": "skill", "exclude_prefix": ["agents/"], "exclude_suffix": [".ps1", ".yaml", ".yml"], "exclude_no_suffix": true, "exclude_files": [], "add": {"LICENSE.txt": "LICENSE"}}, "opal-only": {"layout": "opal"} } } diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 8319057..e72c40d 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -65,6 +65,13 @@ def test_gemini_omits_yml(self): self.assertFalse([n for n in z.namelist() if n.endswith(('.yml', '.yaml', '.ps1'))]) self.assertIn('github-release/scripts/verify_release.py', z.namelist()) + def test_gemini_omits_extensionless(self): + z = zipfile.ZipFile(self.tmp / 'a' / ('github-release-%s-gemini-apps-only.zip' % self.version)) + files = [n for n in z.namelist() if not n.endswith('/')] + self.assertFalse([n for n in files if '.' not in n.rsplit('/', 1)[-1]]) + u = zipfile.ZipFile(self.tmp / 'a' / ('github-release-%s-UNIVERSAL-skill.zip' % self.version)) + self.assertIn('github-release/templates/gitattributes', u.namelist()) + def test_opal_flat_lf(self): z = zipfile.ZipFile(self.tmp / 'a' / ('github-release-%s-opal-only.zip' % self.version)) names = z.namelist()