@uchibeke/gallivant 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/install.md CHANGED
@@ -1,14 +1,34 @@
1
1
  # Install
2
2
 
3
- Get one repository task from a brief to a reviewed pull request. Install the CLI,
4
- sign in to GitHub and one harness, finish the checks, then start a small task.
5
- Gallivant keeps the implementation and review loop moving; you review and merge.
3
+ Get one repository task from a brief to a reviewed pull request. One command sets
4
+ up a repository; `doctor` tells you what still needs your decision or login; then
5
+ you start a small task. Gallivant keeps the implementation and review loop moving
6
+ and you review and merge.
6
7
 
7
8
  **Using a coding agent to set this up?** Give it the [agent setup guide](agent-setup.md).
8
- It includes a copy/paste prompt, setup checks and the point where a real run starts.
9
+ It includes a copy/paste prompt, the setup checks and the point where a real run starts.
9
10
 
10
- For DNS errors or stalled provider calls, see [network diagnosis and recovery](network-recovery.md).
11
- The diagnostic command works before configuration or login, on Linux and macOS.
11
+ ## 1. Prerequisites
12
+
13
+ Requirements shared by the coordinator: macOS or Linux, Node 22 or newer, Git,
14
+ `gh` signed in, and `jq`. For the recommended local path, use signed-in Codex CLI
15
+ 0.160.0+ or Claude Code 2.1.263+ (Codex implementation accepts 0.158.0+, but
16
+ plain-language preparation needs 0.160.0+). Hosted OpenAI Agents execution has a
17
+ separate setup below and needs no local Codex or Claude binary. See the
18
+ [capability matrix](../README.md#harness-support) before choosing another harness.
19
+
20
+ | Prerequisite | Check | If it fails |
21
+ | --- | --- | --- |
22
+ | Node 22+ | `node --version` | install Node from your package manager or a version manager |
23
+ | Git and jq | `git --version`, `jq --version` | `brew install git jq`, `apt install git jq` or `dnf install git jq` |
24
+ | GitHub CLI signed in | `gh auth status` | `gh auth login` |
25
+ | Codex CLI signed in | `codex login status` | `codex login` |
26
+ | Claude Code signed in | `claude auth status` | `claude auth login` |
27
+ | Git identity | `git config --get user.name`, `git config --get user.email` | `git config --global user.name "..."` and `user.email` |
28
+
29
+ Each login belongs to that tool's own flow. Nothing in Gallivant asks for a
30
+ password, token or browser cookie, and `init` stops before writing anything when
31
+ `gh` or the selected harness is signed out.
12
32
 
13
33
  Linux installations need process inspection through `ps` with `lstart`, `command`,
14
34
  PID and process-group fields (normally the `procps`/`procps-ng` package). Minimal
@@ -17,72 +37,103 @@ blocks safe restart rather than allowing overlapping agents. The CI baseline is
17
37
  Ubuntu with Node 22/24; other distributions must also satisfy their harness and
18
38
  sandbox prerequisites.
19
39
 
20
- Start with one harness and one repository. `gallivant init` creates your local
21
- config; `gallivant doctor` checks the prerequisites and prints a fix for each
22
- missing item. Setup time depends on whether your harness and GitHub are already
23
- configured. No model call is needed to install the CLI or validate a hand-written PRD.
40
+ For DNS errors or stalled provider calls, see [network diagnosis and recovery](network-recovery.md).
41
+ The diagnostic command works before configuration or login, on Linux and macOS.
24
42
 
25
- Requirements shared by the coordinator: macOS or Linux, Node 22 or newer, Git,
26
- `gh` signed in, and `jq`. For the recommended local path, use signed-in Claude
27
- Code 2.1.263+ or Codex CLI 0.160.0+ (Codex implementation accepts 0.158.0+, but
28
- plain-language preparation needs 0.160.0+). Hosted OpenAI Agents execution has a
29
- separate setup below and needs no local Codex or Claude binary. See the
30
- [capability matrix](../README.md#harness-support) before choosing another harness.
43
+ ## 2. The command
31
44
 
32
- ## 1. The command
45
+ Inside the Git checkout you want Gallivant to work in:
33
46
 
34
- Check the [registry version](https://www.npmjs.com/package/@uchibeke/gallivant). If one
35
- is available, install the CLI with `npm install -g @uchibeke/gallivant` and check
36
- `gallivant --version`. If the package has not been published yet, use the source
37
- checkout:
47
+ ```bash
48
+ cd /path/to/your/repository
49
+ npx --yes @uchibeke/gallivant init --harness codex # or: --harness claude
50
+ ```
51
+
52
+ This setup flow requires **0.2.1 or newer**. On 2026-10-20, npm's `latest` was
53
+ still 0.2.0: its `init` creates the config only, so repository registration and
54
+ `doctor` must be done separately. A maintainer must [publish 0.2.1](releasing.md)
55
+ before the command above provides the flow below; setup does not publish it.
56
+
57
+ `npx` downloads the published `@uchibeke/gallivant` package into npm's cache and
58
+ runs its `gallivant` command. Nothing is installed globally, no `gallivant`
59
+ command is added to your PATH, and this repository is not cloned. The command
60
+ ends by saying so and printing both the next `npx` command and the optional
61
+ persistent install:
38
62
 
39
63
  ```bash
40
- git clone https://github.com/uchibeke/gallivant && cd gallivant
41
- npm ci --ignore-scripts
42
- npm link
64
+ npm install -g @uchibeke/gallivant # optional; then use plain `gallivant`
43
65
  gallivant --version
44
- gallivant --help # from any directory, to confirm it is on your PATH
45
66
  ```
46
67
 
47
- The package has locked runtime dependencies; `npm ci` installs them for a source
48
- checkout. `npm i -g .` copies instead of linking.
49
- If the shell cannot find the command afterwards,
50
- the `bin` directory under `npm prefix -g` is not on your PATH.
68
+ If the shell cannot find `gallivant` after a global install, the `bin` directory
69
+ under `npm prefix -g` is not on your PATH. Avoid `sudo npm install`; use a
70
+ user-owned Node installation or prefix. The rest of this guide writes
71
+ `gallivant`; with `npx`, read that as `npx --yes @uchibeke/gallivant`.
72
+
73
+ ### What `init` does
74
+
75
+ 1. Refuses to continue, writing nothing, when the directory is not a Git
76
+ checkout, its `origin` remote is not a github.com repository, `gh` is signed
77
+ out, the selected harness is missing, too old or signed out, or the resolved
78
+ run credential cannot see or push to the repository. Each refusal names the
79
+ command that fixes it. Classic credentials also need `public_repo` or `repo`
80
+ scope; a failed scope lookup stops setup before writing and asks you to retry.
81
+ 2. Creates `~/.gallivant/config.json` (mode 600) with your authenticated GitHub
82
+ login in `trusted_authors`, the selected installed harness, notifications
83
+ disabled and a separate `~/.gallivant-worktrees` directory for task checkouts.
84
+ Use `--worktrees /absolute/path` to put checkouts elsewhere, outside the
85
+ config home.
86
+ 3. Records the repository in `repos` once, by its lowercased `owner/name`.
87
+ 4. Prints the `doctor` report and exits with its status: `0` when every check
88
+ passes, `1` while something still needs you.
89
+
90
+ Running it again is safe. An existing config keeps every key as written,
91
+ including custom harness binaries, `github_token_file`, `repos` entries with
92
+ their base branches, `notify` and `worktrees`; the only change is the one `repos`
93
+ entry for the current repository, and a repository already listed is not
94
+ duplicated. `--worktrees` is refused when it differs from the configured
95
+ directory, so edit the config to move checkouts. `--harness` adds the named
96
+ harness when it is not configured yet and leaves the others alone.
97
+ If both Codex and Claude are configured, pass `--harness codex` or
98
+ `--harness claude` explicitly. Setup rechecks settings changed by another command
99
+ before writing, so concurrent repository registrations preserve each other's entries.
100
+
101
+ ### What `init` leaves to you
102
+
103
+ `doctor` reports these with the exact fix. `init` does not perform them:
104
+
105
+ - **Reviewer.** The default reviewer is the Codex GitHub integration. Enable it
106
+ on the repository, configure another supported bot, or set `"reviewer": null`
107
+ to rely on CI and trusted human feedback. Without one of these, the default
108
+ loop waits for a review that cannot arrive. See [the reviewer key](config.md#the-reviewer-object).
109
+ - **Branch protection.** Section 5 below. `doctor` prints the `gh api` call; you
110
+ run it.
111
+ - **Harness guardrails.** Sections 7 and 8 below. APort's installer writes the
112
+ hook, and Codex asks you to trust it interactively.
113
+ - **Credentials and notifications.** Sections 4 and 6 below are optional and
114
+ need your own token or ntfy topic.
115
+ - **Starting work.** Setup never prepares or starts a task, so it spends nothing
116
+ on inference. Section 9 covers the first task and what `--run` authorizes.
51
117
 
52
118
  Global configuration lives in `~/.gallivant/config.json` (or
53
- `$GALLIVANT_HOME/config.json` when explicitly set). Task commands still
54
- need a target checkout: run inside it, pass `--dir` for natural-language tasks, or
55
- pass the PRD path for a written brief.
119
+ `$GALLIVANT_HOME/config.json` when explicitly set). See [config.md](config.md)
120
+ for every key. Task commands still need a target checkout: run inside it, pass
121
+ `--dir` for natural-language tasks, or pass the PRD path for a written brief.
122
+
123
+ ### Install from source
56
124
 
57
- ## 2. Sign in and create config
125
+ Contributors who want to run the checkout they are editing:
58
126
 
59
127
  ```bash
60
- gh auth login # skip if gh auth status already passes
61
- codex login # or sign in with claude, if that is your selected harness
62
- git config --get user.name
63
- git config --get user.email
64
- gallivant init --harness codex # or --harness claude
65
- gallivant doctor
128
+ git clone https://github.com/uchibeke/gallivant && cd gallivant
129
+ npm ci --ignore-scripts
130
+ npm link
131
+ gallivant --version
66
132
  ```
67
133
 
68
- `init` writes `~/.gallivant/config.json` with mode 600, trusts your authenticated
69
- GitHub login, records only the selected installed harness, and creates
70
- `~/.gallivant-worktrees` for separate task checkouts. Use `--worktrees /absolute/path`
71
- to choose another location outside the config home. Notifications start disabled.
72
- Existing configs are never overwritten. If `init` reports an existing config,
73
- continue with `doctor`; preserve the config and repair only the relevant keys.
74
- Set your Git name/email if the checks
75
- above are empty; commits use your configured identity.
76
-
77
- `doctor` can also print a suggested config when none exists. It reports missing
78
- guardrails, credentials and repository protections; `init` does not configure
79
- those for you. Add the target repository to `repos` so `doctor` checks its
80
- permissions, base branch and protections. See [config.md](config.md) for every key.
81
- The default reviewer is
82
- the Codex GitHub integration: enable it on your target repository, configure
83
- another supported bot, or explicitly set `"reviewer": null` to use CI and trusted
84
- human feedback. Without a configured review bot, the default loop waits for a
85
- review that cannot arrive.
134
+ The package has locked runtime dependencies; `npm ci` installs them for a source
135
+ checkout. `npm i -g .` copies instead of linking. This is not the path for using
136
+ Gallivant; the published package is.
86
137
 
87
138
  ### Hosted Agents API instead of a local CLI
88
139
 
@@ -134,7 +185,16 @@ currently disabled because repository ownership of the selected environment
134
185
  cannot be verified. See [the Cloud gate](codex-cloud.md); local Codex and Claude
135
186
  remain available.
136
187
 
137
- ## 3. GitHub auth
188
+ ## 3. Read the report
189
+
190
+ `init` ends with the `doctor` report; `gallivant doctor` prints it again at any
191
+ time. Every failing line carries the command or file that fixes it. Work through
192
+ them in order, then rerun `doctor`. `doctor` without flags checks configuration,
193
+ credentials and repository access without launching a model run; `doctor --probe`
194
+ runs a real Claude sandbox test and `doctor --send` sends a real notification,
195
+ so use those only when you mean to.
196
+
197
+ ## 4. GitHub auth
138
198
 
139
199
  By default Gallivant uses the login you already have, however you set it up. Nothing to configure beyond `gh auth status` passing. There is no file to copy for this: every git setting a run needs, the credential helper included, is passed in the run's environment.
140
200
 
@@ -178,11 +238,11 @@ recognizes enterprise hosts on an existing checkout, but new-task repository
178
238
  resolution and clone URLs currently use github.com. An enterprise login or token
179
239
  alone does not establish supported end-to-end operation.
180
240
 
181
- ## 4. A ruleset on each repo
241
+ ## 5. A ruleset on each repo
182
242
 
183
243
  The one control that stops a push to the default branch is GitHub's. `doctor` prints the exact `gh api` call for every repo in `repos`; it creates a ruleset that blocks deletion and force pushes on the default branch and requires a pull request, with no bypass actors. Gallivant never merges, so the ruleset costs nothing in the loop.
184
244
 
185
- ## 5. Notifications
245
+ ## 6. Notifications
186
246
 
187
247
  Optional: create a topic on [ntfy.sh](https://ntfy.sh) or your own server, install the ntfy app, and set
188
248
  `"notify": { "ntfy": "https://ntfy.sh/<unguessable-topic>", "email": null }`.
@@ -195,7 +255,7 @@ gallivant doctor --send # sends one test message
195
255
 
196
256
  Three kinds of message exist: `ready for a human`, `needs you: <reason>`, and a throttled `gallivant: <error>` when the daemon itself cannot work (gh not authenticated, a harness signed out). Nothing on merge. Nothing per round.
197
257
 
198
- ## 6. APort on Claude Code
258
+ ## 7. APort on Claude Code
199
259
 
200
260
  APort's installer writes a PreToolUse hook with matcher `*` into whichever settings file it is pointed at, and the hook enforces one passport. Gallivant's passport is strict on purpose: writes only under the worktrees directory, control-plane directories blocked. Installed into `~/.claude/settings.json`, that policy would apply to every interactive Claude session you run and block your normal work. So the installer goes into a Gallivant-owned directory, and every run loads it with `--settings` plus `APORT_CLAUDE_CODE_CONFIG_DIR`. Nothing about the sandbox requires this; it is only about keeping Gallivant's policy separate from yours.
201
261
 
@@ -224,7 +284,7 @@ gallivant doctor --probe # a real run: a write inside the checkout succeeds,
224
284
 
225
285
  The probe reads the APort audit log to tell apart a hook that never ran from a passport that allowed the write, and prints the fix for each.
226
286
 
227
- ## 7. APort on Codex
287
+ ## 8. APort on Codex
228
288
 
229
289
  ```bash
230
290
  npx @aporthq/aport-agent-guardrails codex --global --enforcement=warn
@@ -239,7 +299,7 @@ Git metadata writes and escalations, the scoped token and the ruleset. Gallivant
239
299
  sets the approval mode on start and resume. A denial must be diagnosed, not
240
300
  bypassed. See [guardrails.md](guardrails.md#codex-git-writes-need-automatic-approval).
241
301
 
242
- ## 8. First task
302
+ ## 9. First task
243
303
 
244
304
  Before dispatch, confirm the repository, target branch, permitted changes and
245
305
  spending limits. The default `max_iterations` is 30 automatic fix rounds;
package/docs/launch.md CHANGED
@@ -1,10 +1,13 @@
1
1
  # Announcement drafts
2
2
 
3
3
  Publish these only after [the release checks](releasing.md#verify-before-announcing)
4
- pass and the repository is public. Use the install command that actually works
5
- for the released version; do not advertise npm installation before publication.
6
- Add a genuine dashboard screenshot or recording of a task reaching a reviewed
7
- PR. Fixture tests are not live provider evidence.
4
+ pass for the version being announced. The package is published as
5
+ `@uchibeke/gallivant`; the setup command to advertise is
6
+ `npx --yes @uchibeke/gallivant init --harness codex` (or `--harness claude`),
7
+ run inside the target checkout. Check that the behavior the README describes is
8
+ in the released version before quoting it. Add a genuine dashboard screenshot or
9
+ recording of a task reaching a reviewed PR. Fixture tests are not live provider
10
+ evidence.
8
11
 
9
12
  ## X
10
13
 
@@ -414,7 +414,7 @@ publication and review correction. Use the exact prerequisites above to record
414
414
  those phases; fixture passes alone are not end-to-end qualification.
415
415
 
416
416
  The original M5/M6 integration handoff points to
417
- [`aporthq/agent-passport#417`](https://github.com/aporthq/agent-passport/pull/417),
417
+ [`aporthq/agent-passport#417`](https://github.com/aporthq/agent-passport/pull/417) (not publicly readable),
418
418
  merged on 2026-10-09. Its canonical brief is
419
419
  `docs/prds/aport-task-delivery-workflow-integration.md` in that repository, with
420
420
  job identity `aporthq/agent-passport/aport-task-delivery-workflow-integration`.
@@ -102,7 +102,7 @@ Official configuration references:
102
102
  Re-run native qualification when upgrading the CLI or adding tool capabilities.
103
103
 
104
104
  The recorded cross-repository integration handoff names
105
- [`aporthq/agent-passport#417`](https://github.com/aporthq/agent-passport/pull/417),
105
+ [`aporthq/agent-passport#417`](https://github.com/aporthq/agent-passport/pull/417) (not publicly readable),
106
106
  with job identity `aporthq/agent-passport/aport-task-delivery-workflow-integration`.
107
107
  The handoff calls for reusing that continuation after the actual portable package
108
108
  is available. This document qualifies the preparation boundary; it does not
package/docs/releasing.md CHANGED
@@ -1,9 +1,16 @@
1
1
  # Releasing
2
2
 
3
3
  The package name is `@uchibeke/gallivant`; the executable is `gallivant`. The repository is
4
- `uchibeke/gallivant`. Before announcing a version, confirm npm publication and a
5
- passing consumer smoke check. A public GitHub repository alone does not make the
6
- npm command work.
4
+ `uchibeke/gallivant`. The first public release, 0.2.0, was published on
5
+ 2026-10-19; `npm view @uchibeke/gallivant version` reports the current one.
6
+ Before announcing a version, confirm its npm publication and a passing consumer
7
+ smoke check. A public GitHub repository alone does not make the npm command work.
8
+
9
+ The README and installation guide lead with
10
+ `npx --yes @uchibeke/gallivant init --harness codex`, which runs whatever
11
+ version npm serves as `latest`. When `init` or another documented command changes
12
+ behavior, release the version that carries it before, or together with, the
13
+ documentation that describes it.
7
14
 
8
15
  ## One-time launch setup
9
16
 
@@ -21,14 +28,13 @@ npm command work.
21
28
  Enable the `main` ruleset with pull requests, the four portability checks,
22
29
  and protection against deletion and force pushes. Read back both settings:
23
30
  merely referencing `environment: npm` in YAML does not create protection.
24
- 3. For a package that does not exist yet, arrange the initial npm publication
25
- credential with the npm owner. If needed, set a short-lived granular
26
- `NPM_TOKEN` in the protected `npm` environment, scoped as narrowly as npm
27
- permits. Never put it in the repository or a PR.
28
- A local `npm login` is not authentication for GitHub Actions. Do not publish
29
- the release locally to bypass this setup: the release's provenance is
30
- generated in supported CI, not by the developer's terminal.
31
- 4. Once the package exists, configure its npm trusted publisher for GitHub owner
31
+ 3. The initial publication used a short-lived granular `NPM_TOKEN` in the
32
+ protected `npm` environment, scoped as narrowly as npm permits. Never put a
33
+ token in the repository or a PR. A local `npm login` is not authentication
34
+ for GitHub Actions. Do not publish a release locally to bypass this setup:
35
+ the release's provenance is generated in supported CI, not by the
36
+ developer's terminal.
37
+ 4. With the package published, configure its npm trusted publisher for GitHub owner
32
38
  `uchibeke`, repository `gallivant`, workflow `release.yml`, environment `npm`.
33
39
  Permit direct `npm publish`, which is what this workflow runs. Validate a new
34
40
  trusted publisher within two days or recreate it after expiry; configure it
@@ -56,8 +62,8 @@ npm whoami --registry=https://registry.npmjs.org
56
62
  npm view @uchibeke/gallivant version --registry=https://registry.npmjs.org
57
63
  ```
58
64
 
59
- An absent package is expected before the first publication. An absent `npm`
60
- environment or disabled branch protection is unfinished release setup.
65
+ `npm view` should report the published version. An absent `npm` environment or
66
+ disabled branch protection is unfinished release setup.
61
67
 
62
68
  ### Staged publishing is a different workflow
63
69
 
@@ -87,8 +93,8 @@ Choose one trigger:
87
93
 
88
94
  ```sh
89
95
  # From an up-to-date main checkout; replace the example with package.json's version.
90
- git tag -a v0.2.0 -m 'Release 0.2.0'
91
- git push origin v0.2.0
96
+ git tag -a v0.2.1 -m 'Release 0.2.1'
97
+ git push origin v0.2.1
92
98
 
93
99
  # Or dispatch against main; the workflow uses main's package version.
94
100
  gh workflow run release.yml --repo uchibeke/gallivant --ref main
@@ -113,7 +119,8 @@ original workflow rather than dispatching newer `main` with the old version.
113
119
 
114
120
  ```sh
115
121
  npm view @uchibeke/gallivant version dist.integrity dist.attestations
116
- npm install -g @uchibeke/gallivant
122
+ npx --yes @uchibeke/gallivant --version # the command the README leads with
123
+ npm install -g @uchibeke/gallivant # the documented persistent option
117
124
  gallivant --version
118
125
  gallivant --help
119
126
  ```
@@ -124,9 +131,9 @@ in a clean project that installed the released version to verify registry
124
131
  signatures and available provenance. Metadata presence alone is not signature
125
132
  verification; see [npm provenance verification](https://docs.npmjs.com/generating-provenance-statements/#verifying-provenance-attestations).
126
133
 
127
- After publication is verified, update the README and installation guide to put
128
- the tested npm install command first and remove the first-release-pending notes
129
- (including this page). Keep the source-install alternative.
134
+ After publication is verified, check that the README's `npx` command and the
135
+ installation guide match the behavior of the released version, and keep the
136
+ source-install alternative for contributors.
130
137
 
131
138
  Use a clean test account/home and the [getting-started guide](install.md) for one
132
139
  small real PR. Confirm implementation, feedback-driven continuation, CI and the
@@ -135,6 +142,8 @@ paid-provider access. Recheck anonymous GitHub access and all README links.
135
142
  Announcement drafts are in [launch.md](launch.md).
136
143
 
137
144
  For local validation without publishing: `npm run lint`, `npm test`, and
138
- `npm run test:install`. The last command packs and installs under a disposable
139
- directory, exercises setup and PRD commands outside the source tree, and imports
140
- every declared module export. It makes registry requests but no model calls.
145
+ `npm run test:install`. The last command packs the tarball under a disposable
146
+ directory, runs the README's `npx` setup command against it from a fixture
147
+ repository with a throwaway home, installs it as a dependency, exercises PRD
148
+ commands outside the source tree, and imports every declared module export. It
149
+ makes registry requests but no model calls.
@@ -12,7 +12,7 @@ must enforce the same authorization, ownership, persistence and recovery
12
12
  contracts. In the APort integration, APort owns publication and the host
13
13
  responsibilities above. Its existing
14
14
  `aporthq/agent-passport/aport-task-delivery-mcp` job produced merged
15
- [PR #414](https://github.com/aporthq/agent-passport/pull/414); reuse that integration.
15
+ [PR #414](https://github.com/aporthq/agent-passport/pull/414) (not publicly readable); reuse that integration.
16
16
  The M5/M6 notes below preserve that integration's milestone evidence and gates;
17
17
  they do not certify another host or supply a public UI, database or competing
18
18
  runner.
@@ -39,6 +39,18 @@ export const CONTROL_PLANE = ["~/.claude", "~/.codex", "~/.config/opencode", "~/
39
39
  // are checked separately. Public-only OAuth must not require private repo access.
40
40
  export const hasRepositoryWriteScope = scopes => scopes === null || String(scopes).split(/[\s,]+/).some(scope => scope === "repo" || scope === "public_repo");
41
41
 
42
+ /** A missing OAuth header is valid for fine-grained tokens; a failed lookup is not evidence of that absence. */
43
+ export async function repositoryWriteScopeCheck(gh, credential) {
44
+ let scopes;
45
+ try { scopes = await gh.tokenScopes(); }
46
+ catch (error) {
47
+ const detail = scrubbed(String(error?.message || error), credential?.token).split("\n")[0];
48
+ return fail(`could not verify ${describeCredential(credential)} repository write scope: ${detail}`, "retry after restoring GitHub API access; run gallivant doctor before starting a task");
49
+ }
50
+ if (!hasRepositoryWriteScope(scopes)) return fail(`${describeCredential(credential)} has no repository write scope (${scopes})`, "for public repositories use public_repo, or put a selected-repository fine-grained PAT with Contents: write in github_token_file");
51
+ return ok(scopes === null ? "fine-grained" : `classic (${scopes})`);
52
+ }
53
+
42
54
  /**
43
55
  * What a local passport is missing, as a list of sentences, empty when it is sound. Exported because it is the only
44
56
  * thing in `doctor` that speaks for the guardrail the docs promise, so it is worth testing on its own.
@@ -160,12 +172,9 @@ export async function doctor({ home = gallivantHome(), send = false, probe = fal
160
172
  if (!r.ok) return r.fix
161
173
  ? fail(`could not verify ${describeCredential(runCredential)} with GitHub: ${r.detail}`, r.fix)
162
174
  : fail(`${describeCredential(runCredential)} is rejected by GitHub: ${r.detail}`, runCredential.source === SOURCES.file ? `the token in ${contract(p)} is expired or revoked; paste a new one` : "gh auth login, or export a valid GH_TOKEN");
163
- const scopes = await gh.tokenScopes().catch(() => null);
164
- const kind = scopes === null ? "fine-grained" : `classic (${scopes})`;
165
- // `repo` or public-only `public_repo` permits writes. A fine-grained PAT carries no scopes at all,
166
- // so the absence of the header is not a finding; per-repo permissions are what the next check asks GitHub about.
167
- if (!hasRepositoryWriteScope(scopes)) return fail(`${describeCredential(runCredential)} has no repository write scope (${scopes})`, "for public repositories use public_repo, or put a selected-repository fine-grained PAT with Contents: write in github_token_file");
168
- return ok(`${describeCredential(runCredential)}, ${kind}, acts as ${r.detail}`);
175
+ const scope = await repositoryWriteScopeCheck(gh, runCredential);
176
+ if (!scope.ok) return scope;
177
+ return ok(`${describeCredential(runCredential)}, ${scope.detail}, acts as ${r.detail}`);
169
178
  });
170
179
  // Push rights per repo, asked of GitHub. Scopes say what a token is allowed to attempt; `permissions.push` says
171
180
  // whether this identity may write to this repository, which is the thing a run actually needs and the thing an