@uchibeke/gallivant 0.0.0-stage → 0.2.0
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/CONTRIBUTING.md +26 -0
- package/LICENSE +204 -0
- package/README.md +201 -2
- package/SECURITY.md +19 -0
- package/bin/gallivant.mjs +257 -0
- package/bin/package-plugin.mjs +16 -0
- package/bin/release-live.mjs +29 -0
- package/bin/release-package.mjs +23 -0
- package/bin/session-runtime.mjs +13 -0
- package/bin/task-delivery-state.mjs +12 -0
- package/docs/README.md +70 -0
- package/docs/agent-setup.md +214 -0
- package/docs/app.md +111 -0
- package/docs/architecture.md +265 -0
- package/docs/chains.md +102 -0
- package/docs/codex-cloud.md +284 -0
- package/docs/commands.md +620 -0
- package/docs/config.md +430 -0
- package/docs/execution.md +796 -0
- package/docs/guardrails.md +311 -0
- package/docs/install.md +338 -0
- package/docs/launch.md +42 -0
- package/docs/learning.md +369 -0
- package/docs/network-recovery.md +101 -0
- package/docs/openai-agents.md +457 -0
- package/docs/prd.md +246 -0
- package/docs/preparation.md +110 -0
- package/docs/release-qualification.md +196 -0
- package/docs/releasing.md +140 -0
- package/docs/runtime-environment.md +113 -0
- package/docs/task-delivery.md +476 -0
- package/docs/tasks.md +739 -0
- package/lib/aport.mjs +95 -0
- package/lib/attribution.mjs +95 -0
- package/lib/block.mjs +89 -0
- package/lib/capabilities.mjs +3 -0
- package/lib/chains.mjs +222 -0
- package/lib/codex_cloud.mjs +160 -0
- package/lib/codex_cloud_config.mjs +20 -0
- package/lib/codex_cloud_input.mjs +49 -0
- package/lib/codex_cloud_result.mjs +74 -0
- package/lib/commands/doctor.mjs +457 -0
- package/lib/commands/init.mjs +34 -0
- package/lib/commands/learn.mjs +54 -0
- package/lib/commands/new.mjs +47 -0
- package/lib/commands/ready.mjs +45 -0
- package/lib/commands/register.mjs +195 -0
- package/lib/commands/resume.mjs +224 -0
- package/lib/commands/serve.mjs +349 -0
- package/lib/commands/start.mjs +247 -0
- package/lib/commands/start_services.mjs +22 -0
- package/lib/commands/status.mjs +150 -0
- package/lib/commands/task.mjs +73 -0
- package/lib/commands/up.mjs +228 -0
- package/lib/commands/why.mjs +143 -0
- package/lib/config.mjs +461 -0
- package/lib/context.mjs +100 -0
- package/lib/control.mjs +202 -0
- package/lib/control_contract.mjs +16 -0
- package/lib/coordinator.mjs +19 -0
- package/lib/credential.mjs +161 -0
- package/lib/daemon.mjs +156 -0
- package/lib/deliverable.mjs +357 -0
- package/lib/events.mjs +261 -0
- package/lib/executable.mjs +31 -0
- package/lib/execution.mjs +631 -0
- package/lib/execution_contract.mjs +15 -0
- package/lib/execution_input.mjs +4 -0
- package/lib/execution_io.mjs +94 -0
- package/lib/execution_journal.mjs +69 -0
- package/lib/execution_preparation.mjs +29 -0
- package/lib/execution_result.mjs +266 -0
- package/lib/fs.mjs +133 -0
- package/lib/gh.mjs +289 -0
- package/lib/git.mjs +434 -0
- package/lib/harness.mjs +521 -0
- package/lib/harness_log.mjs +432 -0
- package/lib/hash.mjs +16 -0
- package/lib/identity.mjs +98 -0
- package/lib/learning.mjs +654 -0
- package/lib/learning_contract.mjs +2 -0
- package/lib/learning_data.mjs +135 -0
- package/lib/learning_input.mjs +111 -0
- package/lib/learning_qualification.mjs +67 -0
- package/lib/learning_signer.mjs +151 -0
- package/lib/learning_store.mjs +88 -0
- package/lib/log.mjs +53 -0
- package/lib/network_diagnostics.mjs +64 -0
- package/lib/network_recovery.mjs +42 -0
- package/lib/notify.mjs +58 -0
- package/lib/openai_agents.mjs +373 -0
- package/lib/openai_agents_config.mjs +14 -0
- package/lib/openai_agents_http.mjs +87 -0
- package/lib/openai_agents_input.mjs +15 -0
- package/lib/openai_agents_sources.mjs +112 -0
- package/lib/owner_delivery.mjs +443 -0
- package/lib/plugin_package.mjs +340 -0
- package/lib/prd.mjs +635 -0
- package/lib/preparation_agents.mjs +33 -0
- package/lib/preparation_codex.mjs +212 -0
- package/lib/probe.mjs +126 -0
- package/lib/prompt.mjs +238 -0
- package/lib/provider_health.mjs +55 -0
- package/lib/public-format.mjs +31 -0
- package/lib/public_output.mjs +49 -0
- package/lib/release_package.mjs +154 -0
- package/lib/repository_files.mjs +86 -0
- package/lib/run.mjs +385 -0
- package/lib/run_log.mjs +94 -0
- package/lib/runtime-environment.mjs +87 -0
- package/lib/runtime_dependencies.mjs +15 -0
- package/lib/runtime_node.mjs +107 -0
- package/lib/runtime_state.mjs +174 -0
- package/lib/sources/index.mjs +27 -0
- package/lib/sources/prd.mjs +50 -0
- package/lib/start_contract.mjs +37 -0
- package/lib/state.mjs +246 -0
- package/lib/task_allowance.mjs +31 -0
- package/lib/task_completion.mjs +61 -0
- package/lib/task_contract.mjs +450 -0
- package/lib/task_delivery.mjs +219 -0
- package/lib/task_delivery_cancel.mjs +34 -0
- package/lib/task_delivery_contract.mjs +61 -0
- package/lib/task_delivery_mcp.mjs +40 -0
- package/lib/task_delivery_store.mjs +43 -0
- package/lib/task_delivery_view.mjs +88 -0
- package/lib/task_environment.mjs +281 -0
- package/lib/task_prepare.mjs +224 -0
- package/lib/task_store.mjs +38 -0
- package/lib/tasks.mjs +513 -0
- package/lib/tick.mjs +1635 -0
- package/lib/time.mjs +36 -0
- package/lib/tracker/assets.mjs +43 -0
- package/lib/tracker/pages.mjs +320 -0
- package/lib/tracker/runs.mjs +102 -0
- package/lib/tracker/stream.mjs +74 -0
- package/lib/tracker_process.mjs +107 -0
- package/lib/verification_sandbox.mjs +181 -0
- package/node_modules/smol-toml/LICENSE +24 -0
- package/node_modules/smol-toml/README.md +605 -0
- package/node_modules/smol-toml/dist/date.d.ts +41 -0
- package/node_modules/smol-toml/dist/date.js +156 -0
- package/node_modules/smol-toml/dist/error.d.ts +37 -0
- package/node_modules/smol-toml/dist/error.js +67 -0
- package/node_modules/smol-toml/dist/extract.js +289 -0
- package/node_modules/smol-toml/dist/index.cjs +770 -0
- package/node_modules/smol-toml/dist/index.d.ts +44 -0
- package/node_modules/smol-toml/dist/index.js +34 -0
- package/node_modules/smol-toml/dist/parse.d.ts +73 -0
- package/node_modules/smol-toml/dist/parse.js +164 -0
- package/node_modules/smol-toml/dist/primitive.js +151 -0
- package/node_modules/smol-toml/dist/stringify.d.ts +32 -0
- package/node_modules/smol-toml/dist/stringify.js +219 -0
- package/node_modules/smol-toml/dist/struct.js +156 -0
- package/node_modules/smol-toml/dist/util.d.ts +43 -0
- package/node_modules/smol-toml/dist/util.js +57 -0
- package/node_modules/smol-toml/package.json +68 -0
- package/npm-shrinkwrap.json +1823 -0
- package/package.json +105 -4
- package/templates/claude-settings.json +18 -0
- package/templates/passport.gallivant.json +83 -0
- package/templates/plugin/bundled-runtime.json +26 -0
- package/templates/plugin/schemas/mcp.schema.json +120 -0
- package/templates/plugin/schemas/plugin.schema.json +65 -0
- package/templates/plugin/task-delivery.SKILL.md +60 -0
- package/templates/prd.md +32 -0
- package/templates/tracker/tracker.css +301 -0
- package/templates/tracker/tracker.js +721 -0
package/docs/config.md
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# Config
|
|
2
|
+
|
|
3
|
+
Learning migration defaults to off. Per-repository `off`, `collect` and `apply` modes
|
|
4
|
+
live in protected `$GALLIVANT_HOME/learning` state, changed only through
|
|
5
|
+
`gallivant learn configure --repo owner/repo --mode MODE`; task fields, branch files
|
|
6
|
+
and receipts cannot enable them. There is no `learning.enabled` or qualification
|
|
7
|
+
boolean in config.json. Current installed profiles are UNQUALIFIED: collect works,
|
|
8
|
+
while apply and key setup refuse before activation. Key paths are separate explicit
|
|
9
|
+
operator paths, and OpenSSH is pinned outside worker writes. See [learning](learning.md)
|
|
10
|
+
for the CLI, bounds, separate extraction allowance and backup/rotation limitations.
|
|
11
|
+
|
|
12
|
+
`~/.gallivant/config.json`, mode 600. `gallivant init` creates a local Codex or
|
|
13
|
+
Claude configuration; `gallivant doctor` prints a suggested configuration when
|
|
14
|
+
none exists. Set `GALLIVANT_HOME` to use another directory. Hosted providers need
|
|
15
|
+
explicit configuration; they are not discovered from local executable paths.
|
|
16
|
+
See [installation](install.md) for the first-run sequence.
|
|
17
|
+
|
|
18
|
+
| Key | Default | Meaning |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `poll_seconds` | 120 | GitHub review and chain-check cadence. Minimum 30. Provider progress and readiness use the separate `execution.progressMs` cadence; the daemon may tick more frequently. |
|
|
21
|
+
| `max_iterations` | 30 | Automatic fix-round allowance per PR. A pushed fix or changed completion receipt consumes a round. At the cap, further automatic work stops with `needs-human`. Explicit operator resumes can exceed the cap; their progress still counts. |
|
|
22
|
+
| `checks_grace_minutes` | 5 | A head younger than this with no checks reported is not green yet; GitHub shows an empty check list for the first seconds after a push. A head older than this with no checks at all counts as green. |
|
|
23
|
+
| `run_timeout_minutes` | 720 | Local runs exceeding this time are killed; remote execution uses provider cancellation/reconciliation where supported and preserves unknown ownership. It is a runtime allowance, not a provider billing cap. Overridable per repository; see below and [execution](execution.md). |
|
|
24
|
+
| `max_budget_usd` | 5 | Passed to Claude as `--max-budget-usd` and used in task preparation/authorization accounting. Other local harnesses have no equivalent enforced dollar cap. Hosted dispatch also requires its explicit provider allowance; reservations are not a billing hard cap. |
|
|
25
|
+
| `rereview_minutes` | 6 | If the reviewer has not reviewed this long after a request, the request is posted once more. |
|
|
26
|
+
| `stall_hours` | 6 | If the reviewer still has not reviewed, the PR stops with `reviewer-silent`. |
|
|
27
|
+
| `trusted_authors` | required | GitHub logins whose reviews and comments may start a run. Everyone else is ignored. Your own login belongs here. |
|
|
28
|
+
| `reviewer` | Codex | `{ login, rereview_comment, blocking_pattern }` or `null`. See below. |
|
|
29
|
+
| `deliverable` | policy defaults | Limits for the completion gate, in the shape of APort's `deliverable.task.complete` passport limits: `evaluator` (`aport` confirms with APort's hosted evaluator using a per-task passport, `local` uses only the built-in rules), `api_url`, `on_unreachable` (`wait`, the default: no decision until APort answers, the daemon retries next tick and stops as `needs-human` after five consecutive failures; `local`: the built-in verdict stands and the log says so), `require_summary`, `min_summary_words`, `require_tests_passing`, `scan_output`, `blocked_patterns`. `null` turns the gate off. See [prd.md](prd.md). |
|
|
30
|
+
| `notify` | null | `{ ntfy: "https://ntfy.sh/<topic>", email: null }`. Null means transitions are logged only. |
|
|
31
|
+
| `worktrees` | required | Directory for per-PRD checkouts. Must be outside the Gallivant home. |
|
|
32
|
+
| `github_token_file` | null | Null means the credential is resolved from the environment or from `gh`, so whichever way you are logged in already works. Set it to the path of a fine-grained PAT to narrow what a run can reach; it then wins over both of the others. See [GitHub credentials](#github-credentials). |
|
|
33
|
+
| `context_paths` | [] | Directories a run may READ in addition to its own checkout: a sibling repository, a folder of design notes. Absolute or `~`-prefixed, and each must exist. Writes do not widen. Your home directory, the Gallivant home, `~/.claude`, `~/.codex`, `~/.config/opencode` and `~/.aport` are refused, as is any parent of them. Claude Code renders each as `--add-dir <dir>`; Codex and Gemini CLI refuse the run rather than escalate; opencode and goose have no such flag and ignore it. Read [guardrails.md](guardrails.md#extra-read-directories-are-a-widening-not-a-feature) before setting it: whatever a run reads can leave in a commit or a PR body. |
|
|
34
|
+
| `harness` | local discovery | Omit it to discover local CLI harnesses on PATH. Explicit entries select the available providers and disable discovery; hosted entries always require explicit configuration. `task` and `new` default to the first configured entry. Select `--harness` or the PRD's `harness:` explicitly when several are configured. |
|
|
35
|
+
| `harness.claude` | | `{ bin, settings }`. `bin` is the absolute path to `claude`; `settings` is the Gallivant settings file with the APort hook and sandbox. |
|
|
36
|
+
| `harness.codex` | | `{ bin }`. |
|
|
37
|
+
| `harness.gemini` | | `{ bin }`. |
|
|
38
|
+
| `harness.opencode` | | `{ bin }`. |
|
|
39
|
+
| `harness.goose` | | `{ bin }`. |
|
|
40
|
+
| `harness.openai-agents` | absent | Explicit account, organization, project, model, API credential, hosted environment, network policy and allowance. See [Agents setup](openai-agents.md). |
|
|
41
|
+
| `harness.codex-cloud` | absent | Recognized for configuration/recovery, but new dispatch is unavailable. See [Cloud blocker](codex-cloud.md). |
|
|
42
|
+
| `execution` | bounded defaults | Transport/probe deadlines, progress cadence, concurrency, backoff and event limits. See [execution scheduling](execution.md#scheduling-and-limits). |
|
|
43
|
+
| `repos` | [] | The repositories Gallivant watches, appended by `start` and `register` and read by `up` to recover from GitHub. Each entry is either `"owner/name"` or an object: `{ "repo": "owner/name", "base": "staging", "run_timeout_minutes": 90 }`. Both object keys are optional. See below. |
|
|
44
|
+
|
|
45
|
+
The default allows 30 rounds as an upper allowance for unattended review. It is not a target or a promise of completion:
|
|
46
|
+
large tasks can exhaust it, while missing external prerequisites cannot be solved by more rounds. Reaching the cap
|
|
47
|
+
requires an operator decision, and it never increases itself.
|
|
48
|
+
Existing explicit `max_iterations` settings keep their value. More rounds allow more time and model spend; they do
|
|
49
|
+
not resolve missing credentials, permission denials or unavailable providers. Recheck a stopped PR after changing
|
|
50
|
+
the setting and restarting the daemon; its session, worktree and consumed rounds are preserved.
|
|
51
|
+
|
|
52
|
+
For unattended work, keep briefs small enough to review, confirm provider access and scoped tool permissions before
|
|
53
|
+
launch, and keep external setup separate from implementation acceptance. Shared agent instructions require a review
|
|
54
|
+
of related code paths before pushing and return control to the daemon for subsequent review batches. These instructions
|
|
55
|
+
help reduce repeated narrow fixes; the existing tests, reviewer and completion gate still decide readiness. A run is
|
|
56
|
+
ready for human review only when its actual criteria pass; more allowance does not authorize fabricating evidence.
|
|
57
|
+
|
|
58
|
+
## Execution providers
|
|
59
|
+
|
|
60
|
+
Gallivant has five local CLI adapters (Claude Code, Codex, Gemini CLI, OpenCode,
|
|
61
|
+
Goose) and the hosted `openai-agents` adapter. `codex-cloud` is recognized but
|
|
62
|
+
unavailable for new dispatch: the pinned CLI cannot establish the selected
|
|
63
|
+
Cloud environment's connected repository. Local Codex and Claude are enabled;
|
|
64
|
+
this is a separate Cloud transport restriction, not a disabled local harness.
|
|
65
|
+
|
|
66
|
+
Natural-language preparation is available through Claude, Codex 0.160.0+ and
|
|
67
|
+
OpenAI Agents. Other local adapters implement ready PRDs. The capability registry
|
|
68
|
+
in `lib/harness.mjs` is shared by configuration, probes, task clients and the
|
|
69
|
+
tracker; account/model access is checked separately. Hosted setup is documented
|
|
70
|
+
in [openai-agents.md](openai-agents.md) and [codex-cloud.md](codex-cloud.md).
|
|
71
|
+
|
|
72
|
+
Local adapters start non-interactively, resume an exact session and emit a
|
|
73
|
+
machine-readable stream. Earlier session-memory round trips established that
|
|
74
|
+
narrow contract; they did not establish successful repository implementation.
|
|
75
|
+
[The live support matrix](https://github.com/uchibeke/gallivant/blob/main/docs/audit/harness-live-validation.md) records actual
|
|
76
|
+
provider/model cases and blocked phases. Do not infer full live qualification
|
|
77
|
+
from adapter or fixture coverage. A hosted harness moves agent compute to the
|
|
78
|
+
provider; the coordinator, local verification and GitHub publication still need
|
|
79
|
+
an awake machine or a continuously running Node host.
|
|
80
|
+
|
|
81
|
+
`cursor-agent` has no registered adapter. The earlier local exploration did not
|
|
82
|
+
qualify its event stream and unattended permissions; it is not a supported choice.
|
|
83
|
+
Provider/model selection for local harnesses remains in each harness's own
|
|
84
|
+
configuration; Gallivant does not add a general per-task local model router.
|
|
85
|
+
|
|
86
|
+
## The base branch
|
|
87
|
+
|
|
88
|
+
The branch a pull request targets. It decides three things, and all three read one value:
|
|
89
|
+
|
|
90
|
+
- the branch the fresh checkout is cut from, in `git.create`
|
|
91
|
+
- the `gh pr create --base <branch>` the session is told to run
|
|
92
|
+
- the branch the daemon merges into the branch before every round of work
|
|
93
|
+
|
|
94
|
+
Gallivant used to read the repository's default branch from `gh repo view --json defaultBranchRef` and use that for all
|
|
95
|
+
three. That is wrong for a repository whose pull requests go somewhere else. On one real project `gh repo view`
|
|
96
|
+
reported `main` while every merged pull request had `baseRefName: staging`, so a branch was cut from `main` and the
|
|
97
|
+
session was told to open against `main`, both wrong, and nobody found out until the pull request existed.
|
|
98
|
+
|
|
99
|
+
Three ways to set it, highest first:
|
|
100
|
+
|
|
101
|
+
1. `gallivant start <prd.md> --base staging`. Used for that start, and written into `repos` for that repository, so it
|
|
102
|
+
is typed once rather than every time.
|
|
103
|
+
2. `{ "repo": "acme/widget", "base": "staging" }` in `repos`, written by hand or by the flag above.
|
|
104
|
+
3. The repository's default branch, as reported by GitHub. This is the fallback and it has not changed: an operator
|
|
105
|
+
who configures nothing gets what they get today.
|
|
106
|
+
|
|
107
|
+
It is a property of the repository, not of the operator and not of one brief. A single global key would be wrong for
|
|
108
|
+
anyone with two repositories, and a `base:` key in a brief's frontmatter would let two briefs in one repository
|
|
109
|
+
disagree about where their work lands, which is a contradiction with no good way to resolve it. So it lives per
|
|
110
|
+
repository in `repos`, and `SPEC.md` still says `base` is not a frontmatter field.
|
|
111
|
+
|
|
112
|
+
Nothing is derived. Gallivant could guess the base from the most common `baseRefName` among recent pull requests, or
|
|
113
|
+
from a `CONTRIBUTING.md`, or from a branch protection rule. All three can be wrong, and a wrong base that nobody typed
|
|
114
|
+
is the hardest kind to find: it produces a pull request aimed at the wrong branch with no line anywhere saying why. One
|
|
115
|
+
value typed once, printed by `doctor`, `status` and `why`, is worth more than a clever guess.
|
|
116
|
+
|
|
117
|
+
`gallivant doctor` prints the resolved base for every repository in `repos` and says where it came from:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
ok base branch acme/widget staging (configured in repos; acme/widget's default branch is main)
|
|
121
|
+
ok base branch acme/other main (acme/other's default branch; nothing configured). Pull requests will target it.
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
A base that is not on the remote fails at `gallivant start`, before anything is cloned and before a session is
|
|
125
|
+
spawned. Left to `gh pr create --base`, it fails after a run has implemented the brief, run the tests and spent the
|
|
126
|
+
budget, with the work stranded on a branch nobody asked for. `doctor` checks a configured base too, so a branch that
|
|
127
|
+
was deleted after it was configured is reported before the next start rather than during it.
|
|
128
|
+
|
|
129
|
+
## How long a run may take
|
|
130
|
+
|
|
131
|
+
For a local implementation run, `run_timeout_minutes` bounds its process lifetime;
|
|
132
|
+
expiry terminates the owned process group. Remote work has no local process to
|
|
133
|
+
kill: its provider capabilities determine cancellation, and unresolved ownership
|
|
134
|
+
remains fenced. See [execution controls](execution.md#scheduling-and-limits).
|
|
135
|
+
The limit resolves in this order:
|
|
136
|
+
|
|
137
|
+
1. `run_timeout_minutes` on the repository's `repos` entry: `{ "repo": "acme/big", "run_timeout_minutes": 120 }`.
|
|
138
|
+
2. The global `run_timeout_minutes`.
|
|
139
|
+
|
|
140
|
+
How long verification takes is a property of the codebase. A repository whose `Verify` commands are `pnpm type-check`
|
|
141
|
+
and `pnpm build`, at about 2m30s each, cannot do the same work in the same budget as one whose whole suite is `npm test`
|
|
142
|
+
in nine seconds, and an operator with both has no single number that is right for either.
|
|
143
|
+
|
|
144
|
+
**This is not a spend limit.** It catches a session that is genuinely wedged: spinning on a command that never returns,
|
|
145
|
+
waiting on a prompt that will never come, looping. `max_iterations` bounds automatic
|
|
146
|
+
rounds and Claude's `max_budget_usd` bounds that CLI's spend. Other local adapters
|
|
147
|
+
lack a dollar hard cap; hosted reservations bound new dispatch authorization,
|
|
148
|
+
not provider billing. Configure the provider's own spending controls as well.
|
|
149
|
+
|
|
150
|
+
The default was 30 and is now 720, twelve hours. Two runs are the argument. One review round was killed at 31.2 minutes
|
|
151
|
+
against the 30-minute limit with its work finished: it had verified four review findings against the code, created a
|
|
152
|
+
shared route inventory with a test pinning it, fixed a referrer bug, extracted an emitter and mutation-tested it, fixed
|
|
153
|
+
six contradictions on a page with a test pinning them, and run the full baseline green (846 tests, 611 type errors
|
|
154
|
+
matching the recorded baseline). Its last tool call wrote the commit message, and it was killed between writing that
|
|
155
|
+
message and running `git commit`, so none of it was committed and the entry reported `reason: timeout` as though the
|
|
156
|
+
session had failed. The healthy run before it took 7.3 hours and exited 0.
|
|
157
|
+
|
|
158
|
+
These observations explain the twelve-hour default; they do not guarantee every
|
|
159
|
+
legitimate task finishes inside it. Set a shorter or longer limit for the repository
|
|
160
|
+
when its verification and expected workload justify one.
|
|
161
|
+
|
|
162
|
+
The tradeoff is deliberate: a wedged session can consume time and provider usage
|
|
163
|
+
until stopped. Only Claude's native budget flag provides the local dollar cap
|
|
164
|
+
listed above; other providers need their own account controls. Raising the runtime
|
|
165
|
+
limit does not resolve missing access, failed verification or an exhausted budget.
|
|
166
|
+
|
|
167
|
+
The validation floor is 1, so an operator who deliberately wants a short timeout can set one, per repository or globally.
|
|
168
|
+
|
|
169
|
+
A kill reports what it interrupted. `gallivant why` names the limit that was exceeded and which of the two set it, so
|
|
170
|
+
the right number gets raised, and it names any work the kill left behind:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
killed: ran 31m and exceeded its 30m timeout (the global run_timeout_minutes). SIGKILL, so the session had no
|
|
174
|
+
chance to finish what it was doing; the run log below is the last thing it did.
|
|
175
|
+
UNCOMMITTED WORK IN THE WORKTREE: 6 modified, 4 untracked, held by no commit and in no PR.
|
|
176
|
+
Do NOT delete or recreate the checkout: `gallivant resume` rebuilds a missing worktree without asking, which
|
|
177
|
+
would discard this.
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The uncommitted count is the half worth reading. A run killed before its `git commit` has nothing committed, so a
|
|
181
|
+
commit count says zero about the worst case there is, and `resume` recreates a missing worktree without asking. An entry
|
|
182
|
+
killed with a clean tree says so too, which is what makes the warning mean something when it appears.
|
|
183
|
+
|
|
184
|
+
## Keeping a branch on its base
|
|
185
|
+
|
|
186
|
+
The daemon merges the base branch into the work branch before every round of work: before each fix round the tick
|
|
187
|
+
dispatches, and before a `gallivant resume`. At `start` there is nothing to do, because a branch just cut from the tip
|
|
188
|
+
of its base is already on it; what matters there is the fetch, so the cut is from the current base rather than a stale
|
|
189
|
+
local ref.
|
|
190
|
+
|
|
191
|
+
It is a merge, not a rebase, and the reason is not tidiness. A rebase rewrites history, so a pushed branch could only
|
|
192
|
+
move with a force-push, and the rules every session is given say "Do not force-push" for good reasons that apply just
|
|
193
|
+
as much to the daemon. A rebase would also strand the reviewer: Gallivant clears a head only when the commit the
|
|
194
|
+
reviewer named is that head, so every rewrite makes every prior clearance describe a commit that no longer exists, and
|
|
195
|
+
the reviewer's inline comments are anchored to those same commits. And the tick reads a head it did not put there as a
|
|
196
|
+
person's push, resetting the reviewer clock, so a daemon force-push would look to the daemon itself like a stranger
|
|
197
|
+
pushing.
|
|
198
|
+
|
|
199
|
+
A merge avoids all of that. It moves the head forward, so the remote takes it without `--force`; every reviewed commit
|
|
200
|
+
stays an ancestor, so a clearance keeps describing something real; and the commit it adds is the daemon's own, which
|
|
201
|
+
is the honest record. The price is a merge commit in the history, which is the cheaper of the two.
|
|
202
|
+
|
|
203
|
+
An outstanding review does not hold it up, which is a consequence of that choice rather than a separate decision. A
|
|
204
|
+
rebase would have had to wait for the review to land, and waiting is a deadlock: the reviewer is waiting for a fix and
|
|
205
|
+
the fix is waiting for the reviewer.
|
|
206
|
+
|
|
207
|
+
The merge commit stays local until the session pushes, so nothing on GitHub moves when it happens. The head the loop
|
|
208
|
+
is watching is unchanged, the reviewer clock is not reset, and the round is still counted from the session's own push,
|
|
209
|
+
which carries the merge with it as one head move rather than two.
|
|
210
|
+
|
|
211
|
+
A merge that conflicts is aborted, so a session is never handed a checkout with conflict markers in it. The entry then
|
|
212
|
+
stops with reason `base-conflict`, naming the base it could not merge, and `gallivant why` prints the conflict git
|
|
213
|
+
reported. A `gallivant resume` refuses instead of stopping, because there is an operator reading the output: it prints
|
|
214
|
+
the conflict and the `git merge` command that resolves it, and resumes nothing.
|
|
215
|
+
|
|
216
|
+
Every merge is recorded the moment it happens, not when the round finally starts, because several things after it can
|
|
217
|
+
decline the round. The log says what was merged into what and when, and `gallivant why` prints the same thing with the
|
|
218
|
+
commit, so an operator reading a diff they did not expect can see where the extra commit came from.
|
|
219
|
+
|
|
220
|
+
## What is deliberately not a key
|
|
221
|
+
|
|
222
|
+
Two bounds the daemon enforces have no setting, and both are fixed numbers with their reasoning written at the
|
|
223
|
+
constant.
|
|
224
|
+
|
|
225
|
+
- **How many consecutive failures of one GitHub write stop an entry.** Five, in `lib/tick.mjs`. Five steps in the
|
|
226
|
+
tick wait rather than proceed when a write will not land, and each of them stops the entry with reason `stuck` once
|
|
227
|
+
that write has failed five ticks in a row. The only question the number answers is "transient or permanent": at the
|
|
228
|
+
default `poll_seconds: 120` it is ten minutes, which outlasts a GitHub rate-limit window and falls far short of a
|
|
229
|
+
revoked token or a missing permission. `max_iterations` is not reused for it because that counts fix rounds, which
|
|
230
|
+
cost a harness run each, where these cost one API call.
|
|
231
|
+
- **How many entries the tick works on at once.** Four, also in `lib/tick.mjs`. Entries never block each other, and
|
|
232
|
+
`gh` is the shared resource.
|
|
233
|
+
|
|
234
|
+
Sign-in checks are not equal across the five. `claude auth status` and `codex login status` report on credentials
|
|
235
|
+
without spending a call, and `opencode auth list` prints the stored ones. Gemini CLI and goose have no such
|
|
236
|
+
command, so their probe only proves the binary runs; an unauthenticated run fails at its first model call and
|
|
237
|
+
shows up as a failed run rather than a failed probe.
|
|
238
|
+
|
|
239
|
+
These checks do not prove access to the selected model, installed-plugin behavior or a browser/capture surface.
|
|
240
|
+
`doctor` compares the configured executable with the login shell's command, resolves symlinks, and prints both
|
|
241
|
+
versions when they differ. A newer configured CLI can work while a nested bare command starts an older one;
|
|
242
|
+
do not point the config back at that older CLI just to silence the mismatch. Use the configured path explicitly
|
|
243
|
+
or align the shell with the verified installation.
|
|
244
|
+
|
|
245
|
+
Every start and resume tells the agent which executable Gallivant actually launched and asks it to check mandatory
|
|
246
|
+
external prerequisites before substantial work. This is an agent instruction, not a deterministic capability gate:
|
|
247
|
+
the daemon cannot infer every task's required host or purchase a model call on each health poll. Required live tests
|
|
248
|
+
still need their actual evidence, tied to the final artifact. Missing access or a capture surface must remain an
|
|
249
|
+
explicit blocker; fix that prerequisite before resuming the same session, rather than raising its iteration limit.
|
|
250
|
+
|
|
251
|
+
## The reviewer object
|
|
252
|
+
|
|
253
|
+
Gallivant does not know or care which tool reviews the pull request. Three fields say who the reviewer is, how to
|
|
254
|
+
ask it for another look, and what counts as a finding it is blocking on. The default is Codex because that is what
|
|
255
|
+
Gallivant was first driven against, and it is only a default:
|
|
256
|
+
|
|
257
|
+
```json
|
|
258
|
+
"reviewer": {
|
|
259
|
+
"login": "chatgpt-codex-connector[bot]",
|
|
260
|
+
"rereview_comment": "@codex review",
|
|
261
|
+
"blocking_pattern": "\\bP[01]\\b"
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
- `login` must be a bot account; it is matched exactly after normalising the `[bot]` suffix. A human with the same name is not the reviewer. This is the only thing that decides whether a review or an inline comment came from the reviewer.
|
|
266
|
+
- `rereview_comment` is the entire text Gallivant posts to ask for another look. It is posted after every push that came from a fix run, and again as a nudge if the reviewer says nothing for `rereview_minutes`. Gallivant never inspects the string, so a bot mention, a slash command or a plain sentence all work.
|
|
267
|
+
- `blocking_pattern` is a regex tested against each inline comment of the reviewer's newest review since the last request. A match, or a `CHANGES_REQUESTED` state, keeps the PR out of `ready-for-human`.
|
|
268
|
+
|
|
269
|
+
### Pointing it at a different reviewer
|
|
270
|
+
|
|
271
|
+
Nothing beyond these three fields needs to change. Two worked examples.
|
|
272
|
+
|
|
273
|
+
A different review bot, here one that labels its findings by severity instead of Codex's `P0`/`P1`:
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
"reviewer": {
|
|
277
|
+
"login": "coderabbitai[bot]",
|
|
278
|
+
"rereview_comment": "@coderabbitai full review",
|
|
279
|
+
"blocking_pattern": "(?:^|\\s)(?:Critical|Major):"
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
A human reviewer, which needs no knowledge of any vendor at all. `login` is their GitHub login and `rereview_comment`
|
|
284
|
+
is whatever you would have typed yourself:
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
"reviewer": {
|
|
288
|
+
"login": "alice",
|
|
289
|
+
"rereview_comment": "@alice pushed a fix, ready for another look",
|
|
290
|
+
"blocking_pattern": "\\bblocking\\b"
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
One caveat on the human case, and it is a real one: `login` is required to be a bot account, because a `[bot]`
|
|
295
|
+
account is what distinguishes a review tool from the trusted humans in `trusted_authors`, whose comments Gallivant
|
|
296
|
+
dispatches a fix run against rather than waiting on. So a human login in `reviewer.login` never matches, the verdict
|
|
297
|
+
stays `waiting`, and the PR waits out `stall_hours` and then stops with `reviewer-silent` for a person to look at.
|
|
298
|
+
That is a reasonable way to run a repo where a human gates every merge, but it is a wait, not a verdict. If you want
|
|
299
|
+
the human's findings to drive fix rounds instead, put them in `trusted_authors` and set `reviewer` to null.
|
|
300
|
+
|
|
301
|
+
### No automated reviewer
|
|
302
|
+
|
|
303
|
+
`"reviewer": null` means there is no review tool: only humans and CI drive the loop, and a PR is ready as soon as
|
|
304
|
+
checks are green. Gallivant asks nobody for a re-review, so there is no nudge and no `reviewer-silent` stop. It still
|
|
305
|
+
comments after a push, because the round comment is also how a recovery rebuilds the round count off the PR, but the
|
|
306
|
+
comment says `Pushed a fix.` and mentions no one.
|
|
307
|
+
|
|
308
|
+
## Harness flags
|
|
309
|
+
|
|
310
|
+
Flags are not configurable; they live in `lib/harness.mjs` and are the verified working form for each CLI version:
|
|
311
|
+
|
|
312
|
+
- Claude Code: `-p --permission-mode auto --permission-prompts none --max-budget-usd <n> --settings <file> --output-format stream-json --verbose`, with `--session-id <uuid>` on start and `--resume <uuid>` on resume.
|
|
313
|
+
- Codex: `exec --json -c approval_policy="on-request" -c approvals_reviewer="auto_review" -c sandbox_mode="workspace-write" -c sandbox_workspace_write.network_access=true --skip-git-repo-check`, with the same overrides on `exec resume <id>`. The automatic reviewer handles Git metadata writes without an operator click; a denied escalation still stops. These are adapter defaults, not operator settings.
|
|
314
|
+
- Gemini CLI: `--skip-trust --output-format stream-json -p`, with `--session-id <uuid>` on start and `--resume <uuid>` on resume. `--skip-trust` is mandatory: without it a headless run in an untrusted directory refuses and the run is wasted. The help text says `--resume` takes "latest" or an index; that is incomplete, and a UUID is the only form that resumes one specific session.
|
|
315
|
+
- opencode: `run --format json`, with `--session <id>` on resume. Ids are minted by the server and cannot be pre-assigned, so the id is read back off the run's own JSONL, as with Codex.
|
|
316
|
+
- goose: `run --output-format stream-json -t`, with `--name <id>` on start and `--resume --name <id>` on resume. `--session-id` looks like the flag to use but is resume-only and refuses without `--resume`, so it cannot name a session the daemon will come back to.
|
|
317
|
+
|
|
318
|
+
## Files a brief carries: `sources`
|
|
319
|
+
|
|
320
|
+
A brief often cites documents beside it. Name them in its frontmatter and they travel into the session's checkout:
|
|
321
|
+
|
|
322
|
+
```yaml
|
|
323
|
+
---
|
|
324
|
+
status: ready
|
|
325
|
+
harness: codex
|
|
326
|
+
sources:
|
|
327
|
+
- docs/research-notes.md
|
|
328
|
+
- AGENTS.md
|
|
329
|
+
---
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Paths are relative to the repository root, named one at a time with forward slashes. Absolute paths, `..` segments,
|
|
333
|
+
globs, directories, `.git` components and symlinks at any path component are refused. `gallivant ready` reads every file and refuses the brief if one is missing, unreadable or
|
|
334
|
+
not text, so a mistyped path costs a second at your terminal rather than thirty minutes inside a paid run.
|
|
335
|
+
|
|
336
|
+
Each file is snapshotted beside the brief's own snapshot and written into the checkout from that snapshot, not from your
|
|
337
|
+
tree. That is the same rule the brief follows: the daemon judges what it recorded, so a session cannot widen what it was
|
|
338
|
+
given by editing a document it was handed. Editing one in your own tree afterwards is reported by `gallivant why` the
|
|
339
|
+
same way an edited brief is, and the list itself is part of the contract, so adding or removing a file is a contract
|
|
340
|
+
change even though no criterion moved. Changes to either the manifest or file bytes block readiness and resume until
|
|
341
|
+
accepted with `resume --resnapshot`; accepted snapshots are also restored when a checkout is recreated.
|
|
342
|
+
|
|
343
|
+
`sources` and `context_paths` answer different questions and neither replaces the other. `sources` puts named files
|
|
344
|
+
INSIDE the checkout, so it works on every harness because it needs no flag at all. `context_paths` grants a session read
|
|
345
|
+
access to directories OUTSIDE its checkout, which only some harnesses can express:
|
|
346
|
+
|
|
347
|
+
`context_paths` renders per harness, and only Claude Code has a flag that grants read without also granting write:
|
|
348
|
+
|
|
349
|
+
- Claude Code: `--add-dir <dir>` once per path, placed before the prompt. Writes stay bounded by the sandbox in the file `--settings` points at.
|
|
350
|
+
- Codex: refused. `--add-dir` is documented by Codex itself as making directories *writable*, and `sandbox_permissions=["disk-full-read-access"]` is the whole disk. Checked against 0.157.0: a per-path spelling and `sandbox_workspace_write.readable_roots` are both reported as ignored settings, so the read would silently not happen.
|
|
351
|
+
- Gemini CLI: refused. `--include-directories` adds to the workspace, which is Gemini's write boundary as well as its read boundary.
|
|
352
|
+
- opencode and goose: no directory flag exists. The list is ignored and `doctor` reports that it has no effect on those runs, rather than failing a run over a setting the CLI cannot honour.
|
|
353
|
+
|
|
354
|
+
An empty list renders nothing at all, so a run without extras has the argv it had before the setting existed.
|
|
355
|
+
|
|
356
|
+
The path validator rejects the named control directories and grants that contain
|
|
357
|
+
them, resolving existing symlinks before checking. It does not reject every
|
|
358
|
+
descendant of those directories or discover secrets in otherwise allowed folders.
|
|
359
|
+
Do not grant a sensitive subdirectory such as a harness's session or credential
|
|
360
|
+
folder. This validation is a narrow configuration check; the selected harness's
|
|
361
|
+
permissions remain the access boundary.
|
|
362
|
+
|
|
363
|
+
Any `--dangerously-*` flag is refused by the adapter, and so is each harness's own equivalent: `-y`/`--yolo` and
|
|
364
|
+
`--approval-mode yolo` for Gemini CLI, `--auto` for opencode, and a `sandbox_mode` override whose value contains
|
|
365
|
+
`danger` for Codex. goose has no such flag at all; it is bounded by the `GOOSE_MODE` environment variable, which
|
|
366
|
+
the adapter pins to `smart_approve` and the same check refuses to loosen. Gallivant never edits
|
|
367
|
+
`~/.claude/settings.json`, `~/.codex/config.toml` or `~/.config/goose/config.yaml`.
|
|
368
|
+
|
|
369
|
+
## GitHub credentials
|
|
370
|
+
|
|
371
|
+
A run is sandboxed. It cannot read your keychain, your Linux secret service, `~/.git-credentials`, or `~/.config/gh`. The daemon is not sandboxed, so it resolves a credential and passes it to the run in the environment as `GH_TOKEN`, where a git credential helper carried in the same environment reads it.
|
|
372
|
+
|
|
373
|
+
Resolution order, first answer wins:
|
|
374
|
+
|
|
375
|
+
| Order | Source | When it applies |
|
|
376
|
+
|---|---|---|
|
|
377
|
+
| 1 | `github_token_file` | You set one. Explicit and scoped, so it is never overridden by anything below. |
|
|
378
|
+
| 2 | `GH_TOKEN` or `GITHUB_TOKEN` in the daemon's environment | CI, and anyone who exports a token in a shell profile. `GH_TOKEN` is read first. |
|
|
379
|
+
| 3 | `gh auth token` | Everything else. `gh` answers for whichever store it used, so a keyring, a secret service and a plain `hosts.yml` all work without extra setup. |
|
|
380
|
+
|
|
381
|
+
Gallivant asks `gh` rather than reading `~/.config/gh/hosts.yml` itself. Parsing that file would reimplement `gh`'s own resolution and drift from it as `gh` changes.
|
|
382
|
+
|
|
383
|
+
If nothing answers, `gallivant start` refuses and names all three, and the daemon stops the entry with reason `no-credential`. Neither spends a run that could not push.
|
|
384
|
+
|
|
385
|
+
Each run resolves its credential at launch rather than using a token cached at daemon boot. Coordinator Git/GitHub operations also resolve credentials when invoked. Resolution costs a subprocess only when neither a selected file nor the environment answered. A daemon can run for days; re-authentication can therefore take effect on its next operation without restarting it.
|
|
386
|
+
|
|
387
|
+
The coordinator passes the token to a local child through its environment, not
|
|
388
|
+
argv or `cmd.json`. Credential-resolution diagnostics print its source and length,
|
|
389
|
+
not the secret. Raw child output goes to private `provider.log`; recognized
|
|
390
|
+
credentials and supplied secret values are redacted before projection into the
|
|
391
|
+
tracker's `job.log`. A child can print its environment or other sensitive data, so
|
|
392
|
+
raw diagnostics can contain secrets and redaction does not cover every form of
|
|
393
|
+
sensitive content. Keep logs private and inspect/redact them before sharing.
|
|
394
|
+
Token scope and repository protections remain essential.
|
|
395
|
+
|
|
396
|
+
The credential resolver can select a host from an existing checkout's `origin`
|
|
397
|
+
and ask `gh auth token --hostname` for it. That is not end-to-end GitHub Enterprise
|
|
398
|
+
support: new-task repository resolution, clone URLs and learning identity currently
|
|
399
|
+
use github.com. An enterprise token alone does not make those workflows supported.
|
|
400
|
+
|
|
401
|
+
A configured token file that is missing, unreadable or empty fails closed with
|
|
402
|
+
`NO_CREDENTIAL`. It never falls back to a broader environment token or saved login.
|
|
403
|
+
Restore the file, or deliberately set `github_token_file` to `null` if fallback
|
|
404
|
+
is the intended policy. Restart an already-running daemon after changing that
|
|
405
|
+
setting; it keeps the configuration loaded at startup. The coordinator resolves
|
|
406
|
+
the selected credential per operation, so removing a selected file blocks later
|
|
407
|
+
Git/GitHub operations even while the daemon runs.
|
|
408
|
+
|
|
409
|
+
## Environment for runs
|
|
410
|
+
|
|
411
|
+
Every run gets the resolved credential as `GH_TOKEN`, the git settings below, and per harness: `APORT_CLAUDE_CODE_CONFIG_DIR=<dir of the settings file>` for Claude, `GEMINI_CLI_TRUST_WORKSPACE=true` for Gemini CLI, and `GOOSE_MODE=smart_approve` for goose. Runs are spawned with the checkout as the working directory and binaries by absolute path, so shell functions and aliases around `claude` or `codex` are bypassed.
|
|
412
|
+
|
|
413
|
+
### Git settings for a run
|
|
414
|
+
|
|
415
|
+
No file. Git's own `GIT_CONFIG_COUNT` with numbered `GIT_CONFIG_KEY_n`/`GIT_CONFIG_VALUE_n` pairs carries four settings:
|
|
416
|
+
|
|
417
|
+
| Key | Value |
|
|
418
|
+
|---|---|
|
|
419
|
+
| `user.name` | Your Git-configured name, resolved by the coordinator for each run |
|
|
420
|
+
| `user.email` | Your Git-configured email, resolved by the coordinator for each run |
|
|
421
|
+
| `push.default` | `current` |
|
|
422
|
+
| `credential.helper` | a shell snippet that prints `GH_TOKEN` as the password, and prints nothing when there is no `GH_TOKEN` |
|
|
423
|
+
|
|
424
|
+
Plus `GIT_CONFIG_NOSYSTEM=1` and `GIT_CONFIG_GLOBAL=/dev/null`.
|
|
425
|
+
|
|
426
|
+
Nothing in that list is a path outside the worktree, and that is the point. Until v0.1 the helper lived in `~/.gallivant/gitconfig` with `GIT_CONFIG_GLOBAL` pointing at it. A sandboxed run cannot read outside its worktree, so every git command failed with `unable to access '/Users/you/.gallivant/gitconfig': Operation not permitted`, including `git status`. The token was in the environment the whole time and unusable, because the file that would have read it could not itself be read. A test asserts that no value `runEnv` returns is an absolute path other than `/dev/null`, so adding a config file back fails the suite.
|
|
427
|
+
|
|
428
|
+
Both isolating variables are load-bearing, and `GIT_CONFIG_NOSYSTEM` is not decoration. `GIT_CONFIG_GLOBAL` replaces only git's global file; the system one still applies, and on a Mac with the Xcode command line tools `/Library/Developer/CommandLineTools/usr/share/git-core/gitconfig` sets `credential.helper = osxkeychain`. Without `GIT_CONFIG_NOSYSTEM` a run had a path to your keychain whatever else was set. With it, `git config --get-all credential.helper` in a run's environment returns nothing but the snippet above.
|
|
429
|
+
|
|
430
|
+
What this does not bound, said plainly because it is easy to mistake for new: a run's own `.git/config` is still read, `credential.helper` is multi-valued, and a repo-local helper is consulted before this one. That was equally true when the helper lived in a global file, since local config always won there too. A run owns its worktree, so it can always add a helper. What bounds a run is the token's scope and the branch ruleset, not how the config travels.
|