ctxr-cli 0.24.0 → 0.26.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 +59 -4
- package/dist/commands/land.d.ts +50 -0
- package/dist/commands/land.d.ts.map +1 -0
- package/dist/commands/land.js +414 -0
- package/dist/commands/land.js.map +1 -0
- package/dist/commands/publish-check.d.ts +12 -0
- package/dist/commands/publish-check.d.ts.map +1 -1
- package/dist/commands/publish-check.js +38 -7
- package/dist/commands/publish-check.js.map +1 -1
- package/dist/commands/publish-urls.d.ts +7 -0
- package/dist/commands/publish-urls.d.ts.map +1 -1
- package/dist/commands/publish-urls.js +13 -5
- package/dist/commands/publish-urls.js.map +1 -1
- package/dist/commands/review-list.d.ts +34 -0
- package/dist/commands/review-list.d.ts.map +1 -0
- package/dist/commands/review-list.js +49 -0
- package/dist/commands/review-list.js.map +1 -0
- package/dist/commands/serve.d.ts +3 -6
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +81 -48
- package/dist/commands/serve.js.map +1 -1
- package/dist/config/schema.d.ts +40 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +39 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/core/approval/decision.d.ts +73 -0
- package/dist/core/approval/decision.d.ts.map +1 -0
- package/dist/core/approval/decision.js +166 -0
- package/dist/core/approval/decision.js.map +1 -0
- package/dist/core/approval/file-diff.d.ts +32 -0
- package/dist/core/approval/file-diff.d.ts.map +1 -0
- package/dist/core/approval/file-diff.js +83 -0
- package/dist/core/approval/file-diff.js.map +1 -0
- package/dist/core/approval/identity.d.ts +34 -0
- package/dist/core/approval/identity.d.ts.map +1 -0
- package/dist/core/approval/identity.js +105 -0
- package/dist/core/approval/identity.js.map +1 -0
- package/dist/core/approval/landing-record.d.ts +17 -0
- package/dist/core/approval/landing-record.d.ts.map +1 -0
- package/dist/core/approval/landing-record.js +30 -0
- package/dist/core/approval/landing-record.js.map +1 -0
- package/dist/core/approval/resolver.d.ts +15 -0
- package/dist/core/approval/resolver.d.ts.map +1 -0
- package/dist/core/approval/resolver.js +41 -0
- package/dist/core/approval/resolver.js.map +1 -0
- package/dist/core/approval/sessions.d.ts +36 -0
- package/dist/core/approval/sessions.d.ts.map +1 -0
- package/dist/core/approval/sessions.js +76 -0
- package/dist/core/approval/sessions.js.map +1 -0
- package/dist/core/browse/approve.d.ts +48 -0
- package/dist/core/browse/approve.d.ts.map +1 -0
- package/dist/core/browse/approve.js +284 -0
- package/dist/core/browse/approve.js.map +1 -0
- package/dist/core/browse/memo.d.ts +7 -0
- package/dist/core/browse/memo.d.ts.map +1 -0
- package/dist/core/browse/memo.js +56 -0
- package/dist/core/browse/memo.js.map +1 -0
- package/dist/core/browse/review.d.ts +22 -0
- package/dist/core/browse/review.d.ts.map +1 -0
- package/dist/core/browse/review.js +58 -0
- package/dist/core/browse/review.js.map +1 -0
- package/dist/core/browse/serving-state.d.ts +45 -0
- package/dist/core/browse/serving-state.d.ts.map +1 -0
- package/dist/core/browse/serving-state.js +108 -0
- package/dist/core/browse/serving-state.js.map +1 -0
- package/dist/core/checks/organize-checks.js +2 -2
- package/dist/core/checks/organize-checks.js.map +1 -1
- package/dist/core/errors.d.ts +24 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +55 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/forge/credentials.d.ts +69 -0
- package/dist/core/forge/credentials.d.ts.map +1 -0
- package/dist/core/forge/credentials.js +197 -0
- package/dist/core/forge/credentials.js.map +1 -0
- package/dist/core/forge/github.d.ts +73 -0
- package/dist/core/forge/github.d.ts.map +1 -0
- package/dist/core/forge/github.js +101 -0
- package/dist/core/forge/github.js.map +1 -0
- package/dist/core/git/exec.d.ts +15 -0
- package/dist/core/git/exec.d.ts.map +1 -1
- package/dist/core/git/exec.js +48 -1
- package/dist/core/git/exec.js.map +1 -1
- package/dist/core/graph/diff-block.d.ts.map +1 -1
- package/dist/core/graph/diff-block.js +13 -4
- package/dist/core/graph/diff-block.js.map +1 -1
- package/dist/core/graph/diff-document.d.ts +3 -2
- package/dist/core/graph/diff-document.d.ts.map +1 -1
- package/dist/core/graph/diff-document.js +2 -4
- package/dist/core/graph/diff-document.js.map +1 -1
- package/dist/core/publish/lifespan.d.ts +5 -5
- package/dist/core/publish/lifespan.d.ts.map +1 -1
- package/dist/core/publish/lifespan.js +13 -8
- package/dist/core/publish/lifespan.js.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +26 -0
- package/dist/run.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/templates/approve/approve-button.md +1 -0
- package/templates/approve/author-heading.md +1 -0
- package/templates/approve/author-note.md +1 -0
- package/templates/approve/comment-label.md +1 -0
- package/templates/approve/comment-required.md +1 -0
- package/templates/approve/commits-heading.md +1 -0
- package/templates/approve/decision-heading.md +1 -0
- package/templates/approve/decision-note.md +1 -0
- package/templates/approve/diff-binary.md +1 -0
- package/templates/approve/diff-empty.md +1 -0
- package/templates/approve/diff-heading.md +1 -0
- package/templates/approve/facts-heading.md +1 -0
- package/templates/approve/forge-unreachable.md +1 -0
- package/templates/approve/github-app-manifest.json +15 -0
- package/templates/approve/head-missing.md +1 -0
- package/templates/approve/index-empty.md +1 -0
- package/templates/approve/index-heading.md +1 -0
- package/templates/approve/index-intro.md +1 -0
- package/templates/approve/landed.md +1 -0
- package/templates/approve/moved-head.md +1 -0
- package/templates/approve/no-pull.md +1 -0
- package/templates/approve/not-open.md +1 -0
- package/templates/approve/pages-heading.md +1 -0
- package/templates/approve/pages-note.md +1 -0
- package/templates/approve/record-failed.md +1 -0
- package/templates/approve/request-changes-button.md +1 -0
- package/templates/approve/state-line.md +1 -0
- package/templates/graph/knowledge-changes/archived-item.md +1 -0
- package/templates/graph/knowledge-changes/dangling-item.md +1 -0
- package/templates/graph/knowledge-changes/reason-ambiguous.md +1 -0
- package/templates/graph/knowledge-changes/reason-not_found.md +1 -0
- package/templates/graph/review/moved-head.md +1 -0
- package/templates/land/resolver.md +12 -0
- package/templates/serve/style.css +63 -0
- package/templates/skills/ctxr-land.md +37 -31
- package/templates/skills/ctxr-publish.md +25 -1
- package/templates/skills/ctxr-second-opinion/run.mjs +28 -17
- package/templates/skills/ctxr-second-opinion.md +19 -14
- package/templates/skills/ctxr-session-lifecycle.md +9 -0
- package/templates/skills/ctxr-submit.md +4 -2
package/README.md
CHANGED
|
@@ -220,13 +220,15 @@ Each item is validated and applied independently, so one bad path refuses that i
|
|
|
220
220
|
|
|
221
221
|
**5. Submit.** `ctxr-submit`: re-scan git state (never replay a plan from an earlier snapshot), run the capture pass, stage surgically with `git add <paths>` — never `-A`, and derived cache paths never stage — then `ctxr doctor` over the whole store, not `--staged`: a session's job is to leave the *store* healthy, not merely to pass one commit's gate. Then commit, rename the generated branch to something meaningful before it reaches the forge, `git push -u origin <branch>`, and `gh pr create`. If `gh` has no reachable GitHub remote, the push still succeeds on its own and you get manual pull-request instructions instead of a retry loop.
|
|
222
222
|
|
|
223
|
-
**6. Land.** `ctxr-land`, after review: name the pull request explicitly rather than inferring it from whichever checkout you're standing in, read its state and
|
|
223
|
+
**6. Land.** `ctxr-land`, after review: name the pull request explicitly rather than inferring it from whichever checkout you're standing in, read its state with `gh pr view` (and its decision with `ctxr review list`, when the store [approves changes from a page](#approving-changes-from-a-page)), then wait for an explicit go — plan consent is not fire consent — unless a decider already approved it on the page. Then `ctxr land <pr>` (with `--confirm` when the consent is your go): it catches the branch up with the default branch, merges, re-reads the forge to confirm the merge rather than trusting an exit code, fast-forwards the canonical clone, and removes the worktree and its branch — nothing sweeps up afterward, so a worktree left here is left indefinitely.
|
|
224
224
|
|
|
225
225
|
`ctxr-session-lifecycle` covers what surrounds both halves: when to re-scan, the conflict playbook, and sequencing several pull requests.
|
|
226
226
|
|
|
227
|
-
### Why
|
|
227
|
+
### Why step 5 isn't a CLI command, and step 6 is
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
Submitting is ordinary `git` and `gh`. The CLI owns only what git cannot do — creating the worktree, applying validated note writes, running the health gates — and the sequencing, the confirmation gates, and the judgment live in the skills, where you can read and change them. That's why `ctxr session` has exactly three subcommands: `start`, `capture`, and `list`.
|
|
230
|
+
|
|
231
|
+
Landing was the same until a person could approve a change from a page. Then the thing that acts on an approval has no agent in it, and its one job — merge only a head someone approved — is a check that has to be enforced, not an instruction an agent follows. So `ctxr land` is code: it never resolves content, and the decision of *when* to land stays in `ctxr-land` and on the approval page.
|
|
230
232
|
|
|
231
233
|
Two mechanical backstops hold the line underneath all of it: the **pre-commit** hook runs `doctor --staged`, and the **pre-push** hook refuses a push to the default branch (`CONTEXTURE_ALLOW_DEFAULT_BRANCH_PUSH=1` is the emergency override, for emergencies). Both are version-controlled git hooks, so they hold whichever harness, or human, does the writing.
|
|
232
234
|
|
|
@@ -327,12 +329,63 @@ A page has one of two lifespans, read from where it is filed. A **permanent** pa
|
|
|
327
329
|
ctxr serve
|
|
328
330
|
```
|
|
329
331
|
|
|
330
|
-
Renders notes, catalog sections, the graph document, and published pages as cross-linked HTML. It binds loopback (`127.0.0.1`) by default. `--host` widens that, but note what it does not change: `serve` applies **no per-requester filtering at any bind address**. Widening it exposes the whole store to whatever can reach that address, so only do it behind a front end you've arranged yourself.
|
|
332
|
+
Renders notes, catalog sections, the graph document, and published pages as cross-linked HTML. It binds loopback (`127.0.0.1`) by default. `--host` widens that, but note what it does not change: `serve` applies **no per-requester filtering at any bind address**. Widening it exposes the whole store to whatever can reach that address, so only do it behind a front end you've arranged yourself. The one exception is the approval page below, which verifies who is asking before it shows or records anything.
|
|
331
333
|
|
|
332
334
|
A published page's navigation label follows its own declared `<title>`, falling back to its folder name when it declares none. A folder group is labelled with the declared name of the taxonomy layer whose path it matches — so a store's own words for its top-level folders reach the sidebar — and with the bare directory name otherwise; either way a group sits where its directory name sorts, not where its label does. The header's light/dark/system links choose a display theme, and the ☰ control shows and hides the navigation — both persist across pages via cookies, and neither requires client-side script. A published page itself is still served byte-verbatim, so neither the theme nor the navigation reaches into it; `ctxr publish new`'s scaffold instead follows the viewer's own system preference on its own.
|
|
333
335
|
|
|
334
336
|
The **Preview** area, directly below the published pages, holds the pages your open session worktrees are carrying. A page is previewable from the moment its files exist on disk — before any commit, before any push, before a PR is opened or merged — because `serve` reads the worktree rather than asking git or a forge anything. Pages group by the worktree holding them at `/preview/<worktree>/<page>/index.html`, and a preview is byte-identical to what `/publish/<page>/index.html` will serve once the page lands, so what you read is what ships. Both routes address files rather than directories, so a page's address always names its index file — which is the address `ctxr publish new` and `ctxr publish check` report, so you never have to work out which of the two applies. For a whole change set, `ctxr publish urls --staged` (or `--since <ref>`) reports every page added, modified, moved or removed with both addresses, which is what `ctxr-submit` puts in a pull request's **Pages** section; set `serve.previews: none` in `contexture.yaml` if previews never resolve for your reviewers. A session worktree carrying no page appears nowhere, and nothing else inside one — notes, catalog, graph, config — becomes reachable.
|
|
335
337
|
|
|
338
|
+
## Approving changes from a page
|
|
339
|
+
|
|
340
|
+
People who own a store's content often have no forge account. With change approval declared, `ctxr serve` gives them a page instead: `/approve/` lists every session with an open pull request, and each one's page shows the author's account of the change (its title, description and commit messages, labelled as the author's), the knowledge changes computed from the commits, the preview of every changed page, and every changed line. Two buttons — **Approve** and **Request changes** — record the decision on the pull request, as a review by the store's own forge identity naming the person who decided and the commit they saw. Nobody needs a GitHub account to leave an entry in the audit trail.
|
|
341
|
+
|
|
342
|
+
```yaml
|
|
343
|
+
serve:
|
|
344
|
+
base_url: https://ctx.example.com
|
|
345
|
+
change_approval:
|
|
346
|
+
identity: # the signed assertion your access proxy forwards
|
|
347
|
+
header: Cf-Access-Jwt-Assertion
|
|
348
|
+
issuer: https://<team>.cloudflareaccess.com
|
|
349
|
+
audience: <application aud tag>
|
|
350
|
+
jwks_url: https://<team>.cloudflareaccess.com/cdn-cgi/access/certs
|
|
351
|
+
deciders: ["@example.com"] # exact emails or @domain entries
|
|
352
|
+
recording_login: ctx-approvals # the forge identity decisions are recorded as
|
|
353
|
+
resolver: ["claude", "-p"] # optional: who resolves a conflict
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Identity comes from the proxy in front of `serve` (Cloudflare Access, Google IAP, oauth2-proxy): every `/approve/` request must carry its signed assertion, verified against the key set, issuer, audience and expiry, or it gets `401`. A request that reaches the port around the proxy carries none.
|
|
357
|
+
|
|
358
|
+
Then the system does the rest. Run `ctxr land --pending` on a schedule, with a forge token for the recording identity that can push and merge: it lands every pull request whose current head carries an approval — rebasing it onto the default branch when it is behind, merging, syncing the canonical clone, and removing the worktree. A clean rebase keeps the approval only when every commit's patch is unchanged, and the carry-over is recorded on the pull request. A rebase that conflicts goes to the configured resolver; its result is checked (clean, rebased, `ctxr doctor` passes), pushed, and handed back to the page for a new decision, because nobody has read an agent's merge of two sides. With no resolver, the change is marked blocked. Requested changes and blocked landings reach the next agent session through `ctxr review list`.
|
|
359
|
+
|
|
360
|
+
Outside the store, set up the recording identity (below) and, where your plan allows it, a branch protection rule requiring one approving review.
|
|
361
|
+
|
|
362
|
+
### Recording identity
|
|
363
|
+
|
|
364
|
+
Decisions and landings are recorded on the pull request by one forge identity, the `recording_login`. It must not be the identity your agents open pull requests with: the forge refuses an approval from a pull request's own author. There are two ways to provide it.
|
|
365
|
+
|
|
366
|
+
**A GitHub App (recommended).** No account to look after, no seat, and no token to rotate: the App's private key does not expire, and contexture mints a one-hour installation token from it whenever it needs one.
|
|
367
|
+
|
|
368
|
+
1. Create the App at <https://github.com/settings/apps/new> (or the organization's equivalent). Leave the webhook inactive and the callback URL empty, and request no user authorization. Under **Repository permissions** set **Contents**, **Issues** and **Pull requests** to *Read and write*; **Metadata** is read-only by default. Nothing else. [`templates/approve/github-app-manifest.json`](templates/approve/github-app-manifest.json) holds the same settings for GitHub's manifest flow.
|
|
369
|
+
2. Generate a private key on the App's page, and keep the `.pem` outside every repository, readable only by the user that runs `serve` (`chmod 600`).
|
|
370
|
+
3. Install the App on the store's repository, selecting only that repository.
|
|
371
|
+
4. Set `recording_login` to the App's slug followed by `[bot]`, e.g. `ctx-a-approvals[bot]`.
|
|
372
|
+
5. Give `ctxr serve` and the scheduled `ctxr land --pending` these, and nothing else needs a forge credential:
|
|
373
|
+
|
|
374
|
+
```sh
|
|
375
|
+
CONTEXTURE_GITHUB_APP_ID=<the App ID> # or its client ID
|
|
376
|
+
CONTEXTURE_GITHUB_APP_PRIVATE_KEY_FILE=/run/secrets/ctx-approvals.pem # or CONTEXTURE_GITHUB_APP_PRIVATE_KEY=<the PEM text>
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
contexture finds the installation from the store's github.com `origin` (or from `CONTEXTURE_GITHUB_REPOSITORY=owner/repo` when `origin` is not a github.com URL), and `ctxr land` fetches and pushes as the App too, handing git the token for that one command only. Every envelope reports `credential: github-app` and never the credential itself.
|
|
380
|
+
|
|
381
|
+
**One App per key holder.** Whoever holds the key can act on every repository the App is installed on, so share an App only across stores *you* run. To serve several of your stores under different owners, make the App installable on any account and install it on each owner with only that store's repository selected. A store someone else runs uses their own App.
|
|
382
|
+
|
|
383
|
+
**Where the key may live.** Only in the environments of `ctxr serve` and the land runner — never anywhere an agent runs commands, such as a harness gateway. An agent that can read the key can act on every store the App is installed on. If a key leaks, delete it on the App's page; that revokes it at once.
|
|
384
|
+
|
|
385
|
+
**A token for an account.** Without the App variables, `serve`, `land` and `review list` use `gh`'s own credential (`GH_TOKEN` or its credential file), so a dedicated machine account with a fine-grained token (Contents, Issues and Pull requests read and write on the store's repository) also works. It needs a rotation whenever the token expires.
|
|
386
|
+
|
|
387
|
+
GitHub Free cannot protect a private repository on a personal account, so such a store has no required-review rule; `ctxr land`'s consent check is what gates the merge either way. Where branch protection is available, require one approving review and leave **"Require approval of the most recent reviewable push"** off: the runner pushes a rebase and then approves the carry-over as the same identity.
|
|
388
|
+
|
|
336
389
|
## Keeping a store current
|
|
337
390
|
|
|
338
391
|
```sh
|
|
@@ -390,6 +443,8 @@ To pin a value against a future default change, declare it. Nothing rewrites wha
|
|
|
390
443
|
| `rollup gather \| write \| stale` | Entity synthesis: enumerate, write, find what's out of date |
|
|
391
444
|
| `publish gather \| new \| check` | Resolve a subject, scaffold a page, run the structural gates |
|
|
392
445
|
| `session start \| capture \| list` | Session worktrees, and applying an approved capture proposal |
|
|
446
|
+
| `land <pr> [--confirm] \| --pending` | Land a session pull request that carries consent: catch up, merge, confirm, sync, reclaim |
|
|
447
|
+
| `review list` | Each open session pull request's decision from the approval page |
|
|
393
448
|
| `adapters generate` | Regenerate harness outputs (`ctxr update` does this too) |
|
|
394
449
|
| `serve` | Read the store in a browser |
|
|
395
450
|
| `update` | Bring contexture-owned files up to the installed version |
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { CommandOutcome, CommandRequires } from '../core/command.js';
|
|
2
|
+
import type { RunEnv } from '../core/env.js';
|
|
3
|
+
import { type CredentialKind } from '../core/forge/credentials.js';
|
|
4
|
+
import { type ForgeClient } from '../core/forge/github.js';
|
|
5
|
+
import { type Store } from '../core/store.js';
|
|
6
|
+
export declare const requires: CommandRequires;
|
|
7
|
+
export interface LandFlags {
|
|
8
|
+
pr: number | undefined;
|
|
9
|
+
pending: boolean;
|
|
10
|
+
confirm: boolean;
|
|
11
|
+
}
|
|
12
|
+
export type LandOutcome = 'merged' | 'no-consent' | 'needs-reapproval' | 'blocked' | 'merge-refused' | 'lease-refused';
|
|
13
|
+
export type CatchUp = 'not-needed' | 'clean' | 'carried-over' | 'patches-changed' | 'conflict' | 'conflict-resolved';
|
|
14
|
+
export interface LandResult {
|
|
15
|
+
pull_request: number;
|
|
16
|
+
worktree: string | null;
|
|
17
|
+
outcome: LandOutcome;
|
|
18
|
+
/** Where the consent came from: a decision recorded from the approval page, or `--confirm`. */
|
|
19
|
+
consent: 'recorded' | 'confirm' | null;
|
|
20
|
+
/** The decision state, when consent was looked for and not found. */
|
|
21
|
+
state: string | null;
|
|
22
|
+
reason: string | null;
|
|
23
|
+
catch_up: CatchUp | null;
|
|
24
|
+
merged_commit: string | null;
|
|
25
|
+
canonical_sync: {
|
|
26
|
+
synced: boolean;
|
|
27
|
+
reason: string | null;
|
|
28
|
+
} | null;
|
|
29
|
+
reclaim: {
|
|
30
|
+
removed: boolean;
|
|
31
|
+
reason: string | null;
|
|
32
|
+
} | null;
|
|
33
|
+
/** Records posted on the pull request, and records this credential could not post (not the recording login). */
|
|
34
|
+
records_posted: string[];
|
|
35
|
+
records_reported_only: string[];
|
|
36
|
+
}
|
|
37
|
+
export interface LandData {
|
|
38
|
+
/** Which kind of forge credential the command used; never the credential. */
|
|
39
|
+
credential: CredentialKind;
|
|
40
|
+
results: LandResult[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* write-lifecycle spec ("Landing merges only a head that carries consent"):
|
|
44
|
+
* one pull request, or with `--pending` every session pull request whose
|
|
45
|
+
* current head carries an approval. design.md D1: this is code because the
|
|
46
|
+
* runner acting on a page approval has no agent in it, and the consent check is
|
|
47
|
+
* the one thing that must be enforced rather than instructed.
|
|
48
|
+
*/
|
|
49
|
+
export declare function execute(env: RunEnv, store: Store, flags: LandFlags, forge?: ForgeClient): Promise<CommandOutcome<LandData>>;
|
|
50
|
+
//# sourceMappingURL=land.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"land.d.ts","sourceRoot":"","sources":["../../src/commands/land.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAG7C,OAAO,EAAiC,KAAK,cAAc,EAAyB,MAAM,8BAA8B,CAAC;AACzH,OAAO,EAA0C,KAAK,WAAW,EAAsC,MAAM,yBAAyB,CAAC;AAGvI,OAAO,EAAa,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAKzD,eAAO,MAAM,QAAQ,EAAE,eAAuC,CAAC;AAE/D,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,YAAY,GAAG,kBAAkB,GAAG,SAAS,GAAG,eAAe,GAAG,eAAe,CAAC;AACvH,MAAM,MAAM,OAAO,GAAG,YAAY,GAAG,OAAO,GAAG,cAAc,GAAG,iBAAiB,GAAG,UAAU,GAAG,mBAAmB,CAAC;AAErH,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,WAAW,CAAC;IACrB,+FAA+F;IAC/F,OAAO,EAAE,UAAU,GAAG,SAAS,GAAG,IAAI,CAAC;IACvC,qEAAqE;IACrE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAClE,OAAO,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5D,gHAAgH;IAChH,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,qBAAqB,EAAE,MAAM,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,QAAQ;IACvB,6EAA6E;IAC7E,UAAU,EAAE,cAAc,CAAC;IAC3B,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAuWD;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAkDjI"}
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { mkdtemp, rm, stat } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { decisionBody, landingBody } from '../core/approval/decision.js';
|
|
5
|
+
import { writeLandingRecord } from '../core/approval/landing-record.js';
|
|
6
|
+
import { resolverPrompt, runResolver } from '../core/approval/resolver.js';
|
|
7
|
+
import { carriesOver, forgeRead, reviewStateOf, sessionPulls, sessionWorktrees } from '../core/approval/sessions.js';
|
|
8
|
+
import { ChangeApprovalNotDeclaredError, LandFlagsError, LandRefusedError } from '../core/errors.js';
|
|
9
|
+
import { ExitCode } from '../core/exit-codes.js';
|
|
10
|
+
import { credentialsFor, ghCredentials } from '../core/forge/credentials.js';
|
|
11
|
+
import { createGhForgeClient, ForgeRequestError } from '../core/forge/github.js';
|
|
12
|
+
import { storePrefix } from '../core/graph/tree.js';
|
|
13
|
+
import { addDetachedWorktree, mainWorktreePath, pruneWorktrees, removeWorktree } from '../core/git/worktree.js';
|
|
14
|
+
import { openStore } from '../core/store.js';
|
|
15
|
+
import * as catalogBuildCommand from './catalog-build.js';
|
|
16
|
+
import * as doctorCommand from './doctor.js';
|
|
17
|
+
import * as graphBuildCommand from './graph-build.js';
|
|
18
|
+
export const requires = { store: 'required' };
|
|
19
|
+
function emptyResult(number, worktree) {
|
|
20
|
+
return {
|
|
21
|
+
pull_request: number,
|
|
22
|
+
worktree,
|
|
23
|
+
outcome: 'merged',
|
|
24
|
+
consent: null,
|
|
25
|
+
state: null,
|
|
26
|
+
reason: null,
|
|
27
|
+
catch_up: null,
|
|
28
|
+
merged_commit: null,
|
|
29
|
+
canonical_sync: null,
|
|
30
|
+
reclaim: null,
|
|
31
|
+
records_posted: [],
|
|
32
|
+
records_reported_only: [],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function git(ctx, args, cwd, allowFailure = false) {
|
|
36
|
+
return ctx.env.git.run(args, { cwd, allowFailure });
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* write-lifecycle spec ("In App mode, landing's git transport authenticates as
|
|
40
|
+
* the App"; design.md D4): a fetch or push against `origin`. With an App, the
|
|
41
|
+
* host's credential helpers are cleared for this one command and replaced by
|
|
42
|
+
* one that answers with the installation token, read from this process's
|
|
43
|
+
* environment only — never a remote URL, a config file, or the command line.
|
|
44
|
+
* Otherwise git uses the credential it already has.
|
|
45
|
+
*/
|
|
46
|
+
const TOKEN_HELPER = '!f() { echo username=x-access-token; echo "password=$CTXR_GIT_TOKEN"; }; f';
|
|
47
|
+
async function remoteGit(ctx, args, cwd, allowFailure = false) {
|
|
48
|
+
const token = await ctx.credentials.gitToken();
|
|
49
|
+
if (token === null)
|
|
50
|
+
return git(ctx, args, cwd, allowFailure);
|
|
51
|
+
return ctx.env.git.run(['-c', 'credential.helper=', '-c', `credential.helper=${TOKEN_HELPER}`, ...args], {
|
|
52
|
+
cwd,
|
|
53
|
+
allowFailure,
|
|
54
|
+
env: { ...ctx.env.env, CTXR_GIT_TOKEN: token, GIT_TERMINAL_PROMPT: '0' },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** write-lifecycle spec: a record goes on the pull request only under the recording login; otherwise only into the envelope. */
|
|
58
|
+
async function record(ctx, result, label, post) {
|
|
59
|
+
if (!ctx.canRecord) {
|
|
60
|
+
result.records_reported_only.push(label);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
await forgeRead(post);
|
|
64
|
+
result.records_posted.push(label);
|
|
65
|
+
}
|
|
66
|
+
async function recordLanding(ctx, result, number, state, reason, head, sentence) {
|
|
67
|
+
const at = ctx.env.now().toISOString();
|
|
68
|
+
await record(ctx, result, `${state}:${reason}`, () => ctx.forge.createComment(number, landingBody({ state, reason, head, at }, sentence)));
|
|
69
|
+
}
|
|
70
|
+
async function isClean(ctx, cwd) {
|
|
71
|
+
return (await git(ctx, ['status', '--porcelain'], cwd)).stdout.trim() === '';
|
|
72
|
+
}
|
|
73
|
+
async function rebaseInProgress(ctx, cwd) {
|
|
74
|
+
for (const name of ['rebase-merge', 'rebase-apply']) {
|
|
75
|
+
const gitPath = (await git(ctx, ['rev-parse', '--git-path', name], cwd)).stdout.trim();
|
|
76
|
+
try {
|
|
77
|
+
await stat(path.resolve(cwd, gitPath));
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// absent
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Where the catch-up rebase runs. The session's worktree when it is there, clean
|
|
88
|
+
* and at the head being landed — so its branch moves with the rebase. Otherwise
|
|
89
|
+
* a temporary detached worktree at that head, so a session with uncommitted work,
|
|
90
|
+
* or one that is absent, is never touched.
|
|
91
|
+
*/
|
|
92
|
+
async function checkoutFor(ctx, session, head) {
|
|
93
|
+
if (session !== undefined && (await isClean(ctx, session.path))) {
|
|
94
|
+
const at = (await git(ctx, ['rev-parse', 'HEAD'], session.path)).stdout.trim();
|
|
95
|
+
if (at === head)
|
|
96
|
+
return { cwd: session.path, dispose: async () => undefined };
|
|
97
|
+
}
|
|
98
|
+
const dir = await mkdtemp(path.join(tmpdir(), 'ctxr-land-'));
|
|
99
|
+
await rm(dir, { recursive: true, force: true });
|
|
100
|
+
await addDetachedWorktree(ctx.env.git, ctx.canonical, dir, head);
|
|
101
|
+
return {
|
|
102
|
+
cwd: dir,
|
|
103
|
+
dispose: async () => {
|
|
104
|
+
await removeWorktree(ctx.env.git, ctx.canonical, dir);
|
|
105
|
+
await rm(dir, { recursive: true, force: true });
|
|
106
|
+
await pruneWorktrees(ctx.env.git, ctx.canonical);
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** Runs the configured resolver in `cwd` with the playbook as its last argument; resolves to its exit code. */
|
|
111
|
+
async function runResolverFor(ctx, cwd, pull) {
|
|
112
|
+
return runResolver({
|
|
113
|
+
argv: ctx.resolver,
|
|
114
|
+
prompt: resolverPrompt(pull.number, ctx.base, ctx.defaultBranch),
|
|
115
|
+
cwd,
|
|
116
|
+
env: { ...ctx.env.env, CTXR_LAND_PR: String(pull.number), CTXR_LAND_BASE: ctx.base },
|
|
117
|
+
output: ctx.env.io.stderr,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/** write-lifecycle spec: the resolver's result is accepted only clean, rebased, containing the base, and passing doctor. */
|
|
121
|
+
async function acceptResolution(ctx, cwd) {
|
|
122
|
+
if (await rebaseInProgress(ctx, cwd))
|
|
123
|
+
return 'rebase-in-progress';
|
|
124
|
+
if (!(await isClean(ctx, cwd)))
|
|
125
|
+
return 'uncommitted-work';
|
|
126
|
+
const contains = await git(ctx, ['merge-base', '--is-ancestor', ctx.base, 'HEAD'], cwd, true);
|
|
127
|
+
if (contains.exitCode !== 0)
|
|
128
|
+
return 'base-not-contained';
|
|
129
|
+
try {
|
|
130
|
+
const doctorStore = await openStore(ctx.env, { root: path.join(cwd, ctx.prefix) });
|
|
131
|
+
// Derived artifacts are per checkout and never committed; a fresh checkout has none for doctor's store checks to read.
|
|
132
|
+
await catalogBuildCommand.execute(doctorStore);
|
|
133
|
+
await graphBuildCommand.execute(doctorStore);
|
|
134
|
+
const doctor = await doctorCommand.execute(ctx.env, doctorStore);
|
|
135
|
+
if (doctor.exitCode === ExitCode.Ok)
|
|
136
|
+
return null;
|
|
137
|
+
for (const finding of doctor.findings.filter((f) => f.severity === 'error'))
|
|
138
|
+
ctx.env.io.stderr.write(`land: doctor: ${finding.code}: ${finding.message}\n`);
|
|
139
|
+
return 'doctor-failed';
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
// A store doctor cannot even open is a result that fails doctor.
|
|
143
|
+
ctx.env.io.stderr.write(`land: doctor: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
144
|
+
return 'doctor-failed';
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/** A leased push of `cwd`'s HEAD to the pull request's branch; false when the lease was refused because the branch moved. */
|
|
148
|
+
async function leasedPush(ctx, cwd, branch, expected) {
|
|
149
|
+
const pushed = await remoteGit(ctx, ['push', `--force-with-lease=refs/heads/${branch}:${expected}`, 'origin', `HEAD:refs/heads/${branch}`], cwd, true);
|
|
150
|
+
return pushed.exitCode === 0;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* write-lifecycle spec ("An approval carries across a catch-up rebase only when
|
|
154
|
+
* the patches are unchanged"; "A conflict is resolved by the configured resolver
|
|
155
|
+
* and returns for re-approval"). Returns the head to merge, or null when this
|
|
156
|
+
* landing stops here (with `result` saying why).
|
|
157
|
+
*/
|
|
158
|
+
async function catchUp(ctx, result, pull, session, approvedHead) {
|
|
159
|
+
const behind = await git(ctx, ['merge-base', '--is-ancestor', ctx.base, pull.headSha], ctx.canonical, true);
|
|
160
|
+
if (behind.exitCode === 0) {
|
|
161
|
+
result.catch_up = 'not-needed';
|
|
162
|
+
return pull.headSha;
|
|
163
|
+
}
|
|
164
|
+
const checkout = await checkoutFor(ctx, session, pull.headSha);
|
|
165
|
+
try {
|
|
166
|
+
const rebase = await git(ctx, ['-c', 'core.editor=true', 'rebase', '--quiet', ctx.base], checkout.cwd, true);
|
|
167
|
+
if (rebase.exitCode === 0) {
|
|
168
|
+
const rebased = (await git(ctx, ['rev-parse', 'HEAD'], checkout.cwd)).stdout.trim();
|
|
169
|
+
const same = approvedHead === null || (await carriesOver(ctx.env.git, checkout.cwd, ctx.base, approvedHead, rebased));
|
|
170
|
+
if (!(await leasedPush(ctx, checkout.cwd, pull.headRef, pull.headSha))) {
|
|
171
|
+
result.outcome = 'lease-refused';
|
|
172
|
+
result.reason = `the branch moved after ${pull.headSha.slice(0, 7)} was read, so the leased push was refused`;
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
if (!same) {
|
|
176
|
+
result.catch_up = 'patches-changed';
|
|
177
|
+
result.outcome = 'needs-reapproval';
|
|
178
|
+
result.reason = 'the rebase changed a patch, so the approval does not carry over';
|
|
179
|
+
await recordLanding(ctx, result, pull.number, 'needs-reapproval', 'patches-changed', rebased, `Rebased onto ${ctx.base}, and the rebase changed a patch, so the approval of ${approvedHead.slice(0, 7)} does not carry over. This change needs a new decision.`);
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
result.catch_up = approvedHead === null ? 'clean' : 'carried-over';
|
|
183
|
+
if (approvedHead !== null && ctx.recordingLogin !== null) {
|
|
184
|
+
const decision = (await forgeRead(() => reviewStateOf(ctx.env.git, checkout.cwd, ctx.base, ctx.forge, ctx.recordingLogin, pull))).decision;
|
|
185
|
+
await record(ctx, result, 'carry-over', () => ctx.forge.createReview(pull.number, {
|
|
186
|
+
event: 'APPROVE',
|
|
187
|
+
commitId: rebased,
|
|
188
|
+
body: decisionBody({ ...decision, decision: 'approve', head: rebased, carriedFrom: decision.carriedFrom ?? approvedHead, comment: null }),
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
return rebased;
|
|
192
|
+
}
|
|
193
|
+
await git(ctx, ['rebase', '--abort'], checkout.cwd, true);
|
|
194
|
+
if (ctx.resolver === null) {
|
|
195
|
+
result.catch_up = 'conflict';
|
|
196
|
+
result.outcome = 'blocked';
|
|
197
|
+
result.reason = 'conflict';
|
|
198
|
+
await recordLanding(ctx, result, pull.number, 'blocked', 'conflict', pull.headSha, `Rebasing onto ${ctx.base} conflicts, and this store names no conflict resolver. A person or an agent session resolves it.`);
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
const exit = await runResolverFor(ctx, checkout.cwd, pull);
|
|
202
|
+
const refusal = exit === 0 ? await acceptResolution(ctx, checkout.cwd) : `resolver-exit-${exit}`;
|
|
203
|
+
if (refusal !== null) {
|
|
204
|
+
// Leave the branch as the forge has it: put the checkout back where it was.
|
|
205
|
+
if (await rebaseInProgress(ctx, checkout.cwd))
|
|
206
|
+
await git(ctx, ['rebase', '--abort'], checkout.cwd, true);
|
|
207
|
+
await git(ctx, ['reset', '--hard', '--quiet', pull.headSha], checkout.cwd, true);
|
|
208
|
+
result.catch_up = 'conflict';
|
|
209
|
+
result.outcome = 'blocked';
|
|
210
|
+
result.reason = refusal;
|
|
211
|
+
await recordLanding(ctx, result, pull.number, 'blocked', refusal, pull.headSha, `Rebasing onto ${ctx.base} conflicts, and the resolver's result was not accepted (${refusal}). Nothing was pushed.`);
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
const resolved = (await git(ctx, ['rev-parse', 'HEAD'], checkout.cwd)).stdout.trim();
|
|
215
|
+
if (!(await leasedPush(ctx, checkout.cwd, pull.headRef, pull.headSha))) {
|
|
216
|
+
result.outcome = 'lease-refused';
|
|
217
|
+
result.reason = `the branch moved after ${pull.headSha.slice(0, 7)} was read, so the leased push was refused`;
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
result.catch_up = 'conflict-resolved';
|
|
221
|
+
result.outcome = 'needs-reapproval';
|
|
222
|
+
result.reason = 'conflict-resolved';
|
|
223
|
+
await recordLanding(ctx, result, pull.number, 'needs-reapproval', 'conflict-resolved', resolved, `Rebasing onto ${ctx.base} conflicted. The configured resolver resolved it as ${resolved.slice(0, 7)}, which nobody has approved yet. This change needs a new decision.`);
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
await checkout.dispose();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/** write-lifecycle spec: after the forge reports merged — sync the canonical clone, reclaim the worktree, record the landing. */
|
|
231
|
+
async function afterMerge(ctx, result, pull, session) {
|
|
232
|
+
await remoteGit(ctx, ['fetch', '--quiet', 'origin', ctx.defaultBranch], ctx.canonical, true);
|
|
233
|
+
const onBranch = (await git(ctx, ['symbolic-ref', '--quiet', '--short', 'HEAD'], ctx.canonical, true)).stdout.trim();
|
|
234
|
+
if (onBranch !== ctx.defaultBranch) {
|
|
235
|
+
result.canonical_sync = { synced: false, reason: `the canonical clone is on "${onBranch || 'a detached HEAD'}", not ${ctx.defaultBranch}` };
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
const ff = await git(ctx, ['merge', '--ff-only', '--quiet', `origin/${ctx.defaultBranch}`], ctx.canonical, true);
|
|
239
|
+
result.canonical_sync = ff.exitCode === 0 ? { synced: true, reason: null } : { synced: false, reason: `the canonical clone has diverged from origin/${ctx.defaultBranch}` };
|
|
240
|
+
}
|
|
241
|
+
if (session === undefined) {
|
|
242
|
+
result.reclaim = { removed: false, reason: 'no session worktree for this branch' };
|
|
243
|
+
}
|
|
244
|
+
else if (path.resolve(ctx.store.root).startsWith(path.resolve(session.path) + path.sep) || path.resolve(ctx.store.root) === path.resolve(session.path)) {
|
|
245
|
+
result.reclaim = { removed: false, reason: 'this command is running inside that worktree' };
|
|
246
|
+
}
|
|
247
|
+
else if (!(await isClean(ctx, session.path))) {
|
|
248
|
+
result.reclaim = { removed: false, reason: 'the worktree has uncommitted work' };
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
const removed = await git(ctx, ['worktree', 'remove', session.path], ctx.canonical, true);
|
|
252
|
+
if (removed.exitCode !== 0) {
|
|
253
|
+
result.reclaim = { removed: false, reason: removed.stderr.trim() };
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
// The forge said merged; a squashed branch never looks merged to git's ancestry test.
|
|
257
|
+
await git(ctx, ['branch', '-D', session.branch], ctx.canonical, true);
|
|
258
|
+
result.reclaim = { removed: true, reason: null };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (session !== undefined) {
|
|
262
|
+
await writeLandingRecord(ctx.canonicalStoreRoot, {
|
|
263
|
+
worktree: session.name,
|
|
264
|
+
branch: session.branch,
|
|
265
|
+
pull_request: pull.number,
|
|
266
|
+
merged_commit: result.merged_commit,
|
|
267
|
+
landed_at: ctx.env.now().toISOString(),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
async function landOne(ctx, number, confirm) {
|
|
272
|
+
let pull = await forgeRead(() => ctx.forge.pull(number));
|
|
273
|
+
if (pull.headRef === ctx.defaultBranch) {
|
|
274
|
+
throw new LandRefusedError('default_branch', `Pull request #${number}'s head is the default branch "${ctx.defaultBranch}", which is never landed.`, { pull_request: number });
|
|
275
|
+
}
|
|
276
|
+
const sessions = await sessionWorktrees(ctx.env.git, ctx.canonical, ctx.store.config);
|
|
277
|
+
const session = sessions.find((s) => s.branch === pull.headRef);
|
|
278
|
+
const result = emptyResult(number, session?.name ?? null);
|
|
279
|
+
// A retry after the merge went through performs only what remains.
|
|
280
|
+
if (pull.merged) {
|
|
281
|
+
result.merged_commit = pull.mergeCommitSha;
|
|
282
|
+
await afterMerge(ctx, result, pull, session);
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
285
|
+
if (pull.state !== 'open') {
|
|
286
|
+
throw new LandRefusedError('not_open', `Pull request #${number} is closed without being merged.`, { pull_request: number });
|
|
287
|
+
}
|
|
288
|
+
await remoteGit(ctx, ['fetch', '--quiet', 'origin', ctx.defaultBranch, pull.headRef], ctx.canonical, true);
|
|
289
|
+
let approvedHead = null;
|
|
290
|
+
if (confirm) {
|
|
291
|
+
result.consent = 'confirm';
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
const state = await forgeRead(() => reviewStateOf(ctx.env.git, ctx.canonical, ctx.base, ctx.forge, ctx.recordingLogin, pull));
|
|
295
|
+
if (state.state !== 'approved' || state.decision === null) {
|
|
296
|
+
result.outcome = 'no-consent';
|
|
297
|
+
result.state = state.state;
|
|
298
|
+
result.reason = `pull request #${number} is ${state.state}; landing needs an approval of its current head, or --confirm`;
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
result.consent = 'recorded';
|
|
302
|
+
approvedHead = state.decision.head;
|
|
303
|
+
}
|
|
304
|
+
const head = await catchUp(ctx, result, pull, session, approvedHead);
|
|
305
|
+
if (head === null)
|
|
306
|
+
return result;
|
|
307
|
+
try {
|
|
308
|
+
await ctx.forge.merge(number, { method: ctx.mergeMethod, sha: head });
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
if (!(err instanceof ForgeRequestError))
|
|
312
|
+
throw err;
|
|
313
|
+
}
|
|
314
|
+
// Never claim a merge from the merge call alone.
|
|
315
|
+
pull = await forgeRead(() => ctx.forge.pull(number));
|
|
316
|
+
if (!pull.merged) {
|
|
317
|
+
result.outcome = 'merge-refused';
|
|
318
|
+
result.reason = 'the forge did not merge the pull request; it may be waiting on checks or branch protection, and a later run retries';
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
result.outcome = 'merged';
|
|
322
|
+
result.merged_commit = pull.mergeCommitSha;
|
|
323
|
+
await afterMerge(ctx, result, pull, session);
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
function findingFor(result) {
|
|
327
|
+
if (result.outcome === 'merged')
|
|
328
|
+
return null;
|
|
329
|
+
return {
|
|
330
|
+
code: `land.${result.outcome.replace(/-/g, '_')}`,
|
|
331
|
+
severity: result.outcome === 'needs-reapproval' ? 'warning' : 'error',
|
|
332
|
+
message: `#${result.pull_request}: ${result.reason ?? result.outcome}`,
|
|
333
|
+
subject: String(result.pull_request),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function summaryLine(r) {
|
|
337
|
+
const parts = [`#${r.pull_request} ${r.outcome}`];
|
|
338
|
+
if (r.catch_up !== null)
|
|
339
|
+
parts.push(`catch-up ${r.catch_up}`);
|
|
340
|
+
if (r.merged_commit !== null)
|
|
341
|
+
parts.push(`merged as ${r.merged_commit.slice(0, 7)}`);
|
|
342
|
+
if (r.canonical_sync !== null)
|
|
343
|
+
parts.push(r.canonical_sync.synced ? 'canonical clone synced' : `canonical clone not synced: ${r.canonical_sync.reason}`);
|
|
344
|
+
if (r.reclaim !== null)
|
|
345
|
+
parts.push(r.reclaim.removed ? 'worktree removed' : `worktree kept: ${r.reclaim.reason}`);
|
|
346
|
+
if (r.reason !== null && r.outcome !== 'merged')
|
|
347
|
+
parts.push(r.reason);
|
|
348
|
+
if (r.records_reported_only.length > 0)
|
|
349
|
+
parts.push(`not posted (credential is not the recording login): ${r.records_reported_only.join(', ')}`);
|
|
350
|
+
return parts.join(' | ');
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* write-lifecycle spec ("Landing merges only a head that carries consent"):
|
|
354
|
+
* one pull request, or with `--pending` every session pull request whose
|
|
355
|
+
* current head carries an approval. design.md D1: this is code because the
|
|
356
|
+
* runner acting on a page approval has no agent in it, and the consent check is
|
|
357
|
+
* the one thing that must be enforced rather than instructed.
|
|
358
|
+
*/
|
|
359
|
+
export async function execute(env, store, flags, forge) {
|
|
360
|
+
if (flags.pending && flags.confirm)
|
|
361
|
+
throw new LandFlagsError('--pending lands only recorded approvals, so it does not take --confirm.');
|
|
362
|
+
if (flags.pending === (flags.pr !== undefined))
|
|
363
|
+
throw new LandFlagsError('Name one pull request number, or pass --pending — not both, and not neither.');
|
|
364
|
+
if (flags.pr !== undefined && (!Number.isInteger(flags.pr) || flags.pr <= 0))
|
|
365
|
+
throw new LandFlagsError('The pull request must be a positive whole number.');
|
|
366
|
+
const approval = store.config.change_approval;
|
|
367
|
+
if (flags.pending && approval === undefined)
|
|
368
|
+
throw new ChangeApprovalNotDeclaredError('ctxr land --pending');
|
|
369
|
+
const credentials = forge === undefined ? await credentialsFor(env, store.root) : ghCredentials(env.env);
|
|
370
|
+
const client = forge ?? createGhForgeClient(store.root, credentials);
|
|
371
|
+
const canonical = await mainWorktreePath(env.git, store.root);
|
|
372
|
+
const prefix = await storePrefix(env.git, store.root);
|
|
373
|
+
const defaultBranch = store.config.git.default_branch;
|
|
374
|
+
const recordingLogin = approval?.recording_login ?? null;
|
|
375
|
+
const ctx = {
|
|
376
|
+
env,
|
|
377
|
+
store,
|
|
378
|
+
forge: client,
|
|
379
|
+
credentials,
|
|
380
|
+
canonical,
|
|
381
|
+
canonicalStoreRoot: path.join(canonical, prefix),
|
|
382
|
+
prefix,
|
|
383
|
+
defaultBranch,
|
|
384
|
+
base: `origin/${defaultBranch}`,
|
|
385
|
+
recordingLogin,
|
|
386
|
+
canRecord: recordingLogin !== null && (await forgeRead(() => client.login())) === recordingLogin,
|
|
387
|
+
mergeMethod: approval?.merge_method ?? 'squash',
|
|
388
|
+
resolver: approval?.resolver ?? null,
|
|
389
|
+
};
|
|
390
|
+
const results = [];
|
|
391
|
+
if (flags.pending) {
|
|
392
|
+
await remoteGit(ctx, ['fetch', '--quiet', 'origin'], canonical, true);
|
|
393
|
+
const approved = (await sessionPulls(env.git, canonical, store.config, client, recordingLogin)).filter((p) => p.review.state === 'approved');
|
|
394
|
+
// Each is re-read inside landOne, immediately before its merge, so a decision changed meanwhile is honoured.
|
|
395
|
+
for (const p of approved)
|
|
396
|
+
results.push(await landOne(ctx, p.pull.number, false));
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
if (!flags.confirm && approval === undefined)
|
|
400
|
+
throw new ChangeApprovalNotDeclaredError('ctxr land without --confirm');
|
|
401
|
+
results.push(await landOne(ctx, flags.pr, flags.confirm));
|
|
402
|
+
}
|
|
403
|
+
const findings = results.map(findingFor).filter((f) => f !== null);
|
|
404
|
+
const failed = flags.pending ? results.some((r) => r.outcome !== 'merged' && r.outcome !== 'needs-reapproval') : results.some((r) => r.outcome !== 'merged');
|
|
405
|
+
return {
|
|
406
|
+
exitCode: failed ? ExitCode.CheckFailed : ExitCode.Ok,
|
|
407
|
+
data: { credential: client.credential, results },
|
|
408
|
+
findings,
|
|
409
|
+
humanSummary: results.length === 0 ? 'No approved session pull request to land.' : results.map(summaryLine).join('\n'),
|
|
410
|
+
storeRoot: store.root,
|
|
411
|
+
schemaVersion: store.config.schema_version,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
//# sourceMappingURL=land.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"land.js","sourceRoot":"","sources":["../../src/commands/land.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,6 +10,12 @@ export interface PublishCheckFailure {
|
|
|
10
10
|
check: string;
|
|
11
11
|
message: string;
|
|
12
12
|
}
|
|
13
|
+
/** file-pdfs-beside-their-page: a PDF at a page folder's root, a rendering of the page. */
|
|
14
|
+
export interface PublishCheckPdf {
|
|
15
|
+
/** Store-relative. */
|
|
16
|
+
path: string;
|
|
17
|
+
served_at: ServedAt | null;
|
|
18
|
+
}
|
|
13
19
|
export interface PublishCheckData {
|
|
14
20
|
path: string;
|
|
15
21
|
passed: boolean;
|
|
@@ -25,6 +31,12 @@ export interface PublishCheckData {
|
|
|
25
31
|
* file outside the configured publish path, which is not a page.
|
|
26
32
|
*/
|
|
27
33
|
lifespan: PageLifespan | null;
|
|
34
|
+
/**
|
|
35
|
+
* file-pdfs-beside-their-page D5: every PDF at the page folder's root,
|
|
36
|
+
* sorted by file name, with the address it is served at — so the author can
|
|
37
|
+
* hand on the PDF's link. A PDF in a subfolder is an input and is not listed.
|
|
38
|
+
*/
|
|
39
|
+
pdfs: PublishCheckPdf[];
|
|
28
40
|
}
|
|
29
41
|
/**
|
|
30
42
|
* publish spec: the mechanized half of the page-checklist — every check
|