Informe JSON sin procesar legible por máquina
{
"data": {
"repo": {
"topics": [],
"is_fork": false,
"size_kb": 455,
"has_wiki": true,
"homepage": null,
"languages": {
"Go": 635778,
"Just": 4990
},
"pushed_at": "2026-07-24T11:26:41Z",
"created_at": "2026-07-17T09:09:01Z",
"owner_type": "User",
"updated_at": "2026-07-24T11:26:53Z",
"description": "Run commands and transfer files on the local machine, remote SSH hosts, and containers through one Go interface.",
"is_archived": false,
"is_disabled": false,
"license_spdx": "MIT",
"default_branch": "main",
"license_spdx_raw": "MIT",
"primary_language": "Go",
"significant_languages": [
"Go"
]
},
"owner": {
"blog": null,
"name": "Steven Cartmell",
"type": "User",
"login": "ruffel",
"company": null,
"location": null,
"followers": 0,
"avatar_url": "https://avatars.githubusercontent.com/u/45599587?v=4",
"created_at": "2018-12-04T14:38:51Z",
"is_verified": null,
"public_repos": 4,
"account_age_days": 2792
},
"license": {
"state": "standard",
"spdx_id": "MIT",
"raw_spdx": "MIT",
"file_present": true,
"scorecard_found": true,
"profile_has_license": true
},
"activity": {
"releases": [
{
"tag": "v0.5.0",
"kind": "minor",
"published_at": "2026-07-24T08:13:57Z"
},
{
"tag": "v0.4.0",
"kind": "minor",
"published_at": "2026-07-22T13:00:34Z"
},
{
"tag": "v0.3.0",
"kind": "minor",
"published_at": "2026-07-19T22:20:12Z"
}
],
"recent_commits": [
{
"oid": "e389d05eb38a891f0ddf4afddb8d297f6937c6bc",
"body": "…iguration (#48)\n\nIts v7 warns on every run that categories[*].labels, exclude-labels and\nversion-resolver are deprecated and will be removed. They still work, so\nnothing is broken today — but a major bump is exactly how the labelling\nthis repository just repaired came to fail silently, and waiting \n[…]\n — and against the configuration it replaces: for each of\nthe seven labels, plus skip-changelog and an unlabelled pull request, the\ncategory, the version bump and whether it is excluded are unchanged.",
"is_bot": false,
"headline": "ci: Repair release drafter's labelling and retire its deprecated conf…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-24T11:26:40Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "18857cff5014421c7516196d17ea21043b2e3544",
"body": null,
"is_bot": false,
"headline": "build(docker): Require the core module at v0.5.0",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-24T08:06:18Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a94a0e1e08e93bcd0548acec3b48044c21428eaf",
"body": "The in-process server spawned the command's goroutine and only then\nanswered the exec request. A command that finishes quickly — the\npre-flight check, a LookPath probe — could send its exit status and\nclose the channel before the reply was written; a closed channel drops\na late reply silently, so th\n[…]\nn\nleave the same goroutine in order, so no schedule can put the close\nahead of the reply — and answering before running is also what a real\nsshd does, which is the thing this server exists to imitate.",
"is_bot": false,
"headline": "test(ssh): Answer exec before the command can close the channel (#47)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-24T07:53:51Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "e0a3543919efe82e0a795e4b3c3f46e2fdfe1c37",
"body": "The vocabulary lived in an unexported comment, so a user learned what\nexists by watching commands exit 127: nothing said echo and test are\nanswered while ls and grep are not, or which forms of test and find\nand dd are the simulated ones. The package doc now lists every builtin\nwith its supported for\n[…]\nh the interpreter as it\nnow is: braced names and the spaced /dev/null spelling inside the\nsubset; comments, globs, backslash escapes, arithmetic, and the\n${ operator forms on the refused-by-name list.",
"is_bot": false,
"headline": "docs(fake): Put the builtin vocabulary where a reader can find it (#46)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-24T07:20:26Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "64c694f1cfc4c9b11e82da0a57fce8f2766f6662",
"body": "…#45)\n\nThe README described the library well and onboarded it poorly: a\nfragment quickstart with no imports, no SSH example with real\nauthentication anywhere, the day-one error branch reachable only via\npkg.go.dev, and the selling points framed as philosophy 110 lines\ndown.\n\nThe scaffold is now the \n[…]\nble examples are linked\nwhere a reader wants them.\n\nThe fake's subset paragraph also catches up with the shell it now\ndescribes, and every fenced block compiles verbatim, checked against\nboth modules.",
"is_bot": false,
"headline": "docs(readme): Restructure on the conventions strong libraries share (…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T21:06:25Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "77ad737bb60c4f90598b072d7e89fc66eb591836",
"body": "Five small repairs, each aligning an answer with a promise already\nmade.\n\nA workdir that exists but cannot be entered now classifies as\nErrInvalidWorkdir — the sentinel's doc always promised \"does not exist\nor cannot be entered\", but the pre-check only statted, so a sealed\nmode-000 directory surface\n[…]\nation is now enforced by construction rather\nthan asserted in a comment.\n\nWithTerminationGrace's doc tells the truth about zero: it means the\ndefault, as an unset configuration must, not \"never wait\".",
"is_bot": false,
"headline": "fix(local): Classification minors (#44)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T20:46:13Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f4caf46e14e35bc5466b80309d13e5592cd301a9",
"body": "The grace period bounds how long an exited command's output is\nawaited; it also, wrongly, bounded the copier's last write. When the\ngrace expired with the copier holding bytes — mid-write into the\ncaller's own writer — finish released the terminal and returned, Wait\ncomputed its outcome, and the cop\n[…]\nally.\n\nPinned by holding the caller's writer hostage mid-copy: Wait must\nstill be waiting long after the grace expired, and return promptly\nonce the writer yields, with the command's real exit intact.",
"is_bot": false,
"headline": "fix(local): Join the terminal's copier before Wait returns (#43)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T20:39:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d0eb9a1674decb174f7fc50a935e6ccba196e374",
"body": "A failed Start with a TTY leaked the command's end of the terminal.\nThe cleanup path closed only the caller's end; the command's end sat\nin the exec.Cmd's streams, and the exec package does not close\ncaller-supplied files when Start fails — so a retry loop against a\nmissing binary bled one descripto\n[…]\nial, since a parallel test opening\nfiles would count too, and it counts via Readdirnames — on darwin,\ndevfs reports no entry types and a stat fallback trips over the\ndirectory handle's own descriptor.",
"is_bot": false,
"headline": "fix(local): Close the terminal's command end when Start fails (#42)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T20:32:36Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ee74b866b678b3da6c1a8da0a4fc2c1e9fe183dd",
"body": "…ers honestly (#41)\n\nFour classification repairs, one thread: the provider answering with a\nguess where it had a verdict, or a verdict where it had a guess.\n\nName resolution — LookPath and the pre-flight check — now walks the\nlogin PATH for executable files, exactly the search every other\nprovider r\n[…]\n than\n\"probably Linux\".\n\nThe package doc now states the POSIX-shell assumption everything here\nrides on: a csh-family login shell misreads the command lines, and one\nsentence spares the debugging day.",
"is_bot": false,
"headline": "fix(ssh): Resolve names through the filesystem, and classify the answ…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T20:20:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1c48fcf3e0294ce15bdd3c2d2bfbb84503d90932",
"body": "… (#40)\n\nA keepalive probe awaited its reply with no bound of its own, so on a\nlink that died silently — black-holed, never reset — the probe blocked\nuntil the kernel gave up the socket, and Close, which waits for the\nprobing loop before anything else, hung with it. Discovery was\nlikewise the kernel\n[…]\nsh lane —\nrunning beside real containers — declared a healthy 20ms-cadence link\ndead and let a scheduler-lagged probe land after Close. The floor and\nthe seen-out probe are what those failures taught.",
"is_bot": false,
"headline": "fix(ssh): Bound keepalive probes and honor cancellation mid-handshake…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T20:09:26Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a265337c07280584bef5d54f9b6b1fb532849e0c",
"body": "Keys are the dominant real-world way into an SSH server, and none of\nthat path ever executed: every lane dialed by password, the test\nserver answered only PasswordCallback, and the one agent test used an\nagent that never answers. keyAuth's read, parse, and passphrase\nbranches and the agent signing r\n[…]\ng key file and a wrong passphrase are named in the error,\nbefore anything is dialed.\n\nkeyAuth now sits at full coverage and the agent path executes its\nsigning round-trip; both previously sat at zero.",
"is_bot": false,
"headline": "test(ssh): Cover public-key and agent authentication (#39)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T19:48:05Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "97ee11cf5198ca95598ae0dacdfcb99abaeff2c8",
"body": "…p (#38)\n\nThe file route's failure guard could never fire where it stood: dot is\na special built-in, and a POSIX shell aborts outright when one fails,\nskipping the || waiting to catch it — so a vanished delivery file\nsurfaced as a bare exit 1 attributed to a command that never ran. The\nreadability t\n[…]\n unit lane\nrather than only behind the openssh tag; it can also sabotage the\ndelivery file and refuse the command's exec, so the guard and the\norphan cleanup are provable rather than freak-event code.",
"is_bot": false,
"headline": "fix(ssh): Make env-file delivery failures fire, classify, and clean u…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T17:51:43Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "7bb90104adb5005d7f4ea9fd7f016bf3181332f2",
"body": "Four small repairs, each aligning behavior with something already\npromised.\n\nA context named with WithContext (or DOCKER_CONTEXT) now outranks an\nambient DOCKER_HOST, as the option's doc promised and as the docker\ncommand's own --context flag behaves — it warns and prefers the named\ncontext when bot\n[…]\n deliverable set and the\nnumberable set cannot drift apart.\n\nprogressReader drops a conditional whose two branches returned the\nsame pair, in both its copies (the docker packer and the shared\nengine).",
"is_bot": false,
"headline": "fix(docker): Precedence and classification minors (#37)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T16:07:24Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "db8d88019bae392591d9106238bc55165d743ac5",
"body": "The host-side replace step was weaker than the container-side one in\nboth ways that matter. A directory arriving over an existing file or\nsymlink failed outright — only an existing directory was cleared before\nthe rename. And the clearing itself was the hazard: the old directory\nwas removed before t\n[…]\n the\ncontainer: whatever occupies the destination is set aside with a\nsame-filesystem rename, the arrival moves into place, and only then is\nthe aside cleared; a move that fails restores the original.",
"is_bot": false,
"headline": "fix(docker): Replace a download destination atomically (#36)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T15:27:38Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f0e25cd761b2e427ac9c859949a0e7b9eb7cea6e",
"body": "…oes (#35)\n\nWithMode's contract is about files — the shared copy engine behind\nlocal and ssh preserves each walked directory's own mode, and the\noption's doc says so. The docker provider applied the override to\ndirectories too, in both directions: the upload path stamped it into\ndirectory headers, a\n[…]\n contract\nuploads and downloads a tree under WithMode and expects file modes\nforced and directory modes untouched, with a defect that leaks the\noverride onto directories proving the contract can fail.",
"is_bot": false,
"headline": "fix(docker): Keep WithMode off directories, as every other provider d…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T15:19:21Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c2e063fd7ebf97ce41f64b7e941d681705b2729f",
"body": "…#34)\n\nThe main wait path polls inspect until the daemon agrees the exec has\nstopped, precisely because the stream can end while the daemon still\ncalls the command running — and an inspect answered inside that window\ncarries exit code zero. runRaw, which backs path lookup, the workdir\nand executable\n[…]\naemon timeout independently of the caller's context, which\nmay carry no deadline: a status that never settles surfaces as an\nunresolved transport error rather than a spin — or worse, an invented\nzero.",
"is_bot": false,
"headline": "fix(docker): Wait for an exec to stop before trusting its exit code (…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T15:08:24Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "e64d6d6f82824c711b63f7f926b331c06d1a058a",
"body": "A caller-supplied Stdin whose Read never returns — an io.Pipe nobody\nwrites to, a network stream gone quiet — is the one wedge no provider\ncan interrupt, and the temptation it creates is to wait for it: a Wait\nthat joins the goroutine pumping the caller's reader blocks until that\nreader deigns to re\n[…]\ne real\ncontainer and a real OpenSSH server, not just the unit lane.\n\nThe waitJoinsStdin defect re-creates the tempting mistake for the\nself-test matrix, so the contract is provably capable of failing.",
"is_bot": false,
"headline": "test(invoketest): A wedged caller stdin must not hold Wait hostage (#33)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T15:00:17Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "348c392d87bf54e0411e1e91d3840735d5556f03",
"body": "…turns (#32)\n\nA body stuck in a caller-supplied Stdin.Read was immune to the entire\nlifecycle: cancellation did nothing, Signal reported delivery while\nWait stayed blocked, Close hung, and Environment.Close hung behind it —\na test wired to an io.Pipe nobody writes to would wedge where every\nreal tar\n[…]\neleases its context registration, so a long-lived parent context no\nlonger accumulates one child per finished command; and a panicking\nbody reports itself interrupted instead of the old silent exit 0.",
"is_bot": false,
"headline": "fix(fake): Unblock Wait and Close when a caller's stdin read never re…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T14:42:09Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d672a373f60129784e880900b280e9296d08eae4",
"body": "… (#31)\n\nThe fake answered for a name in one place and denied it in another. A\nhandler registered with Handle ran when started directly but was an\nunknown command to a Shell script or a $(...) substitution — exit 127\nfor the very name the consumer scripted. And LookPath reported\n/usr/bin/NAME for ev\n[…]\nrs first, as Start does, so Handle overrides\na builtin everywhere the name can be written. Unknown names keep their\nhonest failures: ErrNotFound from Start and LookPath, the shell's 127\nfrom a script.",
"is_bot": false,
"headline": "fix(fake): Resolve names the same way in Start, LookPath, and scripts…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T14:27:20Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "b7ebee727eaead6df690334de94930116ec0571c",
"body": "A tag can be moved after the fact; a commit digest cannot. Every\nworkflow now names the exact commit its version tag pointed to at the\ntime of pinning, keeping the tag as a comment for humans — and for\ndependabot, whose actions group updates digest pins and their comments\ntogether.\n\nThe repository already declines a third-party action for installing\njust on supply-chain grounds; the actions it does run — one of which\nholds a contents: write token — now meet the same bar.",
"is_bot": false,
"headline": "ci: Pin actions by digest, not tag (#26)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T14:19:20Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e46234d26539c103b5cb53e81c760b71eafbaf9d",
"body": "…#30)\n\ntest answered false for every form outside its unary set — test -z \"\",\ntest a = a, test 1 -eq 1 — where every real shell answers 0. A silent\nfalse reads as a verdict, and it was a made-up one. The simulated forms\nnow include -z and the POSIX one-argument form (both trivially\nanswerable), and \n[…]\ns where a real shell goes — $HOME —\nfailing plainly when HOME is empty.\n\nMarked breaking: scripts that leaned on the lenient answers now see\nthe statuses a real target would have given them all along.",
"is_bot": false,
"headline": "fix(fake)!: Give the builtins their real answers, or a loud refusal (…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T14:17:06Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9a3b6dbf1adb7ebc8ad50174f35495c9fcdd7180",
"body": "… (#29)\n\nSeveral everyday constructs fell through the shell's refusal net and\nran wrongly with exit 0: ${HOME} survived as literal text, a # comment\nwas echoed as arguments, an unmatched glob passed through literally\nwhere a real target would have expanded it, hello\\ world kept its\nbackslash, $((1+2\n[…]\nion the package docs\nalready promised, in its most common spelling.\n\nMarked breaking: a script that leaned on any of the misparses now\nfails loudly at Start, and one refused for its spelling now runs.",
"is_bot": false,
"headline": "fix(fake)!: Close the refusal-net holes and interpret the near subset…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T14:06:47Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d046d3515838827dafb83603fffc8908f920cae5",
"body": "The tokenizer wrote quoted segments and expansion results into the\nsame buffer as bare text, and redirect classification then ran over\nevery finished word. So echo '2>&1' printed an empty line with stderr\nrewired to stdout, echo '>/dev/null' discarded its own output, and a\nvariable whose value looke\n[…]\nxactly as\nbefore.\n\nMarked breaking because a script relying on quoted or expanded text\nacting as a redirection now gets it as an argument — the behavior it\nwould have had on any real target all along.",
"is_bot": false,
"headline": "fix(fake)!: Treat quoted metacharacters as data, not syntax (#28)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T13:46:30Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "52df033ab71a8c69368b5bb07339b931863c4c78",
"body": "The conventions already exist — conventional commits with the breaking\nmarker, one concern per pull request, just check before pushing,\ncontract-first behavioral changes — but they lived as a paragraph at\nthe bottom of the README and in folklore. CONTRIBUTING.md consolidates\nthem where GitHub surfac\n[…]\nner), including the\nrule the suite enforces on itself: a contract that cannot be made to\nfail does not merge.\n\nThe README's development section keeps the two commands and now points\nhere for the rest.",
"is_bot": false,
"headline": "docs: Add a contributing guide (#27)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T13:00:40Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "89f57c447b9d81794c6bfe37eed8120f4575cb8b",
"body": "v0.0.1 through v0.1.0 were published under the old layout, whose\nproviders/* submodule tags were later deleted; the proxy never cached\nthose, so the old versions resolve but cannot build — go get succeeds\nand the consumer's next go mod tidy fails asking for revisions that no\nlonger exist anywhere. R\n[…]\ndule paths remain resolvable\"); it now tells this one,\nfolds the retract block into the bad-release remedy, and drops a\none-time step (re-enabling the submodule tidy arms) that was completed\nlong ago.",
"is_bot": false,
"headline": "build: Retract the pre-rewrite versions, which no longer build (#25)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T12:53:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1d3b5a675c2afcbb26e860bd780ce0bbb302c523",
"body": "The README pinned go get at v0.3.0, one release behind: a manual pin\ngoes stale on every release, and an unversioned go get already resolves\nto the latest tag.\n\nWhile here, repair the quickstart binding — res was declared and never\nused, so the fragment could not be pasted into a function body witho\n[…]\ncarried the same line) — and surface what was\npublished but unreachable from the README: the API reference and\nrunnable examples on pkg.go.dev, the release notes on GitHub Releases,\nand the CI status.",
"is_bot": false,
"headline": "docs: Unpin the install commands and link the published docs (#24)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T12:39:54Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "97198048e520cfa8438a49e8a1a522e5ef3f497d",
"body": "… (#23)\n\nBumps the actions group with 2 updates in the / directory: [actions/setup-go](https://github.com/actions/setup-go) and [release-drafter/release-drafter](https://github.com/release-drafter/release-drafter).\n\n\nUpdates `actions/setup-go` from 6 to 7\n- [Release notes](https://github.com/actions\n[…]\npe: version-update:semver-major\n dependency-group: actions\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "build(deps): Bump the actions group across 1 directory with 2 updates…",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-07-23T12:16:33Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "218d50b219237a91f3e3dd11176908a79abc2e1e",
"body": "The submodule is a separate Go module, and the single gomod entry pointed\nat the root directory only ever saw the core module's go.mod. So the\nsubmodule's own dependencies — the docker SDK among them — raised security\nalerts that could never turn into an update, because nothing was watching\nthe file\n[…]\ne requirement left out\nof it: that pin is repointed to a released version by hand at release\ntime, and an automated bump would propose a version not yet published and\nfight the release process for it.",
"is_bot": false,
"headline": "build: Cover the docker submodule in dependabot (#22)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-23T10:26:23Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9364bd6628afc0d86f3908503e2ff86194a504a3",
"body": "Writing a release meant recalling what had changed since the last tag,\nfrom the commit log, at the moment least suited to it. A draft kept\ncurrent as pull requests merge turns publishing into editing something\nalready written.\n\nThe draft resolves a version from labels, and the labels come from the\np\n[…]\nft is a starting point. It groups by the kind of change, where a\nrelease worth reading groups by theme and says why each change mattered —\nv0.4.0's notes did, and no generator would have written them.",
"is_bot": false,
"headline": "ci: Draft the release notes as the changes land (#21)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T15:40:31Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "8391745a0b819074f864194300c6eac2f97c5daf",
"body": null,
"is_bot": false,
"headline": "build(docker): Require the core module at v0.4.0",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T12:42:02Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b4eaa4f90d4b40c63190b8004fdb60d2acf1d9ef",
"body": "…rongly (#20)\n\nThe fake's shell interprets a subset of sh, and everything outside it was\ntaken literally — an unrecognized character being just another character\nto a tokenizer. A pipeline became arguments to the first command, so\n`echo hi | tr h H` printed \"hi | tr h H\" and exited zero. A redirecti\n[…]\nt attempt at the word boundary.\n\nThis is a breaking change for any test that was passing on a script the\nshell never really ran. That is the point: those tests were asserting\nsomething no target does.",
"is_bot": false,
"headline": "fix(fake): Refuse a script the shell cannot run, rather than run it w…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T12:32:30Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "bb160bc61be938b398149e3350d728e9f50c1fe8",
"body": "…ocesses a signal reaches (#18)\n\nA signal reaches the process it names on every target. Whether it also\nreaches that process's children is where the targets part company: local\nruns a command in a process group of its own and signals the group,\nwhich is what a terminal does and why it does it, while\n[…]\nready contracted — the signalled process dies with the\nsignal recorded, on every target that declares the capability — so this\nrecords the part above the floor that callers must not assume either way.",
"is_bot": false,
"headline": "docs: Signal delivery honesty — what SSH cannot confirm, and which pr…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T12:17:45Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "e06e2de21dfede400e0fb8ff552969738b17ec22",
"body": "…#19)\n\nThe in-process server reported a command's exit as soon as it exited,\nclosing the channel and the terminal with it, while a goroutine was still\ncopying the terminal's buffered output across. Whichever won the race\ndecided whether the command's output survived, and on a loaded Linux\nrunner und\n[…]\nnst.\n\nVerified on Linux under the race detector, where the failure reproduced\nwithin forty runs: a hundred and twenty consecutive runs of the contract\nand repeated runs of the whole package are clean.",
"is_bot": false,
"headline": "test(ssh): Let a terminal's output arrive before the exit closes it (…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T12:11:50Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1b9f6c9c43d8082366fdcf30081fbad0e81a316d",
"body": "* fix(local): Classify an unresolvable path as not found\n\nLookPath promises that a name it cannot resolve fails wrapping\nErrNotFound. It held only for a bare name searched on PATH, which reports\nexec.ErrNotFound. A name with a separator is looked up as a path instead,\nso a missing or non-executable \n[…]\n// endpoint is now refused wrapping ErrNotSupported, however it\narrives — an explicit host, DOCKER_HOST, or a context's stored endpoint —\nnaming the scheme and pointing at the transports that do work.",
"is_bot": false,
"headline": "fix: Three provider-classification minors (#17)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T11:37:30Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a009b25706071a5974c6aa6d4054d1d0eddc2e21",
"body": "A command run with a terminal produced no output at all. Its stdin pump\nhalf-closes the attached connection once there is nothing left to send,\nwhich is how end-of-input is signalled on the multiplexed stream a\nnon-terminal exec uses. A terminal is not multiplexed: it is one\nbidirectional stream, an\n[…]\nr-merge contract, which found this and was gated against the docker\nprovider, passes ungated now — verified against a live daemon, output\ncaptured and the two streams merged as a terminal merges them.",
"is_bot": false,
"headline": "fix(docker): Deliver command output under a requested TTY (#16)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T11:28:05Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9e57ba4ce8fc3a332a684bfc80bc6e375b367011",
"body": "… (#15)\n\nThree properties the design names but the suite did not hold providers\nto, each with a defect proving it can fail.\n\nThe undeclared half of the symlink capability. The suite required that a\ntarget declaring SymlinkPreserve keeps links, but not what a target\nlacking it must do: refuse a tree \n[…]\nlds every provider\nto it: where the two sides share a filesystem the overlap guard refuses\nit, and where they cannot alias the atomic delivery keeps the source\nintact, so the file survives either way.",
"is_bot": false,
"headline": "test(invoketest): Complete the capability and transfer spec contracts…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T11:13:25Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5e27275482cdb3b431a085d6fa9a6a22c153a96d",
"body": "…ailure (#14)\n\nOnly a transport failure is retried, because only it says the command\ndid not run. Every other outcome is terminal: a command that ran and\nfailed may have taken effect, and one that could not start for a settled\nreason will not start on a second ask. The suite checked that each of\ntho\n[…]\nsportError, which passes every\nclassification the outcome already had and is caught only here. Every\nprovider passes clean, confirmed against the in-process and real OpenSSH\nservers and a live daemon.",
"is_bot": false,
"headline": "test(invoketest): Forbid dressing a terminal outcome as a transport f…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T10:50:58Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "b01900879add51fc397cd4ada63e7856d9f65a5a",
"body": "The submodule's tidiness went unchecked. It was left out of the tidy\nrecipes while the core module had no published version for it to resolve,\nso tidying it would have tried to fetch something that did not exist. That\nversion exists now, and the omission outlived its reason across two\nreleases, leav\n[…]\nreceive fixes before 1.0, and draws the two\nboundaries that decide what counts: the command is the caller's, and the\ncaller's machine is defended from an untrusted target even when the\ncommand is not.",
"is_bot": false,
"headline": "build: Verify the submodule stays tidy; add a security policy (#13)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T10:27:19Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "87c9e9de92264d48bbf72f25318169b6dabaa62c",
"body": "An upload staged under a fixed path — /tmp — and then moved the result to\nthe destination. When the two were on different filesystems, which /tmp\nbeing a separate mount makes ordinary, that move was not a rename but a\nrecursive copy, and for a directory destination the old one was removed\nbefore the\n[…]\nhat it no longer clears the destination first. The restore\nbranch is the standard rename-aside pattern and is not exercised on its\nown, since a same-filesystem rename cannot be made to fail on demand.",
"is_bot": false,
"headline": "fix(docker): Move an upload into place on one filesystem (#12)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T10:19:00Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ab69738e1ee4425094742862ffef9671d7078d1a",
"body": "…mlink (#11)\n\nThe overlap guard compared the paths as written, so a destination whose\nown parents lead back into the source by a symlink was not recognized as\noverlapping. The copy then wrote the tree into itself and recursed,\ncreating one nested directory after another until the names grew too long\n[…]\nnother\nname. A third pins the boundary the resolution must not cross — a\ndestination reached through a symlink that leads elsewhere, the ordinary\nrelease-directory deploy, which must still be allowed.",
"is_bot": false,
"headline": "fix(transfer): Detect an overlapping destination reached through a sy…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T10:07:22Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9549d4eb706559edcf5b470215d4ecba9fab351e",
"body": "… (#10)\n\nStart checks the closed flag once and then opens a session and starts the\ncommand several round-trips later. A Close landing in that window had\nalready gathered the processes it would terminate, so the one Start went\non to register ran with nothing left to stop it — and Start handed it\nback\n[…]\no a closed environment must\nrefuse with ErrClosed and leave the active set untouched, and it fails\nwithout the re-check. This is the same class the docker provider carried;\nthe local provider is next.",
"is_bot": false,
"headline": "fix(ssh, local): Refuse to start a process into a closing environment…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T10:07:08Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f278ff2dd8e8c4e36b7325a89ccb81fc126a395c",
"body": "Start checks the closed flag once and then creates the exec over several\ndaemon round-trips. A Close landing in that window had already gathered\nthe processes it would terminate, so the one Start went on to register\nran in a closed environment with nothing left to stop it — and Start\nhanded it back \n[…]\ne window exists in the ssh and local providers, whose track is\nidentically unguarded; their setup is shorter, so the window is smaller,\nbut the class is shared and worth closing there too, separately.",
"is_bot": false,
"headline": "fix(docker): Refuse to start a process into a closing environment (#9)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T09:57:32Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "3c941d95cc0fabddebd6e3958f3ed973087a2f58",
"body": "…(#8)\n\nA command interrupted before it reported a status may already have taken\neffect, and nothing on this side can tell whether it did. That was\nhonored for the one shape the library names — ExitMissingError — while\nevery other shape fell through to a TransportError, which the executor\nretries. So\n[…]\nent part of the session notices first, and fails without it.\nThe end-to-end test now repeats, as its transfer counterpart already did,\nso a change in which shape appears is caught rather than assumed.",
"is_bot": false,
"headline": "fix(ssh): Treat every wait-time failure without a status as terminal …",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T09:38:19Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "835dd0535c30e48e2fb7d015f8ddb74377a1875e",
"body": "The outcome was read from the context before the daemon was asked for a\nstatus, so a cancellation arriving while output still drained rewrote a\ncommand that had already finished. The exit was reported as a\ncancellation, and a caller who retries on cancellation would have run it\na second time.\n\nThe s\n[…]\n gap for the docker provider. Verified\nagainst a live daemon, the lifecycle contracts repeated under the race\ndetector to show the attribution no longer depends on which of the two\nevents lands first.",
"is_bot": false,
"headline": "fix(docker): Attribute cancellation only when it ended the command (#7)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T09:31:45Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "19ae3fed59d9e443065534fd0aabd1280ccfe94b",
"body": "The outcome was read from the context before the status the server sent,\nso a cancellation arriving while output still drained rewrote a command\nthat had already finished. The exit was reported as a cancellation, and\na caller who retries on cancellation would have run it a second time.\n\nA reported s\n[…]\nor both the embedded server and\nthe real one. The contract covers a clean exit; a unit test covers a\nnon-zero one, since a reported status is authoritative whatever it says.\nBoth fail without the fix.",
"is_bot": false,
"headline": "fix(ssh): Attribute cancellation only when it ended the command (#6)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T09:21:45Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c9d229267bdfd942365075e8aa0ccdaaf49fe878",
"body": "…el (#5)\n\nThe suite already asks what a provider reports when a cancellation\narrives after Wait has returned, and every provider passes: the outcome\nwas decided and remembered before the cancel existed. The question never\nasked is what a provider decides when the cancellation arrives first —\nthe pro\n[…]\node: each consults the\ncontext before the status it already has. Both are gated with\nWithKnownGap so main stays green, and each gap names the provider fix\nthat removes it. Local and fake pass ungated.",
"is_bot": false,
"headline": "test(invoketest): Enforce that a real exit survives a concurrent canc…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T09:14:25Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "457de6d3385fa5b8cb42fda2ff71d8a9bf2a6ed4",
"body": "Retry asks one question: might the command not have run? Only a\ntransport failure answers yes, so only that family is retried. But a\nTransportError unwraps, and one wrapping a terminal outcome belongs to\nboth families at once — every assertion about the inner error still\nholds, and the classifier re\n[…]\nis way, so nothing here\nchanges behaviour. The point is that the property no longer depends on\nevery provider — including ones outside this module, which no contract\ncan reach — choosing to be honest.",
"is_bot": false,
"headline": "fix: Refuse to retry an outcome that is also terminal (#4)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-22T09:03:37Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "dc8bc4db0402bf18a39808513ccabff3398f6d27",
"body": "An archive names its own entries, and each name is checked against the\ndestination before it is used. What leaves the destination is not the\nname but a link the archive plants: an entry creating a symbolic link,\nfollowed by one written underneath it, passes both checks while the\nsecond write lands w\n[…]\ndaemon, so\nthe escape is covered in the ordinary lane. Verified: each fails against\nthe previous behaviour, reporting the silent success; the full check and\nthe Docker lane pass against a live daemon.",
"is_bot": false,
"headline": "fix(docker): Contain extraction against symlinked parents (#3)",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-20T10:04:40Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9c85f028f3943b131cdd3898572ed97be3c5578c",
"body": "… (#2)\n\nThe containment check compares names, and a name formed by joining an\nentry onto the destination never leaves the root. A link already at the\ndestination therefore escaped it entirely: where the source tree had a\ndirectory and the destination had a symbolic link, the walk merged into\nthe lin\n[…]\ninst the previous behaviour, reporting\nthe silent success; the full suite, the OpenSSH lane and the Docker lane\npass with the fix, so the sftp and in-memory sides of the shared engine\nare covered too.",
"is_bot": false,
"headline": "fix(transfer): Refuse to write a tree through a symlinked destination…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-20T09:51:12Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "aabbefedc399e526a46b51343fe8a67c2055540b",
"body": null,
"is_bot": false,
"headline": "docs: Point at v0.3.0",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T22:18:25Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "59842088338bfaac31cdf417a132ec825a0b01a0",
"body": null,
"is_bot": false,
"headline": "build(docker): Require the core module at v0.3.0",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T22:16:44Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b5ba90ea334f16aa88a4bd5ced3ae14dcf01ccb2",
"body": "Left behind by resolving the module graph; committing it keeps the\nworkspace reproducible for anyone building here.",
"is_bot": false,
"headline": "build: Record workspace checksums",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T22:15:17Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2c666ed93aba5c7fe24d8a38760dc641e7ad81b8",
"body": "The last target that could not. A command asked for one now gets its own\nsession with a controlling terminal, both output streams merged onto it,\nand the dimensions the caller asked for — which is what makes a command\nthat behaves differently when watched behave the same way here.\n\nA terminal replac\n[…]\napability being undeclared — so the defect now presents itself that\nway rather than inheriting what the wrapped target can do. Without it the\ncontract simply stopped running, which the harness caught.",
"is_bot": false,
"headline": "feat(local): Allocate a terminal on request",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T22:10:06Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "4c803e8bd485567507ba741f1eba6c9e4cfcc78e",
"body": "An installation that does not put its daemon on the conventional socket\nrecords where it is in a context, not in the environment: Colima, Rancher\nDesktop and some Docker Desktop setups all do. Reading only the\nenvironment found nothing and reported that the daemon was not running,\nwhich is both untr\n[…]\n because\nconnecting without its certificates fails for a reason that names the\nwrong cause.\n\nThe tests that had been passing an endpoint explicitly no longer need\nto, which is the change worth having.",
"is_bot": false,
"headline": "feat(docker): Find the daemon the way the docker command does",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T21:56:36Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "56ea1fd4a5ed08731bc762e6e37b8d06254ffedf",
"body": "Every other provider took options and this one took none, which was an\nasymmetry with something behind it: the period a command is given to\nfinish writing once it has exited was fixed at two seconds and picked by\nnobody in particular.\n\nIt matters because a command can outlive its own exit — somethin\n[…]\nut of a command\nthat flushes something substantial on the way out, and is an age for a\ncaller who would rather not wait at all.\n\nThe default is unchanged, so nothing moves for anyone who does not ask.",
"is_bot": false,
"headline": "feat(local): Let the caller set the termination grace period",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T21:44:16Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "6e1039af4bd31d8b652a7ee2685b54e9ab3be72a",
"body": "The release exists, so the note explaining that it did not is replaced by\nthe two commands that fetch it — and by which of them a caller needs.",
"is_bot": false,
"headline": "docs: Say how to install it",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T21:39:11Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "4fa93298630c37482e8dcb80b0fc071f9190583b",
"body": "The verification job asked the proxy for the module before the file that\nimports it existed, so go.sum never gained entries for what those imports\npull in and the build failed on every tag. The release it was checking\nwas sound — a scratch module built against it by hand — but a check that\nfails for\n[…]\n graph is resolved and tidied against\nthem, which is the order a consumer works in.\n\nIt can also be run on demand for a version already released, so a fix\nhere does not need a new tag to be exercised.",
"is_bot": false,
"headline": "ci: Resolve the release the way a consumer builds it",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T21:37:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "4431dd8efe72c6eaa88926e3449141a6f739863c",
"body": "Until the core module was published there was no version for the\nsubmodule to name, so its go.mod had been kept by hand and every\ndependency in it was labelled indirect — including the two it imports\ndirectly. It now names v0.2.0 and has been tidied against it, which is\nthe first time that was possible.\n\nThe submodule resolves and builds outside the workspace as a result,\nwhich is how a consumer meets it.",
"is_bot": false,
"headline": "build(docker): Require the core module at v0.2.0",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T21:35:21Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "815ce90184dc5db5889d7cb87b191b5bef756720",
"body": "Every operation took a context except the one that blocks longest.\nConnecting an SSH target dials, completes a handshake and asks the host\nwhat it is; connecting a container makes three round trips to the daemon\nin sequence. Each was bounded only by a configured timeout defaulting to\nthirty seconds,\n[…]\nnwatched for the\nrest of its life the moment that work finished.\n\nDone now because it cannot be done later: adding a second constructor\nafter the signature is published leaves both in the API forever.",
"is_bot": false,
"headline": "feat: Bound connecting to a target by the caller's context",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T21:26:30Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9a51d35430f3285412279b9f587adbfbf2a75dab",
"body": "Deciding what a transfer does with one entry — copy it, recreate it as a\nlink, follow it, skip it — is the same question whatever the target, and\nanswering it twice is how two targets come to disagree about what a\ntransfer means. The answer lived in an internal package, which the\ncontainer provider \n[…]\n than\nmeaning.\n\nAnyone writing a provider of their own gets the same, which they need:\nimplementing Upload and Download correctly was previously only possible\nby guessing at rules they could not read.",
"is_bot": false,
"headline": "refactor: Publish the transfer policy rather than hiding it",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T21:23:49Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c4cf526f5f07e77067ab3801450e5bad6c7f1200",
"body": "The pre-flight check resolved the command with \"command -v\", which\ngiven a path answers a different question depending on the shell: whether\nthe file exists, or whether it can be run. A container's shell gives the\nfirst answer, as does the shell on most Linux hosts, so a file that could\nnever be exe\n[…]\nething\nexecutable. A path is now tested directly for being a regular file that\ncan be run. The docker provider had the same check and the same flaw, and\nhad no test covering it at all; it has one now.",
"is_bot": false,
"headline": "fix: Check an executable path can actually be executed",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T21:15:56Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "fc63ae342aa7fe256104bdb647f35f3af13d1afc",
"body": "The consumer simulation covered the submodule by replacing both modules,\nwhich satisfies the core import from the scratch module's own graph and\nnever exercises what the submodule itself names. It was green while the\nsubmodule named a version carrying an entirely different API — the one\nbreak it exi\n[…]\ned honestly, and the last moment\nthe answer can still change anything.\n\nAlso give the submodule its own licence file, and record that its go.mod\ncan finally be tidied once a version exists to resolve.",
"is_bot": false,
"headline": "ci: Ask the release question where it can be answered",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T21:11:44Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a96a237855ccb17e7bc8de3385e2ed13632e66b5",
"body": "One provider cannot hand the environment to the kernel. A server accepts\nonly the variables its AcceptEnv setting names, and the stock setting\nnames none, so on SSH the refused ones are delivered by generating shell\ntext — and a name is written into that text directly. A name carrying a\nseparator or\n[…]\ns to run.\n\nAn entry with no separator is refused too, for a duller reason: no target\nconsumes one, so it could only ever have hidden a mistake.\n\nFound by auditing the public surface before tagging it.",
"is_bot": false,
"headline": "fix: Refuse environment names that would become shell script",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T21:09:16Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5e2aacda3e31f8b918fffab0b42605acc8da296b",
"body": "The submodule depends on the core module by version, not by directory:\na workspace is what makes them find each other here, and a consumer does\nnot have one. The core module must therefore exist at a version before\nthe submodule can name it, which fixes the release order and makes one\nmistake easy t\n[…]\nnSSH\nserver and a real container rather than only against the stand-ins. The\nconsumer simulation now resolves both modules with the workspace removed,\nwhich is the situation a consumer is actually in.",
"is_bot": false,
"headline": "build: Prepare the repository for a two-module release",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T20:59:11Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "add37d27ea34bb0fef1286bf5c5e46bb25b6774f",
"body": "The package documentation now covers what a caller needs before writing\nanything: how to reach a target, how to tell a command that ran and\nfailed from one that never ran, why only transport failures are retried,\nand which targets can do what.\n\nThe examples run. Nine of them are executed by the test\n[…]\nup, what a container without\na shell cannot do, and where the container runtime is looked for. Each is\na property of the target rather than of this API, and each traces to a\ncapability flag or a test.",
"is_bot": false,
"headline": "docs: Describe what the library actually does",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T20:49:39Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "4c81b78470076a603a3534e179f413548542146b",
"body": "The contract suite asks whether each provider obeys a stated rule, one\nprovider at a time. Anything no rule names — how a relative executable\nresolves, whether an empty environment value arrives set or unset,\nwhether an argument keeps everything after its first equals — can differ\nbetween targets wh\n[…]\nnsfer, so the setup\nis identical everywhere rather than three approximations of each other.\n\nVerified by reintroducing the workdir-resolution divergence this was\nwritten for, which it names precisely.",
"is_bot": false,
"headline": "test(docker): Compare the providers against each other",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T20:29:25Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a309d5f711f7fe843b042d5c646ef2707c2c98de",
"body": "A server accepts only the environment variables its AcceptEnv setting\nnames, and the stock setting names none — so the out-of-band route,\nwhich keeps values out of the remote process table, is refused on\nessentially every real server. That left two poor options: fail, so\nCommand.Env does not work at\n[…]\n. Delivery still fails rather than running\na command without its environment, and the command line remains\navailable to callers who ask for it.\n\nVerified against a real server with nothing opted into.",
"is_bot": false,
"headline": "feat(ssh): Deliver the environment without exposing it",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T20:22:18Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "b36c7657fe2ed482000c59c6809fb59366e5cdf6",
"body": "The suite has been running the provider against a server written for\nthese tests, which implements what this repository believes the protocol\ndoes and so cannot show where that belief is wrong. Run the same\ncontracts against a real sshd in a container, with its stock\nconfiguration.\n\nIt found one imm\n[…]\nand a caller who needs them\ndelivered anyway can opt into carrying them on the command line, where\nevery account on the host can read them. Both routes are now pinned by\ntests against the real server.",
"is_bot": false,
"headline": "test(ssh): Verify against a real OpenSSH server",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T20:04:19Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "3512523a65fe0f96f9098654d343c0b78b28c45b",
"body": "The exec API takes a terminal flag and an initial size, so declare the\ncapability and make it real.\n\nA terminal changes how the daemon frames the command's output: without\none it interleaves the two streams and they have to be separated again,\nwith one it sends the bytes as they are, stderr already merged in.\nReading a raw stream as a framed one yields silent corruption, so the\ncopy switches on which was asked for, and stderr is not attached\nseparately when there is nothing left for it to carry.",
"is_bot": false,
"headline": "feat(docker): Allocate a terminal on request",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T19:55:11Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "4829ee2c16bbe734afab62906f57a98c4d43a380",
"body": "The protocol carries a pseudo-terminal request natively, so declare the\ncapability and make it real: a session asks for one sized from the\ninvocation before the command starts.\n\nEcho is switched off. A terminal would otherwise reflect whatever the\ncaller writes to stdin back into the output they col\n[…]\nnd quietly ignored it would let the\ncontract pass while the command still had no terminal. It now runs the\ncommand attached to one, which costs a test-only dependency that brings\nnothing else with it.",
"is_bot": false,
"headline": "feat(ssh): Allocate a terminal on request",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T19:55:04Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "2c33ada711e7643cb7dfbe18895fe945a4a67ebc",
"body": "Move files as archives, which is the shape the daemon offers: whole\ntrees in and out, with no way to address a single file. The copy engine\nthe other providers share needs per-file access and a directory listing,\nneither of which exists here, so the archive path decides the same\nquestions itself — a\n[…]\nath rules and\n checked to land inside it, since an archive names its own entries.\n\nEvery transfer contract now runs against a real container: 50 pass, with\nonly terminal allocation left undeclared.",
"is_bot": false,
"headline": "feat(docker): Add file transfer to and from containers",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T19:46:46Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "551298a6daded391249dcb275536ceab174e17ad",
"body": "Run commands inside a running container through the daemon's exec API,\nas a submodule so its dependency tree stays out of core consumers'\ngraphs. A workspace resolves the core module during development.\n\nThe daemon takes a real argument vector and carries the environment over\nthe API, so no shell re\n[…]\nre most\nneeded exactly when that context has just been canceled.\n\nContracts run against a real container behind a build tag, since they\nneed a daemon: 36 pass, with file transfer gated until it lands.",
"is_bot": false,
"headline": "feat(docker): Add command execution in containers",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T19:18:12Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "458f651b2cd35798d67e7127262d0c2d7822b001",
"body": "Adopt testify for assertions. The library was already reachable in the\nmodule graph and its linter was already enabled, so the hand-written\nchecks were a convention nothing enforced.\n\nt.Fatalf sites become require, t.Errorf sites become assert, so what\nhalts and what continues is unchanged. Failure \n[…]\nng suppressions at every such site.\n\nThe defect harness is what makes this safe to do wholesale: all 55\ninjected defects still fail their contracts, so no contract lost its\nability to detect breakage.",
"is_bot": false,
"headline": "refactor(test): Assert with testify across the suite",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-19T18:59:29Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "291372f9193083a1812d3456aadad7b971be4db3",
"body": "A connection that dies mid-operation surfaces two ways depending on\nwhich side of the SFTP client notices first: a connection-lost status, or\na raw write failure from a packet send. Both are transport failures, and\nthe retry policy acts on nothing else, so a test severs the link mid\ntransfer repeate\n[…]\n will silently run a\nsecond time. Transfers earn their retry by delivering atomically;\ncommands have no such guarantee. Say so in the error, which previously\nreported only that no exit status arrived.",
"is_bot": false,
"headline": "test(ssh): Pin how a dying connection is classified",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-18T09:55:45Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "73b78be6c262acc55032fbfbe71fbd7f788f578c",
"body": "The pre-flight check resolved the executable from the login directory\nwhile the command itself runs in the configured working directory, so a\nrelative path present in that directory was reported as not found. The\ncheck now enters the directory first, exactly as the command does.\n\nEntering it also co\n[…]\n\nthe environment staying out of the remote command line; a malformed\nenvironment entry ignored rather than breaking the command; and a file\nthat exists but cannot be executed reported as unresolvable.",
"is_bot": false,
"headline": "fix(ssh): Resolve the executable where the command will run",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-18T09:51:15Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "81c887b78acce6e3daab7138962e3c96f89c033c",
"body": "Constrain host-key negotiation to the algorithms known_hosts records for\nthe host. A server answers with its own preferred key type, so a host\nfirst recorded under another type was reported as a key mismatch — the\nwarning that means a machine-in-the-middle — for an entirely legitimate\nconnection.\n\nP\n[…]\n\n\nRelease the SSH agent socket on Close. Agent authentication signs on\ndemand, so the connection is held for the environment's lifetime; it was\nnever handed back, leaking a descriptor per environment.",
"is_bot": false,
"headline": "feat(ssh): Harden connection setup and teardown",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-18T09:48:17Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f34dabb0576f99606cacc48590578a2803c04b83",
"body": "A directory listing names the entries to copy, and the two sides of a\ntransfer need not agree on what separates a path: a name that is an\nordinary filename to a POSIX source is a directory traversal to a\nWindows destination. An entry addressing a real file outside the root\nwas read and written there.\n\nEach side now screens the joined path with its own path algebra, so a\nname legitimate on the source still transfers while one that leaves\neither directory is refused.",
"is_bot": false,
"headline": "fix(transfer): Refuse directory entries that escape their directory",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-18T09:42:06Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9ca8b385c6e3a5bf916cef9bf7c0e4923b902dd5",
"body": "Upload and Download run the shared copy engine over an SFTP session, so\nremote transfers inherit the same atomic delivery, mode preservation,\nsymlink policy and progress reporting the contracts already specify.\n\nThe remote endpoint departs from the local one where the protocol\ndemands it:\n\n - Paths\n[…]\nst connection is classified as a transport failure whether it\nsurfaces as an SFTP status or as a raw write error, so retryability does\nnot depend on which side of the client observes the outage first.",
"is_bot": false,
"headline": "feat(ssh): Add file transfer over SFTP",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-18T07:35:33Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "0f9417a43b2bd2dc3bf99780e6f7bcbfa6680392",
"body": "A transfer whose context is already canceled must fail without touching\nthe destination. The per-entry cancellation check inside the tree walk\nnever runs for an empty source directory, so that case reported success\nand created the destination root; the check now happens before anything\nis created.\n\nAdd the contract and the defect that proves it can fail.",
"is_bot": false,
"headline": "test(invoketest): Require a canceled transfer to do nothing",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-18T07:34:00Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0876339ac3f0ea0ea4f38899195047cfb6643e7b",
"body": "Move the transfer semantics — atomic temp-and-rename delivery,\ndeepest-first directory-mode restoration, symlink and special-file\npolicy, progress, and prompt cancellation — out of the local provider\ninto internal/transfer, parameterized over a per-side filesystem\ninterface. Remote providers plug in their own transport and inherit the\ncontract-verified behavior instead of re-implementing it.",
"is_bot": false,
"headline": "refactor(transfer): Extract the copy engine shared by providers",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T14:45:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "3101d90ee76f06502b1d085049a0ed2aeaef3d3d",
"body": "The SSH provider runs commands on a remote host, passing every shared\nbehavioral contract except file transfer, which is gated as a known gap\nuntil the SFTP layer lands.\n\nThe SSH protocol carries a command string, not an argv, so each\nCommand is rendered as a single shell-quoted line — every argumen\n[…]\na genuine SSH\nconversation without a container.\n\nThis pulls x/crypto, pkg/sftp, and ssh_config into the core module per\nthe ratified layout; x/crypto's own go directive floors the module at\ngo 1.25.0.",
"is_bot": false,
"headline": "feat(ssh): Add remote command execution over SSH",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T14:24:04Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "109571b390581dd6004e6f658730b5a5fb9a03fc",
"body": "The executor adds invocation policy — retry, sudo, output capture — on\ntop of an Environment, keeping mechanism and policy separate. It exposes\nonly the operations that carry policy (Run, Output, Lines, Upload,\nDownload); LookPath, OS, Capabilities, and Close stay on the\nEnvironment, and code needin\n[…]\nrent-wait contract under -race. Godoc\nexamples land as the compiling quickstart: constructor to captured\noutput in five lines.\n\nThis completes the MVP: core, contract suite, local, fake, and executor.",
"is_bot": false,
"headline": "feat: Add the executor",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T13:56:50Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a270ddb862198aa8bf4a3c0326e53ef079d0260b",
"body": "Close the transfer blind spots that are observable on any target, each\nwith a proving misbehave defect:\n\n- binary-content-survives: a full 0-255 byte ramp plus a NUL-bracketed\n tail round-trips unchanged, catching any text-mode or C-string\n truncation. Every payload before this was short ASCII.\n- \n[…]\nhs with shell metacharacters,\nthe symlink-unsupported capability pair, transport-severed retries — are\nnot target-agnostic and belong to the ssh and docker provider tasks\nrather than the shared suite.",
"is_bot": false,
"headline": "test(invoketest): Harden transfer contracts",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T13:37:55Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "8bd6f542ff5d8b6026365349974cf61489034662",
"body": "Close the lifecycle blind spots the audit surfaced, each with a proving\nmisbehave defect:\n\n- signal-attribution-round-trips: every supported signal name (INT,\n QUIT, USR1, USR2) is delivered faithfully and reported back under the\n same name, caught by the kernel's own attribution. Only SIGTERM was\n[…]\nlent signal after exit, late-cancel overwrite, deadline-as-cancel,\nsingle-process-only, flip-flopping concurrent wait) and now tracks\noutstanding processes so the single-process defect is expressible.",
"is_bot": false,
"headline": "test(invoketest): Harden lifecycle contracts",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T13:30:08Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "210a9973ee24805c9899edfdffe0e8f9d5175d18",
"body": "A coverage and adversarial audit of the suite found behaviors a\ndeliberately broken provider could violate while passing every\ncontract. Close the execution and classification gaps, each with a\nmisbehave defect proving it can now fail:\n\n- os-matches-target: OS() is checked against the target's own u\n[…]\nize arrives\n intact; env-override-wins: an overlay beats an existing base variable\n and the last of duplicate keys wins; lookpath-classifies now also\n requires a real path, not a bare command name.",
"is_bot": false,
"headline": "test(invoketest): Harden execution and error contracts",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T13:21:46Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "993f31dc2d98e266266988ed72f591db41dd43b1",
"body": "The fake is a scriptable Environment for consumer tests, and its\nflagship property is that fake.New() passes the same invoketest suite\nthe real providers pass — 33 contracts, with only TTY allocation gated\noff honestly. Tests written against it inherit contract-accurate\nlifecycle, taxonomy, and stre\n[…]\n fstest.TestFS itself. Along the\nway the vfs grew the classic root-prefix guard: listing \"/\" must not\nbuild the child prefix \"//\" — the same edge this project's transfer\ncode guards against elsewhere.",
"is_bot": false,
"headline": "feat(fake): Add the contract-verified test double",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T11:26:37Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "98fe9ca4e6f90a1f9109066179b6b95a411f09cf",
"body": "…, and TTY\n\nErrors contracts pin the taxonomy end-to-end: missing binaries and\nLookPath misses wrap ErrNotFound, bad working directories wrap\nErrInvalidWorkdir (never an ExitError), and a closed environment\nrefuses every method with ErrClosed.\n\nTransfer contracts pin the shared file-transfer semanti\n[…]\now,\nstripped classifications, pretend TTYs — keep every contract provably\nable to fail. The reference matrix doubles as the local provider's\nparity verification: all 25 contracts run against it clean.",
"is_bot": false,
"headline": "test(invoketest): Complete the contract catalog with errors, transfer…",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T11:04:00Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1daba9fef8d799fe4fefc334127ab1197037f2b7",
"body": "Ten lifecycle contracts pin the promises that make cancellation and\ncleanup trustworthy:\n\n- Wait is idempotent; Close after Wait keeps the cached outcome; Close\n is idempotent and returns promptly (a blocking Close is a failure,\n not a hang — every blocking step carries the contract deadline).\n- C\n[…]\negistrations prove each contract can fail: detached\ncontexts, cancellation dressed as an ExitError, no-op closes at both\nlevels, forgotten outcomes, flip-flopping waits, silent and mislabeled\nsignals.",
"is_bot": false,
"headline": "test(invoketest): Add lifecycle contracts",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T10:53:56Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "77fbacfa36123f17bd6e32b62275c0b5c599859f",
"body": "Nine core contracts pin basic execution semantics: exact stdout\ncapture, stream separation, nil-stdin EOF (no inheriting, no hanging),\nstdin delivery, complete output far past pipe-buffer size on both\nstreams, env overlay over the base environment, working directory,\nexit-code agreement between Resu\n[…]\n.\n\nContracts run through a recording implementation of T, so they can be\nexecuted as test subjects; each carries its own deadline, making a\nhanging provider a failed contract rather than a hung suite.",
"is_bot": false,
"headline": "test(invoketest): Add core execution contracts and the misbehave harness",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T10:50:38Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1f7816b82355fd3d74512698a945fa9bf2df4dd9",
"body": "Upload and Download copy files and trees between local paths with the\nsemantics every provider will share:\n\n- Atomic per file: content streams into a temporary file in the\n destination directory, is flushed and given its final mode, and only\n then renamed over the destination. A failed or canceled\n[…]\nwise; a file that changes type mid-transfer\n is caught on the open handle.\n- Progress reports per-file paths relative to the transfer root with\n real totals; missing destination parents are created.",
"is_bot": false,
"headline": "feat(local): Add file transfer",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T10:39:48Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "8597890a456f1caf560e596222ef0a65bf3f667c",
"body": "The local provider runs commands on the host machine and is the\nreference Environment implementation.\n\nProcess lifecycle is built around honest, evidence-first attribution:\ncancellation and Close record their intent and kill the whole process\ngroup, and Wait credits a death to its initiator only whe\n[…]\n stated beside them (process launching and path transfer are\nthe API, not tainted input), and treats a default case as exhaustive\nsince signal mapping deliberately funnels unmapped values through one.",
"is_bot": false,
"headline": "feat(local): Add the local environment and process execution",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T10:39:21Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "6a269a24404f35735a4d2707e80432317d95ac1d",
"body": "Exit can only be observed through Wait, which may also be draining\noutput; state the achievable guarantee — observation bounded by the\nprovider's wait timeout — rather than an unimplementable one.",
"is_bot": false,
"headline": "docs: Clarify Result.Duration measurement",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T10:11:57Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f457f3a742aae6c168cae2f51080cce637546d5d",
"body": "invoketest is the executable specification for providers: Verify runs\nevery contract against environments produced by a caller-supplied\nfactory, one fresh environment per contract, so contracts stay\nindependent and destructive ones (close, kill) cannot affect later ones.\n\nOptional features gate on d\n[…]\nes not exist fails the\nrun, so opt-outs cannot outlive a rename. Contract bodies land by\ncategory; the mechanism (fresh-env policy, gating, gap validation,\nstructural invariants) is fully tested here.",
"is_bot": false,
"headline": "test: Add invoketest contract suite scaffolding",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T10:09:34Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "dd9b687968296b2203e0b00fb24e1257a225faf6",
"body": "Environment is the connection to an execution target with exactly one\nexecution primitive: Start(ctx, cmd, stdio). Run-style conveniences are\nbuilt on top later, so every provider has a single execution path to\nget right. The godoc states the behavioral contracts — the context owns\nthe process, Wait\n[…]\n symlink policy (preserve by default,\nfollowing never escapes the transfer root), special-file handling\n(error by default, opt-in skip), and per-file progress with an explicit\nUnknownTotal convention.",
"is_bot": false,
"headline": "feat: Add Environment and Process interfaces",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T09:57:38Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d5b5253a69e06b66de28492f9a442db70ade1af7",
"body": "Three disjoint failure families, so callers can always tell what\nhappened from the error alone:\n\n- ExitError: the command ran and terminated unsuccessfully, by non-zero\n exit or by signal (Code -1 with Signal set). Always terminal — the\n command may have had side effects.\n- TransportError: the tra\n[…]\nterminal: retrying must be earned\nby explicit classification, never assumed. Tests pin the messages, the\nunwrap chains, and — most importantly — that the families never\ncross-match under errors.Is/As.",
"is_bot": false,
"headline": "feat: Add error taxonomy",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T09:56:06Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d0ba75bcaaf0eda5865e838600b76d2aa452726d",
"body": "Signal is a typed name rather than an os.Signal: syscall numbers are\nhost-OS concepts that do not travel, while names map cleanly onto every\ntarget — syscall numbers locally, SSH signal names on the wire, kill -s\nin containers. Seven signals cover the interrupt/terminate/kill and\nuser-defined cases; SIGWINCH and resize forwarding are out of scope\nthis cycle.",
"is_bot": false,
"headline": "feat: Add typed signals",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T09:55:15Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1d33bceea24536331551eb680d6ccae0b0eb6d6e",
"body": "Result is a plain value: exit code and wall-clock duration, meaningful\nonly in combination with the returned error. It deliberately carries no\nerror field — the returned error is the single source of truth, so a\nzero Result can never impersonate success.\n\nTargetOS is a typed string mirroring runtime.GOOS values, comparable\nagainst GOOS constants and loggable directly; LocalOS reports the\nrunning process's own.",
"is_bot": false,
"headline": "feat: Add Result and TargetOS",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T09:54:48Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b733743a731760fd95983c871dae2c1b0e1789b5",
"body": "IO wires the streams for exactly one invocation of a Command, keeping\nper-run state out of the reusable spec. Its zero value is safe and\nnon-interactive: immediate EOF on stdin, outputs discarded.\n\nTTY requests a pseudo-terminal with named Cols/Rows fields; Size()\nsubstitutes the 80x24 default for unset dimensions. When a TTY is set,\nstderr merges into stdout — the pseudo-terminal reality on every\ntarget — so IO.Stderr documents that it is ignored in that mode.",
"is_bot": false,
"headline": "feat: Add IO and TTY invocation types",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T09:54:11Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d77bcc9f6d287a5a95fca969f2a5d5a2a7519d10",
"body": "A Command is a pure value describing what to run — executable, args,\nenv overlay, working directory. It carries no per-invocation state, so\none value can be started any number of times, sequentially or\nconcurrently; streams and TTY allocation belong to the invocation and\narrive separately as IO.\n\nNew constructs the direct form; Shell wraps a script in sh -c and\ndocuments that the script is code, with quoting owned by the caller.",
"is_bot": false,
"headline": "feat: Add Command value spec",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T09:53:13Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "640fd00529c02386616288a8cabbd3a85bce8100",
"body": "Four jobs, each with timeout-minutes, under a cancel-in-progress\nconcurrency group and a read-only token:\n\n- Test: unit tests with the race detector on ubuntu and macos.\n- Lint: pinned golangci-lint (built from source, no third-party setup\n action), config verification, formatting check, and go.mod\n[…]\nruns commands directly rather than via just, trading recipe reuse for\none less supply-chain dependency; the Justfile documents the pairing.\n\nDependabot covers gomod and github-actions weekly, grouped.",
"is_bot": false,
"headline": "ci: Add workflows and dependabot configuration",
"author_name": "Steven Cartmell",
"author_login": "ruffel",
"committed_at": "2026-07-17T09:48:55Z",
"body_truncated": true,
"is_coding_agent": false
}
],
"releases_count": 3,
"commits_last_year": 104,
"latest_release_at": "2026-07-24T08:13:57Z",
"latest_release_tag": "v0.5.0",
"releases_from_tags": false,
"days_since_last_push": 3,
"active_weeks_last_year": 3,
"days_since_latest_release": 3,
"mean_days_between_releases": 2.2
},
"community": {
"has_readme": true,
"has_license": true,
"has_description": true,
"has_contributing": true,
"health_percentage": 71,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"ecosystem": {
"packages": [
{
"name": "github.com/ruffel/invoke",
"exists": true,
"license": null,
"keywords": [],
"ecosystem": "go",
"matches_repo": true,
"registry_url": "https://pkg.go.dev/github.com/ruffel/invoke",
"is_deprecated": false,
"latest_version": "v0.5.0",
"repository_url": "https://github.com/ruffel/invoke",
"versions_count": 8,
"total_downloads": null,
"dependents_count": null,
"deprecation_note": null,
"maintainers_count": null,
"monthly_downloads": null,
"first_published_at": null,
"latest_published_at": "2026-07-24T07:53:51Z",
"latest_version_yanked": null,
"days_since_latest_publish": 3
},
{
"name": "github.com/ruffel/invoke/docker",
"exists": true,
"license": null,
"keywords": [],
"ecosystem": "go",
"matches_repo": true,
"registry_url": "https://pkg.go.dev/github.com/ruffel/invoke/docker",
"is_deprecated": false,
"latest_version": "v0.5.0",
"repository_url": "https://github.com/ruffel/invoke",
"versions_count": 4,
"total_downloads": null,
"dependents_count": null,
"deprecation_note": null,
"maintainers_count": null,
"monthly_downloads": null,
"first_published_at": null,
"latest_published_at": "2026-07-24T08:06:18Z",
"latest_version_yanked": null,
"days_since_latest_publish": 3
}
]
},
"popularity": {
"forks": 0,
"stars": 1,
"watchers": 0,
"fork_history": {
"days": [],
"complete": true,
"collected": 0,
"total_forks": 0
},
"star_history": null,
"open_issues_and_prs": 0
},
"ai_readiness": {
"has_nix": false,
"example_dirs": [],
"has_llms_txt": false,
"has_dockerfile": false,
"has_mcp_signal": false,
"bootstrap_files": [
"Justfile"
],
"api_schema_files": [],
"has_devcontainer": false,
"typecheck_configs": [],
"toolchain_manifests": [
"docker/go.mod",
"go.mod"
],
"largest_source_bytes": 28050,
"source_files_sampled": 96,
"oversized_source_files": 0,
"agent_instruction_files": [],
"agent_instruction_max_bytes": null
},
"dependencies": {
"manifests": [
"docker/go.mod",
"go.mod"
],
"advisories": {
"error": null,
"scope": "repository_graph",
"source": "osv",
"findings": [
{
"name": "github.com/docker/docker",
"direct": true,
"version": "v28.5.2+incompatible",
"severity": "high",
"ecosystem": "go",
"cvss_score": 8.8,
"advisory_ids": [
"GHSA-pxq6-2prw-chj9",
"GHSA-rg2x-37c3-w2rh",
"GHSA-vp62-88p7-qqf5",
"GHSA-x744-4wpc-v9h2",
"GHSA-x86f-5xw2-fm2r",
"GO-2026-4883",
"GO-2026-4887",
"GO-2026-5617",
"GO-2026-5668",
"GO-2026-5746"
],
"fixed_version": "29.3.1",
"advisory_count": 10,
"oldest_advisory_days": 121
},
{
"name": "golang.org/x/crypto",
"direct": true,
"version": "v0.54.0",
"severity": "unknown",
"ecosystem": "go",
"cvss_score": null,
"advisory_ids": [
"GO-2026-5932"
],
"fixed_version": null,
"advisory_count": 1,
"oldest_advisory_days": 19
}
],
"collected": true,
"malicious": [],
"truncated": false,
"by_severity": {
"high": 1,
"unknown": 1
},
"advisory_count": 11,
"affected_count": 2,
"assessed_count": 39,
"malicious_count": 0,
"assessed_package": null,
"unassessed_count": 0,
"direct_affected_count": 2
},
"ecosystems": [
"go"
],
"dependencies": [
{
"name": "github.com/containerd/errdefs",
"manifest": "docker/go.mod",
"ecosystem": "go",
"version_constraint": "v1.0.0"
},
{
"name": "github.com/docker/docker",
"manifest": "docker/go.mod",
"ecosystem": "go",
"version_constraint": "v28.5.2+incompatible"
},
{
"name": "github.com/ruffel/invoke",
"manifest": "docker/go.mod",
"ecosystem": "go",
"version_constraint": "v0.5.0"
},
{
"name": "github.com/stretchr/testify",
"manifest": "docker/go.mod",
"ecosystem": "go",
"version_constraint": "v1.11.1"
},
{
"name": "github.com/creack/pty",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v1.1.24"
},
{
"name": "github.com/pkg/sftp",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v1.13.11"
},
{
"name": "github.com/skeema/knownhosts",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v1.3.2"
},
{
"name": "github.com/stretchr/testify",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v1.11.1"
},
{
"name": "golang.org/x/crypto",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v0.54.0"
}
],
"all_dependencies": {
"error": null,
"source": "github-sbom",
"packages": [
{
"name": "github.com/containerd/errdefs",
"direct": true,
"version": "v1.0.0",
"ecosystem": "go"
},
{
"name": "github.com/creack/pty",
"direct": true,
"version": "v1.1.24",
"ecosystem": "go"
},
{
"name": "github.com/docker/docker",
"direct": true,
"version": "v28.5.2+incompatible",
"ecosystem": "go"
},
{
"name": "github.com/pkg/sftp",
"direct": true,
"version": "v1.13.11",
"ecosystem": "go"
},
{
"name": "github.com/ruffel/invoke",
"direct": true,
"version": "v0.4.0",
"ecosystem": "go"
},
{
"name": "github.com/ruffel/invoke",
"direct": true,
"version": "v0.5.0",
"ecosystem": "go"
},
{
"name": "github.com/skeema/knownhosts",
"direct": true,
"version": "v1.3.2",
"ecosystem": "go"
},
{
"name": "github.com/stretchr/testify",
"direct": true,
"version": "v1.11.1",
"ecosystem": "go"
},
{
"name": "golang.org/x/crypto",
"direct": true,
"version": "v0.54.0",
"ecosystem": "go"
},
{
"name": "github.com/cespare/xxhash/v2",
"direct": false,
"version": "v2.3.0",
"ecosystem": "go"
},
{
"name": "github.com/containerd/errdefs/pkg",
"direct": false,
"version": "v0.3.0",
"ecosystem": "go"
},
{
"name": "github.com/containerd/log",
"direct": false,
"version": "v0.1.0",
"ecosystem": "go"
},
{
"name": "github.com/davecgh/go-spew",
"direct": false,
"version": "v1.1.1",
"ecosystem": "go"
},
{
"name": "github.com/distribution/reference",
"direct": false,
"version": "v0.6.0",
"ecosystem": "go"
},
{
"name": "github.com/docker/go-connections",
"direct": false,
"version": "v0.7.0",
"ecosystem": "go"
},
{
"name": "github.com/docker/go-units",
"direct": false,
"version": "v0.5.0",
"ecosystem": "go"
},
{
"name": "github.com/felixge/httpsnoop",
"direct": false,
"version": "v1.0.4",
"ecosystem": "go"
},
{
"name": "github.com/go-logr/logr",
"direct": false,
"version": "v1.4.3",
"ecosystem": "go"
},
{
"name": "github.com/go-logr/stdr",
"direct": false,
"version": "v1.2.2",
"ecosystem": "go"
},
{
"name": "github.com/kr/fs",
"direct": false,
"version": "v0.1.0",
"ecosystem": "go"
},
{
"name": "github.com/microsoft/go-winio",
"direct": false,
"version": "v0.6.2",
"ecosystem": "go"
},
{
"name": "github.com/moby/docker-image-spec",
"direct": false,
"version": "v1.3.1",
"ecosystem": "go"
},
{
"name": "github.com/moby/sys/atomicwriter",
"direct": false,
"version": "v0.1.0",
"ecosystem": "go"
},
{
"name": "github.com/moby/term",
"direct": false,
"version": "v0.5.2",
"ecosystem": "go"
},
{
"name": "github.com/morikuni/aec",
"direct": false,
"version": "v1.1.0",
"ecosystem": "go"
},
{
"name": "github.com/opencontainers/go-digest",
"direct": false,
"version": "v1.0.0",
"ecosystem": "go"
},
{
"name": "github.com/opencontainers/image-spec",
"direct": false,
"version": "v1.1.1",
"ecosystem": "go"
},
{
"name": "github.com/pkg/errors",
"direct": false,
"version": "v0.9.1",
"ecosystem": "go"
},
{
"name": "github.com/pmezard/go-difflib",
"direct": false,
"version": "v1.0.0",
"ecosystem": "go"
},
{
"name": "go.opentelemetry.io/auto/sdk",
"direct": false,
"version": "v1.2.1",
"ecosystem": "go"
},
{
"name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp",
"direct": false,
"version": "v0.69.0",
"ecosystem": "go"
},
{
"name": "go.opentelemetry.io/otel",
"direct": false,
"version": "v1.44.0",
"ecosystem": "go"
},
{
"name": "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp",
"direct": false,
"version": "v1.44.0",
"ecosystem": "go"
},
{
"name": "go.opentelemetry.io/otel/metric",
"direct": false,
"version": "v1.44.0",
"ecosystem": "go"
},
{
"name": "go.opentelemetry.io/otel/trace",
"direct": false,
"version": "v1.44.0",
"ecosystem": "go"
},
{
"name": "golang.org/x/sys",
"direct": false,
"version": "v0.47.0",
"ecosystem": "go"
},
{
"name": "golang.org/x/time",
"direct": false,
"version": "v0.15.0",
"ecosystem": "go"
},
{
"name": "gopkg.in/yaml.v3",
"direct": false,
"version": "v3.0.1",
"ecosystem": "go"
},
{
"name": "gotest.tools/v3",
"direct": false,
"version": "v3.5.2",
"ecosystem": "go"
}
],
"collected": true,
"truncated": false,
"total_count": 39,
"direct_count": 9,
"indirect_count": 30
}
},
"maintainership": {
"issues": {
"open_prs": 0,
"merged_prs": 47,
"open_issues": 0,
"closed_ratio": null,
"closed_issues": 0,
"closed_unmerged_prs": 1
},
"bus_factor": 1,
"bot_contributors": 1,
"top_contributors": [
{
"type": "User",
"login": "ruffel",
"commits": 103,
"avatar_url": "https://avatars.githubusercontent.com/u/45599587?v=4"
}
],
"contributors_sampled": 1,
"top_contributor_share": 1
},
"quality_signals": {
"has_ci": true,
"has_tests": true,
"ci_workflows": [
"ci.yml",
"release-drafter.yml",
"release-verify.yml"
],
"has_docs_dir": false,
"linter_configs": [
".golangci.yml"
],
"has_editorconfig": false,
"has_linter_config": true,
"has_precommit_config": false
},
"security_signals": {
"lockfiles": [
"go.sum"
],
"scorecard": {
"checks": [
{
"name": "Binary-Artifacts",
"score": 10,
"reason": "no binaries found in the repo",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
},
{
"name": "Branch-Protection",
"score": 0,
"reason": "branch protection not enabled on development/release branches",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
},
{
"name": "CI-Tests",
"score": 10,
"reason": "28 out of 28 merged PRs checked by a CI test -- score normalized to 10",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
},
{
"name": "CII-Best-Practices",
"score": 0,
"reason": "no effort to earn an OpenSSF best practices badge detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
},
{
"name": "Code-Review",
"score": 0,
"reason": "Found 0/29 approved changesets -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
},
{
"name": "Contributors",
"score": 0,
"reason": "project has 0 contributing companies or organizations -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
},
{
"name": "Dangerous-Workflow",
"score": 10,
"reason": "no dangerous workflow patterns detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
},
{
"name": "Dependency-Update-Tool",
"score": 10,
"reason": "update tool detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
},
{
"name": "Fuzzing",
"score": 0,
"reason": "project is not fuzzed",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
},
{
"name": "License",
"score": 10,
"reason": "license file detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
},
{
"name": "Maintained",
"score": 0,
"reason": "project was created within the last 90 days. Please review its contents carefully",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
},
{
"name": "Packaging",
"score": null,
"reason": "packaging workflow not detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
},
{
"name": "Pinned-Dependencies",
"score": 10,
"reason": "all dependencies are pinned",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
},
{
"name": "SAST",
"score": 0,
"reason": "SAST tool is not run on all commits -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
},
{
"name": "Security-Policy",
"score": 10,
"reason": "security policy file detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
},
{
"name": "Signed-Releases",
"score": null,
"reason": "no releases found",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
},
{
"name": "Token-Permissions",
"score": 10,
"reason": "GitHub workflow tokens follow principle of least privilege",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
},
{
"name": "Vulnerabilities",
"score": 4,
"reason": "6 existing vulnerabilities detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
}
],
"commit": "e389d05eb38a891f0ddf4afddb8d297f6937c6bc",
"ran_at": "2026-07-27T15:10:12Z",
"aggregate_score": 5.5,
"scorecard_version": "v5.5.0"
},
"has_codeql_workflow": false,
"has_security_policy": true,
"has_dependabot_config": true
},
"contribution_flow": {
"collected": true,
"ci_last_run_at": "2026-07-24T21:13:58Z",
"oldest_open_prs": [],
"last_merged_pr_at": "2026-07-24T11:26:40Z",
"ci_last_conclusion": "SUCCESS",
"oldest_open_issues": []
}
},
"config": {
"disabled_metrics": [],
"disabled_categories": [],
"disabled_components": {}
},
"source": {
"url": "https://github.com/ruffel/invoke",
"host": "github.com",
"name": "invoke",
"owner": "ruffel"
},
"metrics": {
"overall": {
"key": "overall",
"band": "moderate",
"name": "Overall health",
"note": null,
"notes": [],
"value": 57,
"inputs": {
"security": 55,
"vitality": 74,
"community": 33,
"governance": 49,
"engineering": 70
},
"components": []
},
"categories": [
{
"key": "vitality",
"band": "good",
"name": "Vitality",
"value": 74,
"weight": 0.22,
"metrics": [
{
"key": "development_activity",
"band": "moderate",
"name": "Development activity",
"note": null,
"notes": [],
"value": 56,
"inputs": {
"commits_last_year": 104,
"human_commit_share": 0.99,
"days_since_last_push": 3,
"active_weeks_last_year": 3
},
"components": [
{
"key": "push_recency",
"name": "Push recency",
"detail": "last push 3 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "push_recency",
"params": {
"days": 3
}
}
],
"max_points": 36
},
{
"key": "commit_cadence",
"name": "Commit cadence",
"detail": "3/52 weeks with commits",
"points": 2.1,
"status": "partial",
"details": [
{
"code": "commit_cadence_weeks",
"params": {
"weeks": 3
}
}
],
"max_points": 36
},
{
"key": "commit_volume",
"name": "Commit volume",
"detail": "104 commits in the last year",
"points": 18,
"status": "met",
"details": [
{
"code": "commits_last_year",
"params": {
"count": 104
}
}
],
"max_points": 18
},
{
"key": "openssf_scorecard_maintained",
"name": "OpenSSF Scorecard: Maintained",
"detail": "project was created within the last 90 days. Please review its contents carefully",
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
},
{
"key": "release_discipline",
"band": "excellent",
"name": "Release discipline",
"note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Signed-Releases. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"openssf_scorecard_signed_releases"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 100,
"inputs": {
"releases_count": 3,
"latest_release_tag": "v0.5.0",
"releases_from_tags": false,
"days_since_latest_release": 3,
"mean_days_between_releases": 2.2
},
"components": [
{
"key": "ships_releases",
"name": "Ships releases",
"detail": "3 releases published",
"points": 27,
"status": "met",
"details": [
{
"code": "releases_published",
"params": {
"count": 3
}
}
],
"max_points": 27
},
{
"key": "release_recency",
"name": "Release recency",
"detail": "latest release 3 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "release_recency",
"params": {
"days": 3
}
}
],
"max_points": 36
},
{
"key": "release_cadence",
"name": "Release cadence",
"detail": "a release every ~2.2 days",
"points": 27,
"status": "met",
"details": [
{
"code": "release_cadence",
"params": {
"gap": 2.2
}
}
],
"max_points": 27
},
{
"key": "openssf_scorecard_signed_releases",
"name": "OpenSSF Scorecard: Signed-Releases",
"detail": "no releases found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 10
}
]
},
{
"key": "abandonment",
"band": "excellent",
"name": "Abandonment",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"cap": null,
"state": "unverified",
"guards": [],
"signals": [],
"red_flag": false,
"multiplier_pct": 100,
"declared_reason": null,
"unverified_reason": "repository_too_young",
"unanswered_open_prs": null,
"unanswered_open_issues": null,
"days_since_last_merged_pr": null,
"days_since_last_human_commit": null,
"days_since_last_human_commit_is_floor": false
},
"components": [
{
"key": "project_is_still_maintained",
"name": "Project is still maintained",
"detail": "maintenance record not established from the collected data",
"points": 100,
"status": "met",
"details": [
{
"code": "abandonment_unverified",
"params": {}
}
],
"max_points": 100
}
]
}
],
"description": "Is the project alive — is code being written and are releases shipping?"
},
{
"key": "community",
"band": "at_risk",
"name": "Community & Adoption",
"value": 33,
"weight": 0.18,
"metrics": [
{
"key": "popularity",
"band": "critical",
"name": "Popularity & adoption",
"note": null,
"notes": [],
"value": 1,
"inputs": {
"forks": 0,
"stars": 1,
"watchers": 0,
"growth_state": "unverified",
"growth_factor_pct": 100,
"growth_unverified_reason": "no_history"
},
"components": [
{
"key": "stars",
"name": "Stars",
"detail": "1 stars",
"points": 0,
"status": "missed",
"details": [
{
"code": "stars",
"params": {
"count": 1
}
}
],
"max_points": 60
},
{
"key": "forks",
"name": "Forks",
"detail": "0 forks",
"points": 0,
"status": "missed",
"details": [
{
"code": "forks",
"params": {
"count": 0
}
}
],
"max_points": 25
},
{
"key": "watchers",
"name": "Watchers",
"detail": "0 watchers",
"points": 0,
"status": "missed",
"details": [
{
"code": "watchers",
"params": {
"count": 0
}
}
],
"max_points": 15
}
]
},
{
"key": "community_health",
"band": "good",
"name": "Community health",
"note": null,
"notes": [],
"value": 70,
"inputs": {
"has_readme": true,
"has_license": true,
"has_contributing": true,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 22.5,
"status": "met",
"details": [],
"max_points": 22.5
},
{
"key": "license",
"name": "License",
"detail": "recognized license (MIT)",
"points": 22.5,
"status": "met",
"details": [
{
"code": "license_standard",
"params": {}
},
{
"code": "license_spdx",
"params": {
"spdx": "MIT"
}
}
],
"max_points": 22.5
},
{
"key": "contributing_guide",
"name": "CONTRIBUTING guide",
"detail": null,
"points": 18,
"status": "met",
"details": [],
"max_points": 18
},
{
"key": "code_of_conduct",
"name": "Code of conduct",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 13.5
},
{
"key": "issue_template",
"name": "Issue template",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.2
},
{
"key": "pr_template",
"name": "PR template",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 6.3
}
]
}
],
"description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
},
{
"key": "governance",
"band": "at_risk",
"name": "Sustainability & Governance",
"value": 49,
"weight": 0.24,
"metrics": [
{
"key": "maintainer_resilience",
"band": "critical",
"name": "Maintainer resilience (bus factor)",
"note": null,
"notes": [],
"value": 10,
"inputs": {
"bus_factor": 1,
"contributors_sampled": 1,
"top_contributor_share": 1
},
"components": [
{
"key": "bus_factor",
"name": "Bus factor",
"detail": "1 contributor(s) cover half of all commits",
"points": 9,
"status": "partial",
"details": [
{
"code": "bus_factor",
"params": {
"count": 1
}
}
],
"max_points": 54
},
{
"key": "commit_distribution",
"name": "Commit distribution",
"detail": "top contributor authored 100% of commits",
"points": 0,
"status": "missed",
"details": [
{
"code": "top_contributor_share",
"params": {
"share": 100
}
}
],
"max_points": 22.5
},
{
"key": "contributor_breadth",
"name": "Contributor breadth",
"detail": "1 contributors",
"points": 1.4,
"status": "partial",
"details": [
{
"code": "contributors_sampled",
"params": {
"count": 1
}
}
],
"max_points": 13.5
},
{
"key": "openssf_scorecard_contributors",
"name": "OpenSSF Scorecard: Contributors",
"detail": "project has 0 contributing companies or organizations -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
},
{
"key": "responsiveness",
"band": "good",
"name": "Issue & PR responsiveness",
"note": "Excluded from scoring (no data or not applicable): Issue resolution. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"issue_resolution"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 70,
"inputs": {
"merged_prs": 47,
"open_issues": 0,
"closed_issues": 0,
"issue_closed_ratio": null,
"closed_unmerged_prs": 1
},
"components": [
{
"key": "issue_resolution",
"name": "Issue resolution",
"detail": "no issues or no data",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_issues_or_data",
"params": {}
}
],
"max_points": 46.75
},
{
"key": "pr_acceptance",
"name": "PR acceptance",
"detail": "47/48 decided PRs merged",
"points": 37.5,
"status": "partial",
"details": [
{
"code": "decided_prs_merged",
"params": {
"merged": 47,
"decided": 48
}
}
],
"max_points": 38.25
},
{
"key": "openssf_scorecard_code_review",
"name": "OpenSSF Scorecard: Code-Review",
"detail": "Found 0/29 approved changesets -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
}
]
},
{
"key": "stewardship",
"band": "at_risk",
"name": "Ownership & stewardship",
"note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"verified_domain"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 34,
"inputs": {
"followers": 0,
"owner_type": "User",
"is_verified": null,
"owner_login": "ruffel",
"public_repos": 4,
"account_age_days": 2792
},
"components": [
{
"key": "ownership_backing",
"name": "Ownership backing",
"detail": "personal (user) account",
"points": 10,
"status": "partial",
"details": [
{
"code": "owner_personal",
"params": {}
}
],
"max_points": 30
},
{
"key": "verified_domain",
"name": "Verified domain",
"detail": "not applicable to user accounts",
"points": 0,
"status": "excluded",
"details": [
{
"code": "not_applicable_to_user_accounts",
"params": {}
}
],
"max_points": 20
},
{
"key": "owner_reach",
"name": "Owner reach",
"detail": "0 followers of ruffel",
"points": 0,
"status": "missed",
"details": [
{
"code": "owner_followers",
"params": {
"count": 0,
"login": "ruffel"
}
}
],
"max_points": 25
},
{
"key": "track_record",
"name": "Track record",
"detail": "4 public repos, account ~7 yr old",
"points": 17.1,
"status": "partial",
"details": [
{
"code": "public_repos",
"params": {
"count": 4
}
},
{
"code": "account_age_years",
"params": {
"years": 7
}
}
],
"max_points": 25
}
]
},
{
"key": "package_maintenance",
"band": "excellent",
"name": "Package maintenance",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"packages": [
"github.com/ruffel/invoke",
"github.com/ruffel/invoke/docker"
],
"ecosystems": "go",
"any_deprecated": false,
"min_days_since_publish": 3
},
"components": [
{
"key": "published_resolvable",
"name": "Published & resolvable",
"detail": "2 package(s) on go",
"points": 25,
"status": "met",
"details": [
{
"code": "packages_published",
"params": {
"count": 2,
"ecosystems": "go"
}
}
],
"max_points": 25
},
{
"key": "publish_recency",
"name": "Publish recency",
"detail": "latest publish 3 days ago",
"points": 35,
"status": "met",
"details": [
{
"code": "publish_recency",
"params": {
"days": 3
}
}
],
"max_points": 35
},
{
"key": "version_history",
"name": "Version history",
"detail": "8 published versions",
"points": 20,
"status": "met",
"details": [
{
"code": "published_versions",
"params": {
"count": 8
}
}
],
"max_points": 20
},
{
"key": "not_deprecated",
"name": "Not deprecated",
"detail": "active, not deprecated or yanked",
"points": 20,
"status": "met",
"details": [
{
"code": "package_not_deprecated",
"params": {}
}
],
"max_points": 20
}
]
}
],
"description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
},
{
"key": "engineering",
"band": "good",
"name": "Engineering Quality",
"value": 70,
"weight": 0.2,
"metrics": [
{
"key": "engineering_practices",
"band": "good",
"name": "Engineering practices",
"note": null,
"notes": [],
"value": 84,
"inputs": {
"has_ci": true,
"has_tests": true,
"has_editorconfig": false,
"has_linter_config": true,
"has_precommit_config": false
},
"components": [
{
"key": "ci_workflows",
"name": "CI workflows",
"detail": "3 workflow(s)",
"points": 24,
"status": "met",
"details": [
{
"code": "ci_workflows",
"params": {
"count": 3
}
}
],
"max_points": 24
},
{
"key": "tests_present",
"name": "Tests present",
"detail": null,
"points": 24,
"status": "met",
"details": [],
"max_points": 24
},
{
"key": "linter_config",
"name": "Linter config",
"detail": ".golangci.yml",
"points": 16,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": ".golangci.yml"
}
}
],
"max_points": 16
},
{
"key": "pre_commit_hooks",
"name": "Pre-commit hooks",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 9.6
},
{
"key": "editorconfig",
"name": ".editorconfig",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 6.4
},
{
"key": "openssf_scorecard_ci_tests",
"name": "OpenSSF Scorecard: CI-Tests",
"detail": "28 out of 28 merged PRs checked by a CI test -- score normalized to 10",
"points": 20,
"status": "met",
"details": [],
"max_points": 20
}
]
},
{
"key": "documentation",
"band": "moderate",
"name": "Documentation",
"note": null,
"notes": [],
"value": 50,
"inputs": {
"topics": [],
"has_wiki": true,
"homepage": null,
"has_readme": true,
"has_docs_dir": false,
"has_description": true
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 30,
"status": "met",
"details": [],
"max_points": 30
},
{
"key": "documentation_directory",
"name": "Documentation directory",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 25
},
{
"key": "documentation_homepage_site",
"name": "Documentation / homepage site",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
},
{
"key": "repository_description",
"name": "Repository description",
"detail": null,
"points": 10,
"status": "met",
"details": [],
"max_points": 10
},
{
"key": "topics",
"name": "Topics",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
},
{
"key": "wiki",
"name": "Wiki",
"detail": null,
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
}
],
"description": "Are baseline engineering and documentation practices in place?"
},
{
"key": "security",
"band": "moderate",
"name": "Security",
"value": 55,
"weight": 0.16,
"metrics": [
{
"key": "security_posture",
"band": "moderate",
"name": "Security posture",
"note": "Excluded from scoring (no data or not applicable): Packaging, Signed-Releases. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"packaging",
"signed_releases"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 55,
"inputs": {
"source": "openssf_scorecard",
"checks_evaluated": 16,
"scorecard_version": "v5.5.0",
"checks_inconclusive": 2,
"scorecard_aggregate": 5.5
},
"components": [
{
"key": "binary_artifacts",
"name": "Binary-Artifacts",
"detail": "no binaries found in the repo",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "branch_protection",
"name": "Branch-Protection",
"detail": "branch protection not enabled on development/release branches",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "ci_tests",
"name": "CI-Tests",
"detail": "28 out of 28 merged PRs checked by a CI test -- score normalized to 10",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "cii_best_practices",
"name": "CII-Best-Practices",
"detail": "no effort to earn an OpenSSF best practices badge detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 2.5
},
{
"key": "code_review",
"name": "Code-Review",
"detail": "Found 0/29 approved changesets -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "contributors",
"name": "Contributors",
"detail": "project has 0 contributing companies or organizations -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 2.5
},
{
"key": "dangerous_workflow",
"name": "Dangerous-Workflow",
"detail": "no dangerous workflow patterns detected",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
},
{
"key": "dependency_update_tool",
"name": "Dependency-Update-Tool",
"detail": "update tool detected",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "fuzzing",
"name": "Fuzzing",
"detail": "project is not fuzzed",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "license",
"name": "License",
"detail": "license file detected",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "maintained",
"name": "Maintained",
"detail": "project was created within the last 90 days. Please review its contents carefully",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "packaging",
"name": "Packaging",
"detail": "packaging workflow not detected",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 5
},
{
"key": "pinned_dependencies",
"name": "Pinned-Dependencies",
"detail": "all dependencies are pinned",
"points": 5,
"status": "met",
"details": [],
"max_points": 5
},
{
"key": "sast",
"name": "SAST",
"detail": "SAST tool is not run on all commits -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "security_policy",
"name": "Security-Policy",
"detail": "security policy file detected",
"points": 5,
"status": "met",
"details": [],
"max_points": 5
},
{
"key": "signed_releases",
"name": "Signed-Releases",
"detail": "no releases found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 7.5
},
{
"key": "token_permissions",
"name": "Token-Permissions",
"detail": "GitHub workflow tokens follow principle of least privilege",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "vulnerabilities",
"name": "Vulnerabilities",
"detail": "6 existing vulnerabilities detected",
"points": 3,
"status": "partial",
"details": [],
"max_points": 7.5
}
]
},
{
"key": "dependency_advisories",
"band": "moderate",
"name": "Dependency advisories",
"note": "Excluded from scoring (no data or not applicable): Indirect dependencies free of known advisories. Remaining weights renormalized. Matched 39 resolved dependencies against OSV. This repository publishes no package the index resolves, so the repository dependency graph was assessed instead. That graph mixes development and test pins with shipped dependencies, so only the declared runtime dependencies are scored; transitive findings are reported as context and excluded from the score. Reachability is not analyzed.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"indirect_dependencies_free_of_known_advisories"
]
}
},
{
"code": "weights_renormalized",
"params": {}
},
{
"code": "advisories_scope_repository",
"params": {
"assessed": 39
}
},
{
"code": "advisories_repo_graph_caveat",
"params": {}
},
{
"code": "advisories_reachability",
"params": {}
}
],
"value": 57,
"inputs": {
"source": "osv",
"advisories": 11,
"affected_packages": 2,
"assessed_packages": 39,
"unassessed_packages": 0,
"affected_by_severity": "high 1, unknown 1",
"direct_affected_packages": 2
},
"components": [
{
"key": "direct_dependencies_free_of_known_advisories",
"name": "Direct dependencies free of known advisories",
"detail": "2 affected: github.com/docker/docker v28.5.2+incompatible (high 8.8), golang.org/x/crypto v0.54.0 (unknown)",
"points": 9.6,
"status": "partial",
"details": [
{
"code": "advisories_affected",
"params": {
"count": 2,
"packages": "github.com/docker/docker v28.5.2+incompatible (high 8.8), golang.org/x/crypto v0.54.0 (unknown)"
}
}
],
"max_points": 35
},
{
"key": "indirect_dependencies_free_of_known_advisories",
"name": "Indirect dependencies free of known advisories",
"detail": "transitive set not separable from development and test dependencies in this scope",
"points": 0,
"status": "excluded",
"details": [
{
"code": "advisories_scope_not_separable",
"params": {}
}
],
"max_points": 25
},
{
"key": "no_advisories_left_outstanding",
"name": "No advisories left outstanding",
"detail": "1 advisory-carrying package(s) unaddressed past 90 days; oldest published 121 days ago",
"points": 32.8,
"status": "partial",
"details": [
{
"code": "advisories_stale",
"params": {
"days": 90,
"count": 1,
"oldest": 121
}
}
],
"max_points": 40
}
]
},
{
"key": "malicious_dependencies",
"band": "excellent",
"name": "Malicious dependencies",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"source": "osv",
"meaning": "reported as a malicious package by the OpenSSF corpus; the remedy is removal or moving off the compromised name, never an upgrade of the same artifact. Versions the registry has since pulled are listed but not scored",
"packages": [],
"red_flag": false,
"assessed_packages": 39,
"malicious_packages": 0,
"direct_malicious_packages": 0,
"withdrawn_malicious_packages": 0,
"installable_malicious_packages": 0
},
"components": [
{
"key": "no_dependency_reported_as_a_malicious_package",
"name": "No dependency reported as a malicious package",
"detail": "no dependency is reported as a malicious package",
"points": 100,
"status": "met",
"details": [
{
"code": "no_malicious_dependencies",
"params": {}
}
],
"max_points": 100
}
]
}
],
"description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
},
{
"key": "ai_readiness",
"band": "good",
"name": "AI Readiness",
"value": 74,
"weight": 0,
"metrics": [
{
"key": "ai_agent_context",
"band": "at_risk",
"name": "Agent context & guidance",
"note": null,
"notes": [],
"value": 40,
"inputs": {
"has_llms_txt": false,
"legible_history_share": 1,
"agent_instruction_files": [],
"agent_instruction_max_bytes": null
},
"components": [
{
"key": "agent_instructions",
"name": "Agent instructions",
"detail": "no CLAUDE.md / AGENTS.md / editor rules",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_agent_instructions",
"params": {}
}
],
"max_points": 45
},
{
"key": "machine_readable_docs_llms_txt",
"name": "Machine-readable docs (llms.txt)",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
},
{
"key": "legible_commit_history",
"name": "Legible commit history",
"detail": "99 of 99 human commits state their intent (structured subject or explanatory body)",
"points": 40,
"status": "met",
"details": [
{
"code": "legible_history",
"params": {
"legible": 99,
"sampled": 99
}
}
],
"max_points": 40
}
]
},
{
"key": "ai_verify_loop",
"band": "excellent",
"name": "Verify loop (build / test / typecheck)",
"note": null,
"notes": [],
"value": 90,
"inputs": {
"has_nix": false,
"has_tests": true,
"lockfiles": [
"go.sum"
],
"has_dockerfile": false,
"typed_language": true,
"bootstrap_files": [
"Justfile"
],
"has_devcontainer": false,
"has_linter_config": true,
"typecheck_configs": [],
"agent_commit_share": 0,
"toolchain_manifests": [
"docker/go.mod",
"go.mod"
],
"dependency_bot_commit_share": 0.01
},
"components": [
{
"key": "one_command_bootstrap",
"name": "One-command bootstrap",
"detail": "Justfile",
"points": 18,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "Justfile"
}
}
],
"max_points": 18
},
{
"key": "automated_tests",
"name": "Automated tests",
"detail": null,
"points": 22,
"status": "met",
"details": [],
"max_points": 22
},
{
"key": "lint_format_config",
"name": "Lint / format config",
"detail": ".golangci.yml",
"points": 11,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": ".golangci.yml"
}
}
],
"max_points": 11
},
{
"key": "static_type_checking",
"name": "Static type checking",
"detail": "Go (statically typed)",
"points": 11,
"status": "met",
"details": [
{
"code": "statically_typed_language",
"params": {
"language": "Go"
}
}
],
"max_points": 11
},
{
"key": "reproducible_environment",
"name": "Reproducible environment",
"detail": "lockfile",
"points": 10,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "lockfile"
}
}
],
"max_points": 10
},
{
"key": "demonstrated_agent_practice",
"name": "Demonstrated agent practice",
"detail": "no agent-authored commits among the last 100",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_agent_authored_commits",
"params": {
"sampled": 100
}
}
],
"max_points": 10
},
{
"key": "automated_maintenance",
"name": "Automated maintenance",
"detail": "1 of the last 100 commits are automated dependency updates",
"points": 8,
"status": "met",
"details": [
{
"code": "dependency_bot_commits",
"params": {
"count": 1,
"sampled": 100
}
}
],
"max_points": 8
},
{
"key": "openssf_scorecard_pinned_dependencies",
"name": "OpenSSF Scorecard: Pinned-Dependencies",
"detail": "all dependencies are pinned",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
},
{
"key": "ai_code_legibility",
"band": "excellent",
"name": "Code legibility for models",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"primary_language": "Go",
"largest_source_bytes": 28050,
"source_files_sampled": 96,
"oversized_source_files": 0
},
"components": [
{
"key": "type_checkable_code",
"name": "Type-checkable code",
"detail": "Go (statically typed)",
"points": 45,
"status": "met",
"details": [
{
"code": "statically_typed_language",
"params": {
"language": "Go"
}
}
],
"max_points": 45
},
{
"key": "manageable_file_sizes",
"name": "Manageable file sizes",
"detail": "0/96 source files over 60KB",
"points": 55,
"status": "met",
"details": [
{
"code": "oversized_source_files",
"params": {
"kb": 60,
"sampled": 96,
"oversized": 0
}
}
],
"max_points": 55
}
]
}
],
"description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
}
],
"metrics_version": "1.13.0"
},
"warnings": [
"Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token"
],
"report_type": "repository",
"generated_at": "2026-07-27T15:10:28.157023Z",
"schema_version": "0.27.0",
"badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/r/ruffel/invoke.svg",
"full_name": "ruffel/invoke",
"license_state": "standard",
"license_spdx": "MIT"
}