Repository navigation
Releases: 1273984347/dsh-github-gate
Release list
v2.1.0 — the gates now run in CI, and each one is proven able to fail
Makes the verification real: the gates now run in CI, and every one is proven able to fail.
The defect this fixes
verify-bundle.mjs caught three real packaging omissions in three days — install.ps1/install.sh, PRIVACY.md and docs/roadmap.md were each present in the tree and absent from manifest.files, so the tarball would have shipped without them.
All three were caught locally, by hand. CI never ran that script: it imports index.js, which needs a local DSH installation, so the whole file was skipped — including the static packaging checks that need nothing but the tree. CI's own "Manifest files exist" step only checks six fixed paths, not that files is complete. The badge stayed green while the package was incomplete.
What changed
Split along the dependency line, not the topic line.
tools/verify-manifest.mjs— Node builtins only; owns every static check and runs in CI. One implementation, referenced byverify-bundle.mjs.tools/verify-bundle.mjs— 569 → ~200 lines; keeps only checks that must load the plugin, and reportsUNVERIFIEDrather thanPASSwhere it cannot look.tools/lib/report.mjs— one three-state reporter (pass/fail/unverified), so a check that could not run never reads as success, and tests can assert state instead of parsing console output.
tools/check-script-coverage.mjs catches the class, not the instance. Every tools/ script must be referenced by scripts, CI, tests or another tool — or listed in docs/script-baseline.json with a reason (≥20 characters; an exemption that is no longer needed is itself a failure). Its first run found four unreferenced scripts and five gates missing from CI.
Documented numbers are now measured. docs/stats.json + tools/status.mjs --write physically run each measurement and record the source; the README carries 61<!--stats:action_count--> claims that tools/check-docs.mjs verifies. Its first run found two wrong numbers in the README:
| Claim | Actual |
|---|---|
| 9 tools / 58 actions | 61 |
| 23 destructive actions | 29 (+4 github_api write methods, classified at run time) |
Two new test suites (32 tests)
test/wiring.test.mjs— asserts gates appear inside CIrun:blocks, not step names, because a step named "Static delivery gate" whose body isecho skippedsatisfies any name-based check. It caught thatnpm run releasedid not run the new static gates.test/gates.test.mjs— builds a genuinely broken tree per gate and asserts the matching result isFAIL, because "exit code ≠ 0" passes when the wrong check failed.
Verification
| Suite | Result |
|---|---|
| Offline tests | 207/207 (32 new) |
| Static gates | 4/4 (manifest / coverage / docs / encoding) |
| Full dynamic gate | passing |
| Parameters / guidance / snapshots / argv | 211/211 · 114/114 · 180/180 · 84/84 |
Note on scope
No tool behaviour changed — this release is entirely about whether the project can tell when it is wrong.
Full Changelog: v2.0.0...v2.1.0
v2.0.0 — renamed to dsh-github-gate, and the product surface it was missing
Renamed to dsh-github-gate. A plugin called dsh-github-toolkit already exists with the same topics and is more product-complete; in a marketplace we looked like a less-featured copy of it. The new name keeps "github" (searchable) and names the actual differentiator: every state-changing action goes through a gate.
Tool names (github_*) and actions are unchanged — only the package identity changed.
What this release adds
Settings → Plugins → GitHub. A real page, not YAML editing:
- shows whether a credential is configured and which layer supplied it
- a password field that writes the PAT to the Harness credential store
- Test connection — one real
gh api userround trip through the same chain the tools use - cannot read the token back: the Host never returns it, and the field clears on save
- all three routes
requireGateand fail closed (403) when noconnectionservice can judge the caller — a page that writes a secret must not be reachable by an unjudged caller
One failure vocabulary (lib/format.js). 401, 403 quota, 403 SAML, missing scope, 404, 422, 409, network and missing-gh each name the fix. An unrecognised failure gets no hint rather than a generic sentence — generic hints train readers to skip hints.
One-command download. install.ps1 / install.sh fetch the release, verify its sha256 against the digest GitHub publishes, and print the install path. They deliberately do not install: that step belongs to the plugin manager, which asks for approval.
Repository furniture. Bug/feature issue forms whose fields mirror this plugin's own diagnostics; a PR template that asks which behaviour contract applies; dependabot for Actions; and a tag-driven release workflow that checks the tag matches package.json before packing the same tarball a user installs.
PRIVACY.md and docs/roadmap.md — the latter records what is not planned and why.
Verification
| Suite | Result |
|---|---|
| Offline tests | 176/176 (18 new) |
| Parameter coverage | 211/211 across 9 tools |
| Behaviour snapshots | 180/180 deterministic |
argv shapes parsed by real gh |
84/84 |
| Guidance / bundle / encoding | all clean |
install.ps1 against this release |
downloaded 235 KB, sha256 matched |
The 18 new tests cover the two places Node's test runner cannot reach on its own: the HTTP routes (fail-closed admission, no token echo, real API call) and the browser half (loaded through a fake __ModuleLoader__, both slots register, both components return an element tree instead of throwing).
Install
irm https://raw.githubusercontent.com/1273984347/dsh-github-gate/main/install.ps1 | iexThen plugin_manager action install_bundle with the printed path.
If you installed the old name, uninstall dsh-github-toolkit first — the package identity changed.
Full Changelog: v1.7.1...v2.0.0
v1.7.1 — correct the restart claim (measurement disproved it)
Corrects a sentence in 1.7.0 that measurement disproved.
What was wrong
1.7.0 said: "a restart is required before the new version actually runs" — and the injected prompt told the model never to claim the new version is active before that restart.
Installing 1.7.0 over 1.6.1 showed both are wrong for this install path. Immediately after install_bundle returned application: applied, with no process restart:
| Check | 1.6.1 | Observed |
|---|---|---|
github_* tools live |
8 | 9 (including github_update, 4 params) |
| projected Config schema | no updateCheck |
has updateCheck / updateRepo / updateChannel / updateCacheDir |
| prompt section rules | 6 | 7 |
All three exist only in the new generation, so the plugin manager re-activated the entry and the new code was already serving.
The opposite was also measured earlier in the same environment (installing 0.2.0 did not update the projected Config schema until a restart). Two opposite observations mean the boolean was never a fact — and a confident wrong guess is worse than a question, especially in a prompt that then makes the model deny a version which is already live.
What changed
next.restartRequired: true → "unverified"
stagenow says one step remains (install) and asks the caller to verify the running generation with a tool or Config key unique to the new version.- The installed
package.jsonis called out as not proof: pnpm replaces it before the module is necessarily reloaded, sopluginVersion()can report the new version while the old code runs. - Prompt rule 7 compressed to stay inside the 2400-character budget; README and CHANGELOG corrected.
New test pins restartRequired === 'unverified' and the verification guidance; the live probe asserts the same.
Verification
158 offline tests · 211/211 parameter coverage · 114/114 guidance · 84/84 argv shapes by real gh · 180 deterministic snapshots · live update probe passing.
v1.7.0 — auto-update: check and stage, install stays with the platform
Adds github_update: check reports whether a newer release exists, stage downloads it and verifies its sha256 against the digest GitHub publishes.
What it does — and deliberately does not do
check → is there a newer release? read-only
stage → download it and verify its digest writes one file
install → plugin_manager install_bundle platform, approval-gated
restart → you platform
Three measured constraints put installation and the restart outside this plugin:
- Node's
fetchcannot reach github.com on a TLS-intercepted host (UNABLE_TO_VERIFY_LEAF_SIGNATUREwhileghsucceeds), so every network call goes throughgh— the same reasongithub_apiroutes throughgh api. - A replaced bundle needs a restart. Node has already imported the old module generation; writing new files changes nothing until the Host restarts.
- Installation is owned by the plugin manager.
install_bundlerewrites the profile'spackage.json, prunesnode_modules, and applies the patch row. A plugin editing the profile itself would desynchronise those and bypass the approval step that gates running downloaded code.
So stage returns a path and states installed: false, next.restartRequired: true.
Details
- The digest is checked, and "no digest" is not "verified". Three outcomes —
match,mismatch,unverified. A mismatch refuses to stage and prints both hashes. An intact staged copy is reused unlessforce: true. unknownis its own answer. An unparseable version pair is never reported as "up to date", and a source checkout that is ahead of every release is told so instead of being offered a downgrade.updateCheck: true(defaultfalse) adds a cached 24-hour startup notice. Off by default: an activation-time network call is a surprise.
Verification
| Suite | Result |
|---|---|
| Offline tests | 157/157 (17 new) |
| Parameter coverage | 211/211 across 9 tools |
| Behaviour snapshots | 180/180 deterministic |
argv shapes parsed by real gh |
84/84 |
| Live end-to-end against the real release | 11/11 — read, download, digest match, reuse, tamper detection |
The live probe (npm run probe:update) downloads the real 203 KB asset, verifies its digest against GitHub's published sha256:1b2c234e…, and catches a one-byte tamper as mismatch.
New config keys: updateCheck, updateRepo, updateChannel, updateCacheDir.
Full detail in CHANGELOG.md.
v1.6.3 — first open-source release
First open-source release. A GitHub toolkit for DeepSeek Harness: eight typed github_* tools over the GitHub CLI, a layered credential chain with Device Flow authorization, workspace-bound repository detection, and a guarded raw REST escape hatch.
Install
Download dsh-github-toolkit-1.6.3.tgz below and point the Harness plugin manager (plugin_manager action install_bundle) at the unpacked package directory. Do not install a link: to the source tree — the launcher's runtime resolution does not reach bare @deepseek-ai/* imports through a junction, and the plugin then fails with ERR_MODULE_NOT_FOUND.
Requires DSH >=0.2.0-0 <0.3.0 and the GitHub CLI (gh) on PATH.
What it provides
| Tool | Covers |
|---|---|
github_auth |
Credential chain status, end-to-end connectivity probe, Device Flow login, PAT login, logout |
github_repo |
View, list, branches, clone, create, edit, rename, archive, delete |
github_pr |
List, view, diff, checks, status, create, comment, review, ready, checkout, merge, close, reopen, edit |
github_issue |
List, view, create, comment, close, reopen, edit, develop |
github_release |
List, view, create, edit, upload, download, delete |
github_actions |
Run list/view/log/watch/rerun/cancel/delete, workflow list/view/dispatch |
github_search |
Repositories, issues, pull requests, code, commits |
github_api |
Guarded raw REST for everything else (opt-in via allowRawApi) |
Also: a prompt section, three on-demand skills (github-pr-review, github-ci-triage, github-issue-to-pr), and a session-header branch pill.
Safety model
- Every command is an argv array spawned with
shell: false— model-supplied text is never re-interpreted as shell syntax. - Every action that changes remote state is classified destructive: 23 of them, each requiring
confirm: trueand passing an approval seam.confirmis the model's intent, not the user's consent. github_apiwrite methods are classified at run time by theirmethod, so the escape hatch cannot skip the gate.- Secrets never enter argv. Device Flow talks only to
github.com, with no broker and no secret on disk. - Bounded processes (timeout → SIGTERM → SIGKILL) and bounded output (one byte budget across stdout+stderr, spilling to a file).
Verification
- 140 offline tests, 203/203 declared-parameter coverage
- 176 behaviour snapshots pinning exact argv and result values
- 80/80 argv shapes parsed by a real
gh --help; 50/50 positional arities matched against gh's own USAGE text - Bundle integrity, prompt/skill guidance, and encoding all gated in
npm run release
Full history in CHANGELOG.md; design decisions and the defects that shaped them in docs/DESIGN.md.
MIT licensed.