feat(gh): release-asset uploads, the Gemini extension archive, and a coverage push to 98% - #352
Conversation
Gemini CLI installs a GitHub extension from the repository's latest release, preferring a release asset over the source tarball — and for this monorepo the tarball means downloading 50+ packages to obtain two skill folders. The release target now builds a minimal, deterministic archive of the extension manifest, license, and skills tree, and attaches it to the latest release under the three platform-prefixed names Gemini matches deterministically regardless of what else the release carries. The upload itself is a new GhTasks.uploadReleaseAsset task in @zuke/gh, API-based like uploadSarif: it resolves the latest release or a named tag, leaves an already-attached asset untouched so republishing is idempotent, and reports a repository with no releases as an ordinary outcome rather than an error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019As9A3ugixLQiAvcat1ZKk
…effort Adversarial review found two real failure modes: an errored upload reserves the asset name in a non-uploaded state, and the skip-by-name idempotence check would step past that corpse forever, so a re-run now deletes the stuck asset and re-sends it. And a transient upload failure used to redden the release job after the releases already existed, which skipped the JSR publish behind it — the attach step is now best-effort with a warning, since the next release run tops the assets up anyway. The archive builder also refuses symlinks instead of silently dropping them and names the fix when the manifest, license, or skills tree is missing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019As9A3ugixLQiAvcat1ZKk
…ites Checkpoint of the coverage push toward 98%: meaningful branch tests for the AI reviewer plumbing, CI schedule/generation, build description, and the GitHub workflow trigger, all against the existing fakes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019As9A3ugixLQiAvcat1ZKk
Further checkpoint of the coverage push: branch tests for CLI flag parsing and state serialization edge paths, against existing fixtures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019As9A3ugixLQiAvcat1ZKk
Coverage-push checkpoint: meaningful branch tests for the AI fixer, dedup, hosts, comment/gate/report modules, the executor plan and cancellation paths, the scheduler, resume, reap, and the MCP servers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019As9A3ugixLQiAvcat1ZKk
Coverage-push checkpoint: null-payload signal delivery, authorization refusals, and registry lookup edge paths through the fake transports. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019As9A3ugixLQiAvcat1ZKk
Coverage-push checkpoint from the executor cluster. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019As9A3ugixLQiAvcat1ZKk
…eduler Coverage-push checkpoint: CAS-conflict give-up paths, parked-time timeout credit, and effect re-drive semantics, all against fake stores. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019As9A3ugixLQiAvcat1ZKk
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019As9A3ugixLQiAvcat1ZKk
|
🤖 Zuke AI review 🔎 security review —
|
| Finding | Reason |
|---|---|
| Archive builder follows arbitrary nested paths without rejecting traversal-like roots | The new archive walk only recurses through entries under the supplied root and explicitly rejects non-file/non-directory entries like symlinks, so the code does not follow arbitrary nested paths outside the tree. |
| Release-asset upload needs an explicit scope check at call sites that may run on untrusted PR contexts | The change only adds the uploadReleaseAsset API and its docs/tests; the reviewer’s claim about untrusted-PR gating is speculative and no call-site or implementation path in the shown diff indicates an introduced authorization bypass. |
The change mostly adds archive-building and GitHub release asset support, but it introduces one plausible path-traversal exposure in archive creation and one authorization-risk area if release uploads are reachable without a sufficiently scoped token.
|
🤖 Zuke AI review 🔎 generic review —
|
| Finding | Reason |
|---|---|
Type-unsafe fetch stubs are cast with as typeof fetch, violating the repo's strict no-as rule |
The new tests use as typeof fetch, but the repo already contains many existing tests with the same cast, so this change does not introduce a new no-as violation and the candidate’s failure path is pre-existing rather than introduced here. |
| Release-asset upload is documented and exposed, but there are no visible regression tests for the new GitHub release path | The diff does add build/gemini_archive.ts and related docs, but it also adds focused tests for the new archive behavior in packages/ai/tests/comment_test.ts and packages/ai/tests/agent_fixer_test.ts, so the claim that there are no visible regression tests is false. |
The patch adds substantial new Gemini/GitHub release-asset and AI-review behavior, but it also introduces a couple of high-confidence type-safety and runtime robustness issues in the new test/support code.
CodeQL flags substring URL checks as incomplete sanitization. These are hermetic test fakes routing canned responses, not a security boundary, but a parsed-hostname comparison is just as readable and keeps the scanner quiet without a dismissal trail. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019As9A3ugixLQiAvcat1ZKk
|
🤖 Zuke AI review 🔎 security review —
|
|
🤖 Zuke AI review 🔎 generic review —
|
| Finding | Reason |
|---|---|
| Release asset upload can silently degrade to the wrong release without validating the target tag | The new API explicitly documents that a missing tag selects the latest release and returns a non-error no-release state, so the behavior is intentional rather than an unvalidated misupload path. |
| Gemini archive builder now hard-fails on any non-regular file in skills/ | walk() only throws on non-file/non-directory entries after Deno.readDir, and geminiArchiveFiles() separately catches missing skills/, so the claimed symlink failure is a deliberate guard, not an introduced regression. |
Tests introduce runtime-only type assertions that bypass the project's no-as rule |
The casts are confined to test fakes that are intentionally shaped as typeof fetch seams, and the tests verify the exact runtime behavior those seams need, so this does not expose a production type-safety bug in the change. |
The change adds release-asset upload and a Gemini release archive, but several new tests and helper code rely on unsafe type assertions and insecure fallbacks that could mask real failures or expose incorrect behavior.
What & why
Two related deliverables on the multi-harness skills work, plus a repo-wide test-coverage push.
1. Releases now carry a Gemini CLI extension archive.
gemini extensions install https://github.com/zuke-build/zukeresolves the repo's latest release and prefers a release asset over cloning; without one it downloads the whole monorepo source tarball to install two skill folders. Thereleasetarget now builds a minimal, deterministic tar.gz (gemini-extension.json+LICENSE+skills/) and attaches it to the latest release under three platform-prefixed names (darwin.zuke.tar.gz,linux.zuke.tar.gz,win32.zuke.tar.gz) — the shape Gemini'sfindReleaseAssetmatches deterministically regardless of what else the release carries (a single generic asset silently degrades to the source tarball as soon as any second file is attached; verified against the gemini-cli source).The upload is a new
GhTasks.uploadReleaseAssettask in@zuke/gh, API-based likeuploadSarif: it resolves the latest release (or a named tag), skips an asset the release already carries, deletes-and-resends an asset stuck in a non-uploadedstate (GitHub's documented recovery for interrupted uploads), and reports a repo with no releases as an ordinary outcome. Inzuke.tsthe attach step is best-effort — a transient upload failure must not redden the release job after releases exist, which would skip the JSR publish behind it. Both failure modes were found by the pre-PR adversarial review and carry regression tests.2. Coverage raised from 96.8%/95.0% to 98.4% lines / 97.5% branches (3,151 tests, 0 failed). Targeted, behavior-asserting tests for the weakest files: the AI reviewer/fixer/hosts plumbing, the MCP run-tools/registry/hardening surface, the executor (scheduler, cancel, resume, reap, execute-plan), core
cli/ci/ci_schedule/describe, core state serialization/stores, and theghpackage (now 96–100% per file). Residual uncovered branches are justified in the test files — unreachable defensive guards or paths that would require real network/ambient tools to hit.Drive-by:
README.mddocuments that the Gemini extension installs from releases; test fixture wording made dictionary-safe for the spell gate.Related issues
Follow-up to #351 (multi-harness skills distribution) — closes the "Gemini installs the whole monorepo" gap noted there.
Checklist
type(scope): summary).deno task cipasses locally (lint, fmt, type-check, tests, spell —securityverified except zizmor's advisories lookup, which the sandbox proxy blocks).README.md, JSDoc) when behaviour changed../zuke apiDocs(llms.txt,llms-full.txt, package README## API).any, noascasts or!non-null assertions insrc/(narrow with type guards instead).🤖 Generated with Claude Code
https://claude.ai/code/session_019As9A3ugixLQiAvcat1ZKk
Generated by Claude Code