@uchibeke/gallivant 0.2.1 → 0.3.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/README.md +38 -16
- package/SECURITY.md +9 -0
- package/bin/gallivant.mjs +33 -10
- package/bin/promote-bitbucket.mjs +40 -0
- package/bin/task-pr.mjs +9 -0
- package/bin/task-review.mjs +15 -0
- package/docs/README.md +2 -0
- package/docs/architecture.md +11 -3
- package/docs/bitbucket-promotion.md +69 -0
- package/docs/bitbucket-review.md +330 -0
- package/docs/chains.md +30 -1
- package/docs/codex-cloud.md +7 -0
- package/docs/codex-review.md +50 -0
- package/docs/commands.md +65 -5
- package/docs/config.md +35 -10
- package/docs/first-party-review.md +266 -0
- package/docs/git-hosts.md +294 -0
- package/docs/guardrails.md +56 -0
- package/docs/install.md +18 -8
- package/docs/network-recovery.md +9 -0
- package/docs/openai-agents.md +2 -1
- package/docs/prd.md +23 -2
- package/docs/preparation.md +1 -1
- package/docs/release-qualification.md +1 -1
- package/docs/releasing.md +17 -2
- package/docs/task-delivery.md +4 -4
- package/docs/task-profiles.md +537 -0
- package/docs/tasks.md +59 -197
- package/lib/attribution.mjs +7 -3
- package/lib/block.mjs +10 -1
- package/lib/chains.mjs +101 -30
- package/lib/codex_cloud.mjs +2 -1
- package/lib/codex_cloud_input.mjs +15 -6
- package/lib/codex_cloud_result.mjs +1 -1
- package/lib/commands/doctor.mjs +22 -462
- package/lib/commands/init.mjs +72 -30
- package/lib/commands/register.mjs +25 -11
- package/lib/commands/resume.mjs +5 -1
- package/lib/commands/review.mjs +66 -0
- package/lib/commands/serve.mjs +8 -0
- package/lib/commands/start.mjs +30 -11
- package/lib/commands/status.mjs +18 -2
- package/lib/commands/task.mjs +9 -1
- package/lib/commands/up.mjs +19 -11
- package/lib/commands/why.mjs +7 -3
- package/lib/config.mjs +42 -9
- package/lib/context.mjs +23 -18
- package/lib/control.mjs +21 -6
- package/lib/credential.mjs +20 -14
- package/lib/deliverable.mjs +2 -0
- package/lib/events.mjs +96 -53
- package/lib/execution.mjs +48 -3
- package/lib/execution_preparation.mjs +16 -1
- package/lib/execution_result.mjs +26 -5
- package/lib/first_party_review.mjs +618 -0
- package/lib/gh.mjs +144 -56
- package/lib/git.mjs +133 -32
- package/lib/git_diff.mjs +121 -0
- package/lib/harness.mjs +14 -10
- package/lib/host_contract.mjs +71 -0
- package/lib/host_definitions.mjs +22 -0
- package/lib/hosts/bitbucket.mjs +553 -0
- package/lib/hosts/bitbucket_contract.mjs +57 -0
- package/lib/hosts/bitbucket_credential.mjs +62 -0
- package/lib/hosts/bitbucket_review.mjs +44 -0
- package/lib/hosts/bitbucket_review_producer.mjs +50 -0
- package/lib/hosts/github.mjs +57 -0
- package/lib/hosts/github_contract.mjs +27 -0
- package/lib/hosts/github_doctor.mjs +482 -0
- package/lib/hosts/github_review_summary.mjs +38 -0
- package/lib/hosts/transport_doctor.mjs +35 -0
- package/lib/hosts.mjs +130 -0
- package/lib/learning_qualification.mjs +1 -1
- package/lib/notify.mjs +1 -1
- package/lib/openai_agents_input.mjs +7 -2
- package/lib/openai_agents_sources.mjs +16 -8
- package/lib/plugin_package.mjs +13 -1
- package/lib/pr_operation.mjs +41 -0
- package/lib/prd.mjs +18 -3
- package/lib/preparation_codex.mjs +6 -1
- package/lib/probe.mjs +6 -4
- package/lib/process.mjs +24 -0
- package/lib/promotion.mjs +132 -0
- package/lib/promotion_git.mjs +63 -0
- package/lib/prompt.mjs +16 -13
- package/lib/provider_health.mjs +14 -3
- package/lib/repository.mjs +68 -0
- package/lib/repository_preflight.mjs +11 -0
- package/lib/review_evaluation.mjs +25 -0
- package/lib/review_evidence.mjs +500 -0
- package/lib/review_history.mjs +73 -0
- package/lib/review_image.mjs +145 -0
- package/lib/review_link.mjs +12 -0
- package/lib/review_policy.mjs +132 -0
- package/lib/review_policy_contract.mjs +18 -0
- package/lib/review_state.mjs +13 -0
- package/lib/review_visual.mjs +94 -0
- package/lib/runtime-environment.mjs +6 -0
- package/lib/runtime_node.mjs +3 -2
- package/lib/runtime_state.mjs +39 -5
- package/lib/sources/prd.mjs +6 -3
- package/lib/state.mjs +137 -24
- package/lib/task_completion.mjs +7 -3
- package/lib/task_contract.mjs +201 -27
- package/lib/task_delivery.mjs +9 -2
- package/lib/task_delivery_contract.mjs +13 -9
- package/lib/task_delivery_view.mjs +18 -2
- package/lib/task_environment.mjs +13 -1
- package/lib/task_limits.mjs +13 -0
- package/lib/task_model_config.mjs +99 -0
- package/lib/task_model_route.mjs +243 -0
- package/lib/task_prepare.mjs +21 -8
- package/lib/task_profile.mjs +1875 -0
- package/lib/task_repository_context.mjs +65 -0
- package/lib/tasks.mjs +252 -51
- package/lib/tick.mjs +353 -97
- package/lib/time.mjs +1 -0
- package/lib/tracker/pages.mjs +19 -5
- package/lib/tracker/review.mjs +21 -0
- package/lib/tracker_process.mjs +8 -1
- package/lib/verification_sandbox.mjs +2 -1
- package/lib/work_artifacts.mjs +79 -0
- package/npm-shrinkwrap.json +12 -3
- package/package.json +9 -3
- package/templates/plugin/task-delivery.SKILL.md +17 -10
- package/templates/plugin/task-intake-contract.json +18 -0
- package/templates/plugin/task-profile-requests.mjs +1448 -0
- package/templates/tracker/tracker.css +3 -0
- package/templates/tracker/tracker.js +38 -0
package/README.md
CHANGED
|
@@ -19,10 +19,10 @@ Run this inside the Git checkout you want to work in:
|
|
|
19
19
|
npx --yes @uchibeke/gallivant init --harness codex # or: --harness claude
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
The setup flow below requires **0.2.1 or newer**.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
The setup flow below requires **0.2.1 or newer**. `npx` runs npm's latest published
|
|
23
|
+
version. Changes merged on `main` become available through npm after a
|
|
24
|
+
[versioned release](docs/releasing.md); check the registry with
|
|
25
|
+
`npm view @uchibeke/gallivant version`.
|
|
26
26
|
|
|
27
27
|
`init` writes a private config at `~/.gallivant/config.json`, records this
|
|
28
28
|
repository, creates a separate directory for task checkouts, and ends with the
|
|
@@ -62,8 +62,8 @@ from you or an account with a third party.
|
|
|
62
62
|
|
|
63
63
|
- **Reviewer.** The default loop waits for the Codex GitHub review integration
|
|
64
64
|
(`chatgpt-codex-connector[bot]`). Enable it on the repository, configure
|
|
65
|
-
another reviewer, or
|
|
66
|
-
|
|
65
|
+
another reviewer, or opt into a [qualified first-party reviewer](docs/first-party-review.md).
|
|
66
|
+
Explicit legacy no-review needs both `"reviewer": null` and `review.legacy_no_review: true`. Without one of these, the first PR waits for a review
|
|
67
67
|
that never arrives. See [the reviewer key](docs/config.md#the-reviewer-object).
|
|
68
68
|
- **Branch protection.** `doctor` prints the `gh api` call that creates a
|
|
69
69
|
ruleset blocking direct and force pushes to the default branch. Run it
|
|
@@ -80,14 +80,17 @@ from you or an account with a third party.
|
|
|
80
80
|
|
|
81
81
|
```sh
|
|
82
82
|
gallivant task "Document how to run the existing tests in CONTRIBUTING.md. Use the commands already defined in the repository. Change documentation only." \
|
|
83
|
-
--harness codex --verify 'git diff --check'
|
|
83
|
+
--harness codex --verify 'git diff --check'
|
|
84
|
+
# Review the draft, then substitute its returned task ID and revision:
|
|
85
|
+
gallivant task start TASK_ID --revision REVISION --run
|
|
84
86
|
gallivant start
|
|
85
87
|
```
|
|
86
88
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
`task` uses your harness to prepare a brief, which is a paid inference call,
|
|
90
|
+
and may ask you questions. Review the draft and answer any questions, then use
|
|
91
|
+
its printed `Next` action to approve that revision with `task start --run`.
|
|
92
|
+
Preparation always returns for review, even if its command includes `--run`.
|
|
93
|
+
The agent then works in a clone under `~/.gallivant-worktrees`,
|
|
91
94
|
pushes a branch, opens a PR against the default branch, and the daemon watches CI
|
|
92
95
|
and the reviewer, sending each round of feedback back to the same session.
|
|
93
96
|
`gallivant start` opens the dashboard at **http://127.0.0.1:8791** and keeps
|
|
@@ -119,7 +122,7 @@ and the same review loop. [Writing briefs](docs/prd.md) · [Task commands](docs/
|
|
|
119
122
|
2. Each tick (every two minutes by default) the daemon reads CI and reviewer
|
|
120
123
|
state. A blocking review or a failing check starts a fix round in the same
|
|
121
124
|
agent session, and the fix is pushed to the same PR.
|
|
122
|
-
3. Rounds stop at `max_iterations` (
|
|
125
|
+
3. Rounds stop at `max_iterations` (60 by default), when the reviewer stays
|
|
123
126
|
silent for `stall_hours`, or when a run exceeds its timeout. Every stop is
|
|
124
127
|
recorded with a reason you can read with `gallivant why`.
|
|
125
128
|
4. **Ready** means the configured checks, reviewer and acceptance gate passed.
|
|
@@ -210,7 +213,7 @@ for the installed version's flags.
|
|
|
210
213
|
daemon, including when execution is hosted. Gallivant is not a managed service.
|
|
211
214
|
- **Your accounts pay for inference.** Subscriptions and API credits differ.
|
|
212
215
|
There is no automatic provider fallback. Review [limits](docs/config.md) before
|
|
213
|
-
unattended work; the default review allowance is
|
|
216
|
+
unattended work; the default review allowance is 60 rounds.
|
|
214
217
|
- **Your permissions apply.** Scope GitHub access to the required repositories.
|
|
215
218
|
Harness permissions, OS sandboxing and optional APort checks are different
|
|
216
219
|
controls. [Security boundaries](docs/guardrails.md).
|
|
@@ -223,9 +226,20 @@ for the installed version's flags.
|
|
|
223
226
|
|
|
224
227
|
## Documentation
|
|
225
228
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
+
| Need | Guide |
|
|
230
|
+
| --- | --- |
|
|
231
|
+
| Install and run your first task | [Getting started](docs/install.md) |
|
|
232
|
+
| Start from a request, answer questions, inspect results | [Natural-language tasks](docs/tasks.md) |
|
|
233
|
+
| Select the typed delivery profile and planner route for a request | [Typed delivery profiles](docs/task-profiles.md) |
|
|
234
|
+
| Write a brief or define noncoding acceptance checks | [PRD contract](docs/prd.md) |
|
|
235
|
+
| Use API-backed hosted execution | [OpenAI Agents](docs/openai-agents.md) |
|
|
236
|
+
| Dashboard messages, controls and recovery | [Dashboard](docs/app.md) · [Commands](docs/commands.md) |
|
|
237
|
+
| Models, credentials, limits and notifications | [Configuration](docs/config.md) |
|
|
238
|
+
| Plan sequential work | [Chains](docs/chains.md) |
|
|
239
|
+
| Embed task delivery in another application | [Task-delivery package](docs/task-delivery.md) |
|
|
240
|
+
| Understand internals or recorded qualification | [Documentation index](docs/README.md) |
|
|
241
|
+
| Set up from an agent | [Agent setup](docs/agent-setup.md) |
|
|
242
|
+
| Release, contribute or report a vulnerability | [Releasing](docs/releasing.md) · [Contributing](CONTRIBUTING.md) · [Security](SECURITY.md) |
|
|
229
243
|
|
|
230
244
|
**Asking a coding agent to set this up?** Give it the
|
|
231
245
|
[setup-agent instructions](docs/agent-setup.md). They cover inspection, the
|
|
@@ -238,3 +252,11 @@ hosted service or publish a chat plugin.
|
|
|
238
252
|
|
|
239
253
|
[Contributing](CONTRIBUTING.md) · [Security reports](SECURITY.md) ·
|
|
240
254
|
[Releasing](docs/releasing.md) · [Apache-2.0 license](LICENSE)
|
|
255
|
+
|
|
256
|
+
## Bitbucket Cloud
|
|
257
|
+
|
|
258
|
+
Bitbucket Cloud tasks support the same independent local Codex review service as GitHub. The adapter captures an exact same-repository PR comparison through Bitbucket's commit, diffstat, merge-base and source APIs, then the shared service divides large evidence into bounded jobs. Activation still requires a successful live route qualification; until that and the disposable private/public review matrix pass, keep an existing run's explicit legacy no-review policy. A prior public disposable run qualified PR publication and receipt acceptance only, not semantic review. See the [setup, activation limits and troubleshooting guide](docs/bitbucket-review.md). Bitbucket-only setup needs no GitHub sign-in and does not install a pipeline or alter repository settings.
|
|
259
|
+
|
|
260
|
+
## First-party review (opt-in)
|
|
261
|
+
|
|
262
|
+
New tasks can pin `external-required`, `first-party-required`, or `both-required`. Repositories with the configured external reviewer retain their existing default. First-party review requires a separately qualified restricted route; it never claims Git-host bot approval. GitHub and Bitbucket Cloud are the only evidence adapters currently implemented. See [selection, qualification and migration](docs/first-party-review.md) and the [current live qualification gap](docs/audit/first-party-review.md). Existing no-review runs keep that requirement until the audited policy command changes them and disclose the absence of semantic review.
|
package/SECURITY.md
CHANGED
|
@@ -17,3 +17,12 @@ they need. Automatic approval is not equivalent to an operating-system sandbox.
|
|
|
17
17
|
The [OpenAI Agents API adapter](docs/openai-agents.md) keeps service credentials
|
|
18
18
|
on the coordinator and publishes returned artifacts through that coordinator.
|
|
19
19
|
Its hosted execution controls differ from local harness hooks and sandboxes.
|
|
20
|
+
|
|
21
|
+
## Bitbucket reviewer isolation
|
|
22
|
+
|
|
23
|
+
Use a separate reviewer account and keep its publication credential outside the
|
|
24
|
+
coding process's filesystem and environment access. A private file readable by
|
|
25
|
+
the coding identity does not provide isolation. The review model receives the
|
|
26
|
+
prepared diff and a read-only checkout; an operator publishes its exact-head
|
|
27
|
+
verdict. See [reviewer setup](docs/bitbucket-review.md#independent-local-codex-reviewer).
|
|
28
|
+
Bitbucket dispatch remains disabled pending live qualification.
|
package/bin/gallivant.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// CLI entry. Parses arguments, builds the context, calls one command. No logic lives here.
|
|
3
|
+
import { reviewCommand } from "../lib/commands/review.mjs";
|
|
3
4
|
import { acquireCliMutation } from "../lib/daemon.mjs";
|
|
4
5
|
import { parseArgs } from "node:util";
|
|
5
6
|
import { makeContext } from "../lib/context.mjs";
|
|
@@ -28,7 +29,7 @@ import { safeDisplay } from "../lib/learning_data.mjs";
|
|
|
28
29
|
|
|
29
30
|
const HELP = `gallivant: give an agent a task, let the review loop run, go outside.
|
|
30
31
|
|
|
31
|
-
gallivant init [--harness codex|claude] [--worktrees <directory>]
|
|
32
|
+
gallivant init [--harness codex|claude] [--worktrees <directory>] [--host-config <json>] [--base <branch>]
|
|
32
33
|
set up the repository you are in: private config, this repo in repos,
|
|
33
34
|
then doctor's report. Safe to repeat; never signs in or overwrites settings
|
|
34
35
|
gallivant --version print the installed version
|
|
@@ -51,15 +52,18 @@ const HELP = `gallivant: give an agent a task, let the review loop run, go outsi
|
|
|
51
52
|
write a starting PRD into docs/prds/ of the repo you are in
|
|
52
53
|
gallivant task [prepare] "<request>" [--stdin] [--dir <checkout>] [--repo <owner/repo>]
|
|
53
54
|
[--base <branch>] [--harness <name>] [--context <file>] [--request-id <id>] [--run] [--json]
|
|
54
|
-
[--completion-target delivery_ready|integrated] [--
|
|
55
|
-
|
|
55
|
+
[--completion-target delivery_ready|integrated] [--delivery pull_request] [--route harness|model]
|
|
56
|
+
[--verify <command>] [--no-daemon]
|
|
57
|
+
prepare an isolated brief for review; approve later with task start --run
|
|
58
|
+
--delivery selects the typed delivery profile (the only delivery authority);
|
|
59
|
+
--route drafts through the connected harness or the operator's bounded model route
|
|
56
60
|
Claude, Codex and OpenAI Agents support isolated preparation.
|
|
57
61
|
Other local harnesses use written PRDs; Codex Cloud dispatch is unavailable
|
|
58
62
|
gallivant task answer <task-id> --revision <n> --request-id <answer-id> --answer <question-id=text>
|
|
59
63
|
resume saved questions; --answer, --context and --verify may repeat
|
|
60
64
|
gallivant task inspect <task-id> [--json] | task list [--json]
|
|
61
65
|
gallivant task resume|validate|start <task-id> --revision <n> [--run] [--no-daemon] [--json]
|
|
62
|
-
shared versioned operations; start
|
|
66
|
+
shared versioned operations; start --run approves the reviewed revision
|
|
63
67
|
gallivant ready <prd.md> [--check] check the PRD against the contract; flip it to ready when it passes
|
|
64
68
|
gallivant start [prd.md] [--no-daemon] [--no-server] [--no-open] [--port 8791] [--base <branch>]
|
|
65
69
|
start the daemon and dashboard, then open it; optionally start a ready PRD
|
|
@@ -67,6 +71,7 @@ const HELP = `gallivant: give an agent a task, let the review loop run, go outsi
|
|
|
67
71
|
gallivant register <pr-url> [--harness <name>] [--session <id>] [--worktree <dir>]
|
|
68
72
|
watch a hand-made PR; on an existing entry, reset it
|
|
69
73
|
gallivant chain <name> <prd1.md> <prd2.md> ... [--base <branch>]
|
|
74
|
+
gallivant chain <name> --after <pr-url|handle> <next.md> ...
|
|
70
75
|
queue ready PRDs; each starts after the previous PR is merged
|
|
71
76
|
gallivant chain [<name>] [--cancel | --retry [--resnapshot]]
|
|
72
77
|
inspect chains, stop future work, or retry after fixing a blocker
|
|
@@ -81,6 +86,15 @@ const HELP = `gallivant: give an agent a task, let the review loop run, go outsi
|
|
|
81
86
|
--resnapshot makes an edited brief the one the gate judges
|
|
82
87
|
--fresh-session replaces retired learned memory after settlement
|
|
83
88
|
gallivant recheck <handle> queue a PR recheck without resetting its budget
|
|
89
|
+
gallivant review retry <handle> --reason <text>
|
|
90
|
+
retry confirmed incomplete or coordinator-cancelled review; retain pieces and limits
|
|
91
|
+
gallivant review qualify --repo <owner/repo> [--capability text|image] [--run [--wait] [--retry --reason <text>]]
|
|
92
|
+
inspect qualification; --run launches a bounded probe; --wait records its exit before returning
|
|
93
|
+
gallivant review status <handle> show pinned policy, owned verdict and unresolved review evidence
|
|
94
|
+
gallivant review policy <handle> --mode external-required|first-party-required|both-required --reason "..."
|
|
95
|
+
explicitly replace policy and re-evaluate the current head
|
|
96
|
+
gallivant review dispute <handle> --finding <id> --reason "..."
|
|
97
|
+
record a dispute for a person; never accepts the author's own dispute
|
|
84
98
|
gallivant cancel <handle> stop further dispatch; request provider cancellation
|
|
85
99
|
gallivant say <handle> --note "..." [--interrupt]
|
|
86
100
|
send an update in the same session; --interrupt stops the active run first
|
|
@@ -94,15 +108,17 @@ const OPTIONS = {
|
|
|
94
108
|
send: { type: "boolean" }, probe: { type: "boolean" }, help: { type: "boolean", short: "h" }, version: { type: "boolean", short: "v" },
|
|
95
109
|
"no-server": { type: "boolean" }, "no-open": { type: "boolean" }, foreground: { type: "boolean" }, worktrees: { type: "string" },
|
|
96
110
|
"no-daemon": { type: "boolean" }, note: { type: "string" }, resnapshot: { type: "boolean" }, "fresh-session": { type: "boolean" }, interrupt: { type: "boolean" },
|
|
97
|
-
base: { type: "string" }, harness: { type: "string" }, session: { type: "string" }, worktree: { type: "string" }, dir: { type: "string" }, port: { type: "string" },
|
|
98
|
-
cancel: { type: "boolean" }, retry: { type: "boolean" },
|
|
111
|
+
base: { type: "string" }, after: { type: "string" }, harness: { type: "string" }, session: { type: "string" }, worktree: { type: "string" }, dir: { type: "string" }, port: { type: "string" },
|
|
112
|
+
cancel: { type: "boolean" }, retry: { type: "boolean" }, wait: { type: "boolean" },
|
|
99
113
|
run: { type: "boolean" }, stdin: { type: "boolean" }, repo: { type: "string" }, revision: { type: "string" },
|
|
100
114
|
"request-id": { type: "string" }, context: { type: "string", multiple: true },
|
|
101
|
-
"completion-target": { type: "string" },
|
|
115
|
+
"completion-target": { type: "string" }, delivery: { type: "string" }, route: { type: "string" },
|
|
102
116
|
answer: { type: "string", multiple: true }, verify: { type: "string", multiple: true },
|
|
103
117
|
since: { type: "string" }, until: { type: "string" }, "dry-run": { type: "boolean" },
|
|
104
118
|
mode: { type: "string" }, status: { type: "string" }, from: { type: "string" }, reason: { type: "string" },
|
|
119
|
+
"host-config": { type: "string" },
|
|
105
120
|
"key-path": { type: "string" }, "ssh-keygen": { type: "string" }, "learning-budget": { type: "string" },
|
|
121
|
+
finding: { type: "string" }, capability: { type: "string" },
|
|
106
122
|
};
|
|
107
123
|
|
|
108
124
|
async function dispatch(argv) {
|
|
@@ -111,9 +127,10 @@ async function dispatch(argv) {
|
|
|
111
127
|
const [cmd, arg] = positionals;
|
|
112
128
|
if (values.version) { console.log(VERSION); return 0; }
|
|
113
129
|
if (values.help || !cmd || cmd === "help") { console.log(HELP); return 0; }
|
|
130
|
+
if (values.after !== undefined && (cmd !== "chain" || !values.after.trim())) throw new Error("--after requires a registered PR URL or handle when queuing a new chain");
|
|
114
131
|
if (cmd === "init") {
|
|
115
132
|
if (arg) throw new Error("init takes no positional argument; run it from inside the target checkout");
|
|
116
|
-
const r = await init({ harness: values.harness, worktrees: values.worktrees });
|
|
133
|
+
const r = await init({ harness: values.harness, worktrees: values.worktrees, hostConfig: values["host-config"], base: values.base });
|
|
117
134
|
const report = renderDoctor(r.doctor);
|
|
118
135
|
console.log(renderInit(r, report));
|
|
119
136
|
// Doctor's exit code, so a script or an agent reading only the status knows whether setup still has open items.
|
|
@@ -165,7 +182,7 @@ async function dispatch(argv) {
|
|
|
165
182
|
}
|
|
166
183
|
}
|
|
167
184
|
const ctx = makeContext();
|
|
168
|
-
const mutating = ["start", "resume", "register", "remove", "recheck", "say", "cancel"].includes(cmd) || cmd === "chain" && (positionals.length > 2 || values.cancel || values.retry);
|
|
185
|
+
const mutating = ["start", "resume", "register", "remove", "recheck", "say", "cancel"].includes(cmd) || cmd === "review" && arg !== "status" || cmd === "chain" && (positionals.length > 2 || values.cancel || values.retry);
|
|
169
186
|
const owner = mutating ? acquireCliMutation(ctx.home) : null;
|
|
170
187
|
try {
|
|
171
188
|
if (cmd === "recheck" || cmd === "say" || cmd === "cancel") {
|
|
@@ -180,6 +197,11 @@ async function dispatch(argv) {
|
|
|
180
197
|
return 0;
|
|
181
198
|
}
|
|
182
199
|
switch (cmd) {
|
|
200
|
+
case "review": {
|
|
201
|
+
const result = await reviewCommand(ctx, { action: arg, handle: positionals[2], repo: values.repo, mode: values.mode, reason: values.reason, finding: values.finding, run: values.run, capability: values.capability, retry: values.retry, wait: values.wait });
|
|
202
|
+
console.log(JSON.stringify(result, null, 2));
|
|
203
|
+
return result.qualified === false ? 1 : 0;
|
|
204
|
+
}
|
|
183
205
|
case "chain": {
|
|
184
206
|
const paths = positionals.slice(2);
|
|
185
207
|
const operation = paths.length > 0 || values.retry;
|
|
@@ -188,7 +210,8 @@ async function dispatch(argv) {
|
|
|
188
210
|
if (values.retry && paths.length) throw new Error("--retry uses the existing plan; do not pass PRD paths");
|
|
189
211
|
if ((values.cancel || values.retry) && !arg) throw new Error("a chain name is required");
|
|
190
212
|
if (values.base && !paths.length) throw new Error("--base is set when queuing a new chain");
|
|
191
|
-
if (paths.length)
|
|
213
|
+
if (values.after && (!paths.length || values.retry || values.cancel)) throw new Error("--after is set only when queuing a new chain with ready PRD paths");
|
|
214
|
+
if (paths.length) await queueChain(ctx, { name: arg, paths, base: values.base, after: values.after });
|
|
192
215
|
else if (values.cancel) cancelChain(ctx, arg);
|
|
193
216
|
else if (values.retry) await retryChain(ctx, { name: arg, resnapshot: values.resnapshot });
|
|
194
217
|
console.log(renderChains(ctx, arg));
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { makeContext } from "../lib/context.mjs";
|
|
5
|
+
import { promote } from "../lib/promotion.mjs";
|
|
6
|
+
import { makePromotionTransfer } from "../lib/promotion_git.mjs";
|
|
7
|
+
|
|
8
|
+
const help = `Usage: npm run promote:bitbucket -- --github-pr URL --bitbucket-repo bitbucket.org/workspace/repo [--bitbucket-pr N] [--base main] [--branch name] [--out plan.json]
|
|
9
|
+
Default: read-only dry run. Inspect the plan, then add --apply --expected-plan DIGEST.
|
|
10
|
+
Merged sources use retained exact acceptance. If the reported base changed, --accepted-plan plan.json supplies an optional archived-base fallback.
|
|
11
|
+
No force push, merge or deployment. See docs/bitbucket-promotion.md.`;
|
|
12
|
+
let transfer;
|
|
13
|
+
try {
|
|
14
|
+
const { values } = parseArgs({ options: {
|
|
15
|
+
"github-pr": { type: "string" }, "bitbucket-repo": { type: "string" }, "bitbucket-pr": { type: "string" },
|
|
16
|
+
base: { type: "string" }, branch: { type: "string" }, out: { type: "string" }, "accepted-plan": { type: "string" },
|
|
17
|
+
apply: { type: "boolean", default: false }, "expected-plan": { type: "string" }, help: { type: "boolean", short: "h" },
|
|
18
|
+
} });
|
|
19
|
+
if (values.help) console.log(help);
|
|
20
|
+
else {
|
|
21
|
+
if (!values["github-pr"] || !values["bitbucket-repo"]) throw new Error(help);
|
|
22
|
+
if (values.apply && values.out) throw new Error("--out saves a dry-run plan only; omit it when applying");
|
|
23
|
+
let acceptedPlan;
|
|
24
|
+
if (values["accepted-plan"]) {
|
|
25
|
+
const text = await readFile(values["accepted-plan"], "utf8");
|
|
26
|
+
if (Buffer.byteLength(text) > 1_048_576) throw new Error("Accepted plan is oversized");
|
|
27
|
+
acceptedPlan = JSON.parse(text);
|
|
28
|
+
}
|
|
29
|
+
const ctx = makeContext({ readOnly: true });
|
|
30
|
+
transfer = await makePromotionTransfer(ctx);
|
|
31
|
+
const result = await promote(ctx, {
|
|
32
|
+
githubPr: values["github-pr"], bitbucketRepo: values["bitbucket-repo"],
|
|
33
|
+
...(values["bitbucket-pr"] !== undefined ? { bitbucketPr: Number(values["bitbucket-pr"]) } : {}),
|
|
34
|
+
branch: values.branch, base: values.base, acceptedPlan, apply: values.apply, expectedPlan: values["expected-plan"],
|
|
35
|
+
}, transfer);
|
|
36
|
+
if (values.out) await writeFile(values.out, `${JSON.stringify(result.plan, null, 2)}\n`, { flag: "wx", mode: 0o600 });
|
|
37
|
+
console.log(JSON.stringify(result, null, 2));
|
|
38
|
+
}
|
|
39
|
+
} catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
40
|
+
finally { await transfer?.close(); }
|
package/bin/task-pr.mjs
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { prOperation } from "../lib/pr_operation.mjs";
|
|
3
|
+
try {
|
|
4
|
+
const result = await prOperation(process.argv.slice(2));
|
|
5
|
+
console.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
|
|
6
|
+
} catch (error) {
|
|
7
|
+
console.error(error.message);
|
|
8
|
+
process.exitCode = 1;
|
|
9
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { makeBitbucketReviewProducer } from "../lib/hosts/bitbucket_review_producer.mjs";
|
|
5
|
+
try {
|
|
6
|
+
const { values, positionals } = parseArgs({ allowPositionals: true, options: Object.fromEntries(["config", "url", "input", "result"].map(key => [key, { type: "string" }])) });
|
|
7
|
+
if (positionals.length !== 1 || !["prepare", "publish"].includes(positionals[0]) || !values.config || !values.url) throw new Error("Use task-review prepare|publish --config <private-reviewer-config> --url <exact-PR-URL> [--input <prepared-json> --result <verdict-json>]");
|
|
8
|
+
const json = path => JSON.parse(readFileSync(path, "utf8"));
|
|
9
|
+
const producer = makeBitbucketReviewProducer({ config: json(values.config) });
|
|
10
|
+
const result = positionals[0] === "prepare" ? await producer.prepare(values.url) : await producer.publish(values.url, json(values.input), json(values.result));
|
|
11
|
+
console.log(JSON.stringify(result, null, 2));
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error(error.message);
|
|
14
|
+
process.exitCode = 1;
|
|
15
|
+
}
|
package/docs/README.md
CHANGED
|
@@ -17,9 +17,11 @@ requirements and checks.
|
|
|
17
17
|
| Install, configure access and complete a first task | [Getting started](install.md) |
|
|
18
18
|
| Ask an agent to inspect, install and verify setup | [Agent setup](agent-setup.md) |
|
|
19
19
|
| Describe work, answer questions, inspect a draft and authorize execution | [Natural-language tasks](tasks.md) |
|
|
20
|
+
| Choose the typed delivery profile, planner route and review a prepared task | [Typed delivery profiles](task-profiles.md) |
|
|
20
21
|
| Write a brief with acceptance criteria, source files and verification commands | [PRD contract](prd.md) |
|
|
21
22
|
| Find commands, monitor progress, message or resume a run | [Commands](commands.md) |
|
|
22
23
|
| Configure repositories, credentials, harnesses and execution limits | [Configuration](config.md) |
|
|
24
|
+
| Request and verify Codex reviews against the current commit | [Codex review procedure](codex-review.md) |
|
|
23
25
|
| Use the local dashboard and understand product boundaries | [Application](app.md) |
|
|
24
26
|
| Run coding work on an API-backed hosted environment | [OpenAI Agents API](openai-agents.md) |
|
|
25
27
|
| Understand the unavailable Codex Cloud dispatch and its required qualification | [Codex Cloud](codex-cloud.md) |
|
package/docs/architecture.md
CHANGED
|
@@ -38,6 +38,10 @@ lib/events.mjs pure rules: what counts as an event, trust, reviewer ve
|
|
|
38
38
|
lib/prompt.mjs start, register and fix prompts; quoting and caps
|
|
39
39
|
lib/harness.mjs capability registry plus local argv, session, outcome and sign-in adapters
|
|
40
40
|
lib/tasks.mjs natural-language intake, revisions, validation, readiness and explicit execution consent
|
|
41
|
+
lib/task_profile.mjs portable typed delivery profile, deterministic delivery screen and planner tag coverage (vendored by Agent Passport)
|
|
42
|
+
lib/task_model_route.mjs bounded text-only model route for drafting; pre-call allowance checks, usage-derived cost
|
|
43
|
+
lib/task_model_config.mjs operator configuration and absolute ceilings for that route
|
|
44
|
+
lib/task_repository_context.mjs read-only, bounded repository snapshot pinned to the prepared revision
|
|
41
45
|
lib/task_delivery.mjs authenticated host operation boundary; delegates to the same task service
|
|
42
46
|
lib/execution.mjs durable local/remote dispatch, ownership, observations and recovery
|
|
43
47
|
lib/execution_result.mjs owned hosted artifact verification and coordinator publication
|
|
@@ -72,10 +76,10 @@ templates/ passport, Claude sandbox settings, PRD, tracker/{tracke
|
|
|
72
76
|
|
|
73
77
|
## Design rules
|
|
74
78
|
|
|
75
|
-
|
|
79
|
+
Legacy review policies on intake tasks targeting `delivery_ready` stop automatic observation and fixing
|
|
76
80
|
at the qualified saved handoff. Explicit queued recheck/resume/message controls
|
|
77
81
|
reopen the workflow. `integrated` targets and legacy written-PRD runs retain
|
|
78
|
-
monitoring. `completionStopsAutomation` and the exported `coordinatorEntryActive`
|
|
82
|
+
monitoring. Version-1 review policies keep ready deliveries monitored so late trusted feedback and changed input can revoke readiness. `completionStopsAutomation` and the exported `coordinatorEntryActive`
|
|
79
83
|
keep core and host schedulers aligned. The review transitions below apply while
|
|
80
84
|
an entry is monitored; malformed or incomplete delivery proof cannot mark it
|
|
81
85
|
finished or suppress work.
|
|
@@ -215,7 +219,7 @@ For each registry entry that is not `done`:
|
|
|
215
219
|
6. **Dispatch.** Past the cap, stop with `iterations`. Otherwise recreate the checkout if it is gone, merge the base branch into the work branch, build the fix prompt, resume the session (or start one for a registered PR), move the cursor to the newest event and record the ids dispatched at that instant, record the run.
|
|
216
220
|
|
|
217
221
|
The merge is where a round of work starts from the current base rather than from wherever the base was when the branch was cut, and it is a merge rather than a rebase for three reasons that all live in this codebase. A rebase of a pushed branch can only move with a force-push, which the rules every session is given forbid. `reviewerVerdict` clears a head only when the commit the reviewer named is that head, so a rewrite makes every prior clearance describe a commit that no longer exists, and the reviewer's inline comments are anchored to those same commits. And step 7 reads a head the loop did not put there as a person's push, so a daemon force-push would look to the daemon itself like a stranger pushing. A merge moves the head forward, keeps every reviewed commit an ancestor, and needs no `--force`. An outstanding review therefore does not hold it up, which is the point: a rebase would have had to wait for the review, and that is a deadlock where the reviewer waits for a fix and the fix waits for the reviewer. The merge commit stays local until the session pushes, so nothing on GitHub moves, the reviewer clock is not reset and the round is still counted from the session's own push. It is recorded the moment it succeeds rather than by the save at the end of this step, because four things after it can return early and a merge nobody recorded is a change to a checkout with nothing to explain it. A conflict is aborted and the entry stops with `base-conflict` naming the base, because a half-merged checkout handed to a session as if it were clean is the worst thing this step could leave, and a conflict does not resolve on its own so retrying every tick would loop forever. Four things that would refuse identically on every poll stop the entry instead of looping: a checkout that cannot be recreated (`no-worktree`, with the cause as the event), a harness that refuses the configured `context_paths` (`context-paths`), no GitHub credential (`no-credential`) and no git identity on the machine (`no-identity`, whose event is the two `git config --global` commands that fix it). None of them resolves on its own, and the entry would otherwise redispatch the same feedback every poll with nothing notified. One invariant orders the rest of this step: **a spawned run must be recorded before anything else can fail.** Everything that can throw, the checkout and the ready-label removal included, happens before the spawn, and the registry write that owns the run comes immediately after it with no `await` in between. A process the registry does not know about is the worst state the dispatch can leave, because the settle step reads it as an unrelated leftover and the feedback that caused it is still new, so the same feedback spawns a second fixer when the first one exits. The label removal is the one write that can therefore hold a dispatch back, and holding it back is bounded: five consecutive failures stop the entry with `stuck`. `gh.removeLabel` throws by default and swallows only gh's own "label not found", which is what makes that bound reachable at all: the lenient behaviour was once the default, one of the two callers omitted the flag, and a GitHub that refused the write resolved as though the label had merely been absent.
|
|
218
|
-
7. **Ready.** First, a head nobody told the loop about. Both watching states go through the same check, in one place: `fixed_sha` follows the head, the reviewer clock resets to the push and the nudge becomes available again, because the reviewer has not seen this commit. A `ready` entry also leaves ready with the label off, since its verdict, its green checks and its accepted receipt all describe a commit that is not the head any more. Handling it for `ready` alone used to leave a `pr-open` entry judging the new head with the old head's age, so the same tick that noticed the push could stop it as `reviewer-silent` on a wait that expired before the commit existed, and the nudge already spent on the old head was never reissued. Then, for a `pr-open` entry: no events, no run, not a draft,
|
|
222
|
+
7. **Ready.** First, a head nobody told the loop about. Both watching states go through the same check, in one place: `fixed_sha` follows the head, the reviewer clock resets to the push and the nudge becomes available again, because the reviewer has not seen this commit. A `ready` entry also leaves ready with the label off, since its verdict, its green checks and its accepted receipt all describe a commit that is not the head any more. Handling it for `ready` alone used to leave a `pr-open` entry judging the new head with the old head's age, so the same tick that noticed the push could stop it as `reviewer-silent` on a wait that expired before the commit existed, and the nudge already spent on the old head was never reissued. Then, for a `pr-open` entry: no events, no run, not a draft, checks settled and the pinned review policy satisfied. Version-1 policies require explicit zero-check CI authorization for an empty check response; legacy no-review is disclosed as absence of review. Then the brief, compared with the operator's own copy: this is the only place the daemon reads their working tree, and it is here because this is where the contract is about to be used to certify something. A contract that moved stops the entry with `prd-changed` and the difference named, before the gate runs and without setting `gate_skipped`, which describes the other thing entirely (a PR that reached READY with the gate unrun, where this entry is not reaching ready at all). An edit outside the contract re-takes the snapshot, notifies nobody and carries on. Then the completion gate (`deliverable.mjs`): the PR body's receipt must attest every acceptance criterion of the PRD with evidence; a deny starts a receipt round. A receipt whose shape is wrong is one of those denies, not an exception. The output scan needs the whole diff, so a diff that cannot be read or reaches the 16 MiB scan cap is no decision at all. Large hosted payloads are sent in overlapping pieces within the API request limit, with full criteria and attestations on each; every piece must allow. Unreadable diffs and unavailable evaluators use the same bounded `entry.retry` mechanism as writes, with the cause visible in `status` and `why`; five consecutive failures stop as `needs-human`, without a fixer run or iteration. The label goes on before the registry says ready, so a label write that fails leaves the entry in `pr-open` to be retried rather than a ready PR carrying no label, and those retries are counted on the same `entry.retry` as every other declining write: a token that cannot write labels at all used to repeat the readiness judgement, the completion gate included, on every poll forever, while `status` said the entry was waiting on a reviewer who had already answered. Only then notify, once.
|
|
219
223
|
8. **Silence.** Reviewer configured and quiet since the last request: nudge once after `rereview_minutes`, stop with `reviewer-silent` after `stall_hours`. Draft conversion in step 3 starts a fresh reviewer window, so time spent as a draft does not count as reviewer silence.
|
|
220
224
|
|
|
221
225
|
The reviewer clock (`rereview.at`) is when the reviewer was last asked about the current head, and all three of those timers measure from it. Its best available value is the head commit's committer date, which is when the commit was made and not when it arrived on the branch, so it is floored at `rereview_minutes` ago: a commit made on Monday and pushed on Thursday must not stop the PR for a silence the reviewer was never given a chance to break. The rule is `reviewerClock` in `events.mjs`, pure and in one place because the tick is not its only writer: `reconcile` sets the same field on a recovered entry and goes through the same function, so a PR whose last round comment is older than `stall_hours` is not stopped `reviewer-silent` on the first tick after a crash recovery for silence that accrued while no daemon was running to ask.
|
|
@@ -263,3 +267,7 @@ a successful version probe alone cannot establish unattended support.
|
|
|
263
267
|
## Adding a reviewer
|
|
264
268
|
|
|
265
269
|
No code. Set `reviewer.login`, `reviewer.rereview_comment` and `reviewer.blocking_pattern` in config.
|
|
270
|
+
|
|
271
|
+
## Coordinator-owned first-party review
|
|
272
|
+
|
|
273
|
+
`review_policy.mjs` owns versioned task policy and input bindings; `review_evidence.mjs` captures complete snapshots and validates structured judgement. `first_party_review.mjs` advances keyed review jobs through the existing execution registry, in a separate session and allowance. The shared tick handles readiness, trusted external feedback and fix rounds. Review jobs do not create a second scheduler or publish independent approvals. Host adapters provide the same normalized evidence contract; only the existing GitHub boundary is implemented here. See [policy and recovery behavior](first-party-review.md), including native qualification and measured rollout limitations.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Review on GitHub, deliver through Bitbucket
|
|
2
|
+
|
|
3
|
+
`npm run promote:bitbucket` transfers an accepted GitHub PR's **original commits** to Bitbucket Cloud and creates or updates its Bitbucket PR. It uses Git transport, the existing host adapters and the same review/check policy as the coordinator. It does not copy files, create commits, force-push, merge, enable CI or deploy.
|
|
4
|
+
|
|
5
|
+
This is an operator command from a Gallivant source checkout. Install dependencies with `npm ci`. Configure both hosts using the [Git host guide](git-hosts.md); Git push access and Bitbucket API repository/PR access are both required. The command uses the existing scoped credentials, including a configured Bitbucket SSH key. Never put a token in a repository URL or a plan file.
|
|
6
|
+
|
|
7
|
+
## Before promotion
|
|
8
|
+
|
|
9
|
+
- Register and finish the GitHub run. It must retain an exact accepted completion and review binding under an explicit review policy. A label, green CI or the PR author's statement alone is insufficient.
|
|
10
|
+
- Stopped dispatch and queued/applying operator controls block promotion. Cancellation, messages, resume and recheck requests must be reconciled before accepted work can be delivered. These controls are checked again after plan validation and after a push; a cancellation during an already-running push can leave the branch updated, but prevents subsequent PR publication when observed.
|
|
11
|
+
- Only the exact remotely reviewed commit is transferred. Local checkout edits, untracked carried research inputs, environment files and session logs are neither read nor included. The transfer uses its own disposable bare Git repository, so an operator checkout may remain dirty or may already have been removed after merge.
|
|
12
|
+
- Specify the destination repository explicitly. No organization or repository mapping is inferred.
|
|
13
|
+
- For an existing Bitbucket PR, supply its number. This updates that PR rather than creating a duplicate.
|
|
14
|
+
- The Bitbucket base must have the same Git tree as the reviewed GitHub base. Its commit hash may differ because the two hosts merged earlier changes separately. Promote dependent work in order, after the preceding Bitbucket change has merged. Reconcile differing trees on GitHub and obtain review again.
|
|
15
|
+
- An existing destination branch must be an ancestor of the reviewed source head. Divergence is not resolved automatically.
|
|
16
|
+
|
|
17
|
+
GitHub Actions may remain disabled for a review-only mirror. Zero CI checks are accepted **only** when the source run's operator-approved review policy explicitly allows zero checks. Configured required checks still must exist and pass. Local test results and production release requirements are not replaced by that setting. Missing required external or first-party review blocks promotion.
|
|
18
|
+
|
|
19
|
+
## Preview, approve and apply
|
|
20
|
+
|
|
21
|
+
For an existing mapped PR:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm run promote:bitbucket -- \
|
|
25
|
+
--github-pr https://github.com/example/app/pull/1 \
|
|
26
|
+
--bitbucket-repo bitbucket.org/example/app \
|
|
27
|
+
--bitbucket-pr 93 \
|
|
28
|
+
--out accepted-promotion.json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The default is a remote read-only dry run. It fetches into a disposable temporary Git repository and prints the source and destination heads, bases, exact title/body, review policy, check count and a SHA-256 `digest`. `--out` saves that plan locally with mode 600 and refuses to overwrite an existing file. Keep private plans outside your repository; they contain private PR prose and the previous destination PR text, which may include its task metadata. Transport credentials are never added to the plan.
|
|
32
|
+
|
|
33
|
+
Inspect the complete plan, then rerun the same mapping with its digest:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
npm run promote:bitbucket -- \
|
|
37
|
+
--github-pr https://github.com/example/app/pull/1 \
|
|
38
|
+
--bitbucket-repo bitbucket.org/example/app \
|
|
39
|
+
--bitbucket-pr 93 \
|
|
40
|
+
--apply --expected-plan DIGEST_FROM_THE_DRY_RUN
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The digest is an exact human approval pin, not a substitute for fresh review evidence. Changed source code, review, checks, public text, destination branch, base or existing PR text requires another dry run. The command reads accepted state and operator controls from the run registry; it never writes that registry or starts another agent.
|
|
44
|
+
|
|
45
|
+
For a new PR, omit `--bitbucket-pr`. The default destination branch is `work/github-OWNER-REPO-pr-N`; `--branch` and `--base` select explicit alternatives. Subsequent runs reuse an existing PR only when its repository, branch/base and source-review provenance match. Otherwise supply an explicit PR mapping. Closed, cross-repository or duplicate targets are rejected.
|
|
46
|
+
|
|
47
|
+
The Bitbucket title and human-readable body come from the GitHub PR, with a source-review link and original commit SHA appended. Source task receipts and recovery/session metadata are removed because they do not authorize a new host/task. The source GitHub PR retains the complete evidence.
|
|
48
|
+
|
|
49
|
+
## After merging on GitHub
|
|
50
|
+
|
|
51
|
+
Run the same dry-run command after merge. When GitHub still supplies the original reviewed base, the coordinator's retained acceptance hash is checked directly against the original head, base, body, checks and policy. No pre-merge plan file is required. The accepted local record and fresh clear reviewer/check evidence remain mandatory; merging a PR before it was accepted does not manufacture that authority.
|
|
52
|
+
|
|
53
|
+
Optionally save `--out accepted-promotion.json` while the PR is accepted and still open. If GitHub later reports a different base, that archived plan can reconstruct the original base using:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
--accepted-plan /absolute/path/to/accepted-promotion.json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Use the new printed digest for `--apply --expected-plan ...`, keeping `--accepted-plan` on that command too when using the fallback. The reconstructed binding must exactly match the coordinator's retained acceptance hash. The plan cannot replace a missing local acceptance record or authorize changed code, body, checks or policy.
|
|
60
|
+
|
|
61
|
+
The transferred head is the original reviewed PR head even when GitHub used squash or merge commits. The GitHub merge/squash commit is never described as reviewed or silently substituted. Squash merges can make subsequent branch histories diverge between hosts; prefer history-preserving merges for this bridge. A future branch that is not a fast-forward of its mapped Bitbucket branch must be reconciled and reviewed, not force-pushed.
|
|
62
|
+
|
|
63
|
+
## Races and retries
|
|
64
|
+
|
|
65
|
+
Apply rechecks the plan before pushing. A native pre-push guard checks the server-advertised old branch SHA; Git's ordinary fast-forward update then compares that SHA on the server. No force or force-with-lease option is used.
|
|
66
|
+
|
|
67
|
+
There is no atomic transaction across GitHub and Bitbucket. The command rechecks evidence and destination after the push, before publishing the PR, and verifies the returned PR head/base. The publication adapter's final reconciliation read also enforces the approved PR absence or exact existing number and previous title/body. An observed concurrent creation or text edit stops publication. This adapter has no atomic conditional-update contract with Bitbucket: an edit between that final read and the write can still be overwritten without detection, so avoid concurrent PR editing during apply. If an operation fails, including a lost push response, the branch or PR may already have been updated; the error states that possibility. Inspect the target and rerun a dry run. Never respond with a force push. Bitbucket merge and deployment remain separate operator decisions and retain their normal release controls.
|
|
68
|
+
|
|
69
|
+
This bridge supports GitHub to Bitbucket Cloud only. It does not automatically schedule promotion, accept unregistered PRs, establish live production readiness, or migrate CI/secrets/deployment configuration.
|