@sjawhar/opencode-legion-envoy 3.9.0 → 3.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/server.js
CHANGED
|
@@ -15150,7 +15150,14 @@ class DispatchClient {
|
|
|
15150
15150
|
return this.#json("POST", ["api", "v1", "projects", project, "architecture-source", "sync"]);
|
|
15151
15151
|
}
|
|
15152
15152
|
async getArchitectureSource(project) {
|
|
15153
|
-
|
|
15153
|
+
try {
|
|
15154
|
+
return await this.#json("GET", ["api", "v1", "projects", project, "architecture-source"]);
|
|
15155
|
+
} catch (error48) {
|
|
15156
|
+
if (error48 instanceof DispatchServiceError && error48.status === 404 && error48.code === "SOURCE_NOT_FOUND") {
|
|
15157
|
+
return null;
|
|
15158
|
+
}
|
|
15159
|
+
throw error48;
|
|
15160
|
+
}
|
|
15154
15161
|
}
|
|
15155
15162
|
async resolveAsk(id, input) {
|
|
15156
15163
|
return this.#json("POST", ["api", "v1", "asks", id, "resolve"], input);
|
|
@@ -16041,13 +16048,23 @@ async function resolveOwnerArguments(tool, input, cwd, env, exec, serverUrl, pro
|
|
|
16041
16048
|
const issue2 = `${repo2}#${legionIssue}`;
|
|
16042
16049
|
return { args: { ...args, issue: issue2 }, ref, owner: { kind: "issue", issue: issue2 } };
|
|
16043
16050
|
}
|
|
16044
|
-
function artifactByReference(artifacts, artifactReference, owner) {
|
|
16045
|
-
const
|
|
16046
|
-
if (
|
|
16047
|
-
return
|
|
16051
|
+
function artifactByReference(artifacts, artifactReference, owner, canonical) {
|
|
16052
|
+
const bySlug = artifacts.find((candidate) => candidate.slug === artifactReference);
|
|
16053
|
+
if (canonical && bySlug !== undefined)
|
|
16054
|
+
return bySlug;
|
|
16055
|
+
const byId = artifacts.find((candidate) => candidate.id === artifactReference);
|
|
16056
|
+
if (byId !== undefined)
|
|
16057
|
+
return byId;
|
|
16048
16058
|
const byName = artifacts.filter((candidate) => candidate.name === artifactReference);
|
|
16059
|
+
if (bySlug !== undefined) {
|
|
16060
|
+
const named = byName.filter((candidate) => candidate.id !== bySlug.id && candidate.kind === "doc");
|
|
16061
|
+
if (named.length > 0) {
|
|
16062
|
+
throw new Error(documentReferenceProblem(artifactReference, [bySlug, ...named], owner, "id"));
|
|
16063
|
+
}
|
|
16064
|
+
return bySlug;
|
|
16065
|
+
}
|
|
16049
16066
|
if (byName.length > 1) {
|
|
16050
|
-
throw new Error(documentReferenceProblem(artifactReference, byName, owner,
|
|
16067
|
+
throw new Error(documentReferenceProblem(artifactReference, byName, owner, "slug"));
|
|
16051
16068
|
}
|
|
16052
16069
|
const [artifact] = byName;
|
|
16053
16070
|
if (artifact === undefined) {
|
|
@@ -16055,32 +16072,31 @@ function artifactByReference(artifacts, artifactReference, owner) {
|
|
|
16055
16072
|
}
|
|
16056
16073
|
return artifact;
|
|
16057
16074
|
}
|
|
16058
|
-
async function resolveArtifact(client, owner, artifactReference) {
|
|
16075
|
+
async function resolveArtifact(client, owner, artifactReference, { canonical = false } = {}) {
|
|
16059
16076
|
if (owner.kind === "project") {
|
|
16060
16077
|
if (artifactReference === undefined) {
|
|
16061
16078
|
throw new Error("artifact is required for a project document");
|
|
16062
16079
|
}
|
|
16063
|
-
|
|
16064
|
-
return {
|
|
16065
|
-
owner,
|
|
16066
|
-
artifact: await client.getProjectArtifact(owner.project, artifactReference)
|
|
16067
|
-
};
|
|
16068
|
-
} catch (error48) {
|
|
16080
|
+
const routed = await client.getProjectArtifact(owner.project, artifactReference).catch((error48) => {
|
|
16069
16081
|
if (!(error48 instanceof DispatchServiceError) || error48.status !== 404)
|
|
16070
16082
|
throw error48;
|
|
16071
|
-
|
|
16072
|
-
|
|
16073
|
-
|
|
16074
|
-
|
|
16075
|
-
|
|
16076
|
-
|
|
16083
|
+
return;
|
|
16084
|
+
});
|
|
16085
|
+
if (canonical && routed !== undefined)
|
|
16086
|
+
return { owner, artifact: routed };
|
|
16087
|
+
const unlinked = await client.listProjectArtifacts(owner.project, true);
|
|
16088
|
+
const artifacts = routed === undefined ? unlinked : [routed, ...unlinked.filter((candidate) => candidate.id !== routed.id)];
|
|
16089
|
+
return {
|
|
16090
|
+
owner,
|
|
16091
|
+
artifact: artifactByReference(artifacts, artifactReference, "project", canonical)
|
|
16092
|
+
};
|
|
16077
16093
|
}
|
|
16078
16094
|
const issue2 = await client.getIssue(owner.issue);
|
|
16079
16095
|
let artifact;
|
|
16080
16096
|
if (artifactReference === undefined || artifactReference === "spec") {
|
|
16081
16097
|
artifact = issue2.artifacts.find((candidate) => candidate.primary || candidate.id === issue2.primary_artifact_id);
|
|
16082
16098
|
} else {
|
|
16083
|
-
artifact = artifactByReference(issue2.artifacts, artifactReference, "issue");
|
|
16099
|
+
artifact = artifactByReference(issue2.artifacts, artifactReference, "issue", canonical);
|
|
16084
16100
|
}
|
|
16085
16101
|
if (!artifact) {
|
|
16086
16102
|
throw new Error(documentReferenceProblem(artifactReference ?? "spec", issue2.artifacts, "issue"));
|
|
@@ -16088,10 +16104,10 @@ async function resolveArtifact(client, owner, artifactReference) {
|
|
|
16088
16104
|
return { owner, issue: issue2, artifact };
|
|
16089
16105
|
}
|
|
16090
16106
|
var documentHintLimit = 8;
|
|
16091
|
-
function documentReferenceProblem(reference, documents, owner,
|
|
16092
|
-
const hints = documents.slice(0, documentHintLimit).map((document) => `${document.slug} (${document.name})`);
|
|
16107
|
+
function documentReferenceProblem(reference, documents, owner, use) {
|
|
16108
|
+
const hints = documents.slice(0, documentHintLimit).map((document) => use === "id" ? `${document.id} (${document.slug}, ${document.name})` : `${document.slug} (${document.name})`);
|
|
16093
16109
|
const list = hints.length === 0 ? "none" : hints.join(", ");
|
|
16094
|
-
return
|
|
16110
|
+
return use === undefined ? `document "${reference}" not found by slug; this ${owner}'s documents: ${list}` : `"${reference}" names ${documents.length} documents on this ${owner}; use ${use === "id" ? "the id" : "a slug"}: ${list}`;
|
|
16095
16111
|
}
|
|
16096
16112
|
var askHintLimit = 8;
|
|
16097
16113
|
function askIDInputProblem(asks, scope) {
|
|
@@ -16481,6 +16497,10 @@ async function executeDispatchTool(input) {
|
|
|
16481
16497
|
const args = parsed.success ? parsed.data : ownerArguments.args;
|
|
16482
16498
|
const actor = toolActor(await resolveOrigin(env, exec, input.cwd), input);
|
|
16483
16499
|
const client = dispatchClient();
|
|
16500
|
+
const refDocument = ownerArguments.ref?.kind === "artifact" ? ownerArguments.ref.id : ownerArguments.ref?.artifact;
|
|
16501
|
+
const resolveDocument = (documentOwner2, reference) => resolveArtifact(client, documentOwner2, reference, {
|
|
16502
|
+
canonical: reference !== undefined && reference === refDocument
|
|
16503
|
+
});
|
|
16484
16504
|
const owner = ownerArguments.owner?.kind === "issue" ? {
|
|
16485
16505
|
kind: "issue",
|
|
16486
16506
|
issue: await resolveExistingIssue(client, ownerArguments.owner.issue)
|
|
@@ -16771,7 +16791,7 @@ async function executeDispatchTool(input) {
|
|
|
16771
16791
|
const issueKey = ref.owner.issue;
|
|
16772
16792
|
id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), () => client.getComments(issueKey));
|
|
16773
16793
|
} else if (ref !== null) {
|
|
16774
|
-
const artifact = (await resolveArtifact(client, ref.owner, ref.artifact)).artifact;
|
|
16794
|
+
const artifact = (await resolveArtifact(client, ref.owner, ref.artifact, { canonical: true })).artifact;
|
|
16775
16795
|
document = artifact;
|
|
16776
16796
|
id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), () => client.getArtifactComments(artifact.id));
|
|
16777
16797
|
}
|
|
@@ -16783,7 +16803,7 @@ async function executeDispatchTool(input) {
|
|
|
16783
16803
|
const anchorArgs = asObject(args.anchor);
|
|
16784
16804
|
const owner2 = documentOwner();
|
|
16785
16805
|
const artifactReference = optionalString(args, "artifact") ?? (anchorArgs === null ? undefined : optionalString(anchorArgs, "artifact"));
|
|
16786
|
-
const resolved = owner2.kind === "project" || artifactReference === undefined ? owner2.kind === "project" ? await
|
|
16806
|
+
const resolved = owner2.kind === "project" || artifactReference === undefined ? owner2.kind === "project" ? await resolveDocument(owner2, artifactReference) : undefined : await resolveDocument(owner2, artifactReference);
|
|
16787
16807
|
const options = args.options;
|
|
16788
16808
|
const multiple = optionalBoolean(args, "multiple");
|
|
16789
16809
|
const urgency = askUrgency(args);
|
|
@@ -16833,7 +16853,7 @@ ${followsAsk(askOwner)}`,
|
|
|
16833
16853
|
case "dispatch_comment": {
|
|
16834
16854
|
const artifactReference = optionalString(args, "artifact");
|
|
16835
16855
|
const owner2 = documentOwner();
|
|
16836
|
-
const resolved = owner2.kind === "project" || artifactReference === undefined ? owner2.kind === "project" ? await
|
|
16856
|
+
const resolved = owner2.kind === "project" || artifactReference === undefined ? owner2.kind === "project" ? await resolveDocument(owner2, artifactReference) : undefined : await resolveDocument(owner2, artifactReference);
|
|
16837
16857
|
const anchored = resolved ? anchor(resolved.artifact, args) : undefined;
|
|
16838
16858
|
const replyTo = optionalString(args, "reply_to");
|
|
16839
16859
|
const replyToAskReference = optionalString(args, "reply_to_ask");
|
|
@@ -16886,7 +16906,7 @@ ${followsAsk(askOwner)}`,
|
|
|
16886
16906
|
};
|
|
16887
16907
|
}
|
|
16888
16908
|
case "dispatch_suggest": {
|
|
16889
|
-
const resolved = await
|
|
16909
|
+
const resolved = await resolveDocument(documentOwner(), stringArg(args, "artifact"));
|
|
16890
16910
|
const anchored = anchor(resolved.artifact, args);
|
|
16891
16911
|
if (anchored === undefined)
|
|
16892
16912
|
throw new Error("quote is required");
|
|
@@ -16941,7 +16961,7 @@ ${followsAsk(askOwner)}`,
|
|
|
16941
16961
|
};
|
|
16942
16962
|
}
|
|
16943
16963
|
case "dispatch_doc_edit": {
|
|
16944
|
-
const resolved = await
|
|
16964
|
+
const resolved = await resolveDocument(documentOwner(), stringArg(args, "artifact"));
|
|
16945
16965
|
const ops = args.ops;
|
|
16946
16966
|
const summary = optionalString(args, "summary");
|
|
16947
16967
|
const { precondition: rawPrecondition } = args;
|
|
@@ -16983,7 +17003,7 @@ ${followsAsk(askOwner)}`,
|
|
|
16983
17003
|
}
|
|
16984
17004
|
case "dispatch_doc_read": {
|
|
16985
17005
|
const artifactReference = optionalString(args, "artifact") ?? (ownerArguments.ref?.kind === "spec" || ownerArguments.ref?.kind === "artifact" ? ownerArguments.ref.id : undefined);
|
|
16986
|
-
const resolved = await
|
|
17006
|
+
const resolved = await resolveDocument(documentOwner(), artifactReference);
|
|
16987
17007
|
const version2 = optionalNumber(args, "version") ?? ownerArguments.ref?.version;
|
|
16988
17008
|
const documentPromise = client.docRead(resolved.artifact.id, version2);
|
|
16989
17009
|
const marksPromise = openArtifactMarks(client, resolved);
|
|
@@ -17012,7 +17032,7 @@ ${trailer.join(`
|
|
|
17012
17032
|
}
|
|
17013
17033
|
case "dispatch_request_approval": {
|
|
17014
17034
|
const artifactReference = optionalString(args, "artifact") ?? (ownerArguments.ref?.kind === "spec" || ownerArguments.ref?.kind === "artifact" ? ownerArguments.ref.id : undefined);
|
|
17015
|
-
const resolved = await
|
|
17035
|
+
const resolved = await resolveDocument(documentOwner(), artifactReference);
|
|
17016
17036
|
const result = await client.requestApproval(resolved.artifact.id, { actor });
|
|
17017
17037
|
if (result.ask === null) {
|
|
17018
17038
|
return {
|
|
@@ -17090,7 +17110,7 @@ ${trailer.join(`
|
|
|
17090
17110
|
case "dispatch_read": {
|
|
17091
17111
|
if (ownerArguments.ref?.kind === "ask") {
|
|
17092
17112
|
const ref = ownerArguments.ref;
|
|
17093
|
-
const id = await resolveIdPrefix(input.tool, "ask", ref.id, refOwnerName(ref), async () => ref.owner.kind === "issue" ? client.listIssueAsks(ref.owner.issue) : client.getArtifactAsks((await
|
|
17113
|
+
const id = await resolveIdPrefix(input.tool, "ask", ref.id, refOwnerName(ref), async () => ref.owner.kind === "issue" ? client.listIssueAsks(ref.owner.issue) : client.getArtifactAsks((await resolveDocument(ref.owner, ref.artifact)).artifact.id, "all"));
|
|
17094
17114
|
const askRead = await client.getAsk(id);
|
|
17095
17115
|
const askRef = refTarget(ref, "ask", id);
|
|
17096
17116
|
return {
|
|
@@ -17100,7 +17120,7 @@ ${trailer.join(`
|
|
|
17100
17120
|
}
|
|
17101
17121
|
if (ownerArguments.ref?.kind === "comment") {
|
|
17102
17122
|
const ref = ownerArguments.ref;
|
|
17103
|
-
const id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), async () => ref.owner.kind === "issue" ? client.getComments(ref.owner.issue) : client.getArtifactComments((await
|
|
17123
|
+
const id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), async () => ref.owner.kind === "issue" ? client.getComments(ref.owner.issue) : client.getArtifactComments((await resolveDocument(ref.owner, ref.artifact)).artifact.id));
|
|
17104
17124
|
const comment = await client.getComment(id);
|
|
17105
17125
|
const commentRef = refTarget(ref, "comment", id);
|
|
17106
17126
|
return {
|
|
@@ -17120,7 +17140,7 @@ ${trailer.join(`
|
|
|
17120
17140
|
};
|
|
17121
17141
|
}
|
|
17122
17142
|
if (documentOwner().kind === "project") {
|
|
17123
|
-
const resolved = await
|
|
17143
|
+
const resolved = await resolveDocument(documentOwner(), stringArg(args, "artifact"));
|
|
17124
17144
|
const documentRef = dispatchDocumentRef(resolved.artifact.project, resolved.artifact.slug);
|
|
17125
17145
|
return {
|
|
17126
17146
|
text: [
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -603,7 +603,8 @@ dispatch_doc_read({ issue?, project?, artifact?, version?, ref? })
|
|
|
603
603
|
```
|
|
604
604
|
It returns live or versioned markdown with open marks. A live read ends with a document token; `issue` with an
|
|
605
605
|
omitted `artifact` reads the issue specification; a project needs `artifact`; and a
|
|
606
|
-
`dispatch://PROJECT/artifact/<document-ref>` ref supplies both, where `document-ref` is the id
|
|
606
|
+
`dispatch://PROJECT/artifact/<document-ref>` ref supplies both, where `document-ref` is the slug (an id or a
|
|
607
|
+
filename resolves when no document has that slug).
|
|
607
608
|
|
|
608
609
|
Editing one is [Editing a document](references/document-edits.md): the shape of `dispatch_doc_edit`,
|
|
609
610
|
how to quote the text you mean, one `replace` per paragraph, preconditions against a stale edit, and
|
|
@@ -741,7 +742,9 @@ Exactly one of `path` and `content` is required. It returns issue or project-doc
|
|
|
741
742
|
Uploading the same `name` creates its next version — so uploading `spec.md` **replaces the issue's own specification**
|
|
742
743
|
with your text. Never do that: the spec is edited in place with `dispatch_doc_edit` (see [Editing a document](references/document-edits.md)). Address an existing
|
|
743
744
|
artifact by the slug shown in the upload result or by its filename, and a project document by its artifact id, slug, or filename; the
|
|
744
|
-
slug also arrives on `artifact.created` events.
|
|
745
|
+
slug also arrives on `artifact.created` events. Dispatch suffixes a slug two documents would share, so one document's
|
|
746
|
+
filename can be another's slug (`plan v2` takes `plan-v2`, then a document named `plan-v2` takes `plan-v2-2`): a bare
|
|
747
|
+
`artifact` that names both is refused with each one's id, while a `dispatch://` reference's document part is always the slug.
|
|
745
748
|
|
|
746
749
|
**Where a deliverable goes.** Text the human must read to decide — a draft message, a proposal,
|
|
747
750
|
a summary — goes in the spec as a section: the spec is the one document they open. A separate
|
|
@@ -255,16 +255,20 @@ Preserve this order exactly:
|
|
|
255
255
|
with options for its outcomes, and the issue waits for it.
|
|
256
256
|
|
|
257
257
|
What returns the tree to review: a changed diff — a commit above the approved head that
|
|
258
|
-
touches anything outside `docs/solutions/`, or a
|
|
258
|
+
touches anything outside `docs/solutions/`, or a conflict-resolution merge whose fingerprint
|
|
259
259
|
(`skill://legion-worker`'s unchanged-diff check) differs from the approved head's. What does not: retro's
|
|
260
|
-
`docs/solutions/` commit, and a
|
|
261
|
-
is unchanged. For that
|
|
262
|
-
`legion-worker`'s
|
|
263
|
-
|
|
264
|
-
|
|
260
|
+
`docs/solutions/` commit, and a merge forced by a GitHub-reported conflict whose fingerprint
|
|
261
|
+
is unchanged. For that merge the order is: the implementer merges the bookmark forward with the
|
|
262
|
+
destination (`legion-worker`'s forward-merge procedure — `jj new legion/<KEY> <destination>`,
|
|
263
|
+
never a rebase, since a rebase rewrites every descendant of the chain's fork point, including
|
|
264
|
+
another tree's branch stacked on it), pushes it with the ordinary push procedure (a genuine
|
|
265
|
+
fast-forward), and posts the before/after fingerprints; the tester re-runs the bare gates only;
|
|
266
|
+
the reviewer confirms and approves the new head by SHA (or continues its round if it had not
|
|
267
|
+
approved); the merger republishes READY. Retro does not re-run. This merge happens only when
|
|
268
|
+
GitHub reports `CONFLICTING`
|
|
265
269
|
(`legion gh -- pr view <n> --json mergeable,mergeStateStatus`); read that on every end-game
|
|
266
270
|
wake — `pr-ready`, `pr-review`, `phase-complete`, `catchup-overseer` — because a `CONFLICTING`
|
|
267
|
-
PR gets no CI and no wake announces it, and send the implementer to
|
|
271
|
+
PR gets no CI and no wake announces it, and send the implementer to resolve it the moment you see
|
|
268
272
|
it. Do not let the merger publish `READY` for an obsolete approval.
|
|
269
273
|
|
|
270
274
|
If a worker reports that `legion threads resolve` exited 1 naming a review thread GitHub refused
|
|
@@ -280,7 +280,7 @@ Verified the implementer's proof by <re-running its command | driving the same s
|
|
|
280
280
|
**Fast-follow:** <one named cleanup item and where it will land>, or "none".
|
|
281
281
|
|
|
282
282
|
**Chain:** stacked on <base bookmark> frozen at <sha> / not stacked.
|
|
283
|
-
**Retarget:** Retargeting a pull request to a new base does not re-run Tests; after a retarget,
|
|
283
|
+
**Retarget:** Retargeting a pull request to a new base does not re-run Tests; after a retarget, merge the bookmark onto the new base (`jj new legion/<KEY> <new base> -m "<message>"`) and push with `legion-worker`'s ordinary push procedure — a genuine fast-forward, never the procedure for rewritten commits — the new head runs Tests against the new merge result — and cite that run in the PR body.
|
|
284
284
|
```
|
|
285
285
|
|
|
286
286
|
**A proof** is the changed behaviour exercised on the surface a user reaches it through, recorded
|
|
@@ -362,9 +362,30 @@ this proof.
|
|
|
362
362
|
the fingerprint at the current tip; after pushing the rebased branch, record it at the new
|
|
363
363
|
tip; post one PR comment (Legion footer):
|
|
364
364
|
`rebase <old-tip-sha> → <new-tip-sha>; fingerprint <before> → <after>; unchanged|changed`.
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
365
|
+
Every issue workspace is a `jj workspace` of the same shared repository and operation log, and
|
|
366
|
+
jj always rebases every descendant of any commit it rewrites — a revset naming the root of your
|
|
367
|
+
own chain and rewriting it in place also rewrites whatever another tree has stacked on that root,
|
|
368
|
+
whichever selector chose it (`-s`, `-b`, and `-r` all rewrite descendants; `-r` only re-parents
|
|
369
|
+
them to fill the hole, which is worse). This is what happened in LEGION-118: one issue's own
|
|
370
|
+
conflict step moved a second issue's twelve commits and its bookmark onto a conflicted copy.
|
|
371
|
+
Resolve the conflict with a forward merge instead of a rewrite — merge the branch's own
|
|
372
|
+
bookmark with the destination in one new commit, so nothing existing is rewritten and nothing
|
|
373
|
+
built on your prior commits, in this tree or another, ever moves:
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
jj -R "$LEGION_WORKSPACE" new legion/<KEY> main@origin -m "merge: resolve conflict against main@origin"
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Merge from the bookmark, never from `@`: a handoff split leaves `@` an empty, undescribed
|
|
380
|
+
commit above the described one the bookmark already names, and `jj git push` refuses to push
|
|
381
|
+
any commit without a description — merging from `@` drags that undescribed commit into the
|
|
382
|
+
ancestry and the push fails (`Won't push commit … since it has no description`); the bookmark
|
|
383
|
+
is always on a described, already-pushed commit. If the merge conflicts, resolve it in that
|
|
384
|
+
one commit — edit the markers directly; there is nothing to squash, since the merge is the
|
|
385
|
+
only new commit. Then `jj -R "$LEGION_WORKSPACE" new` to move off it, and push with the one
|
|
386
|
+
push procedure (*Every role pushes its own commits*, below): the merge descends from both the
|
|
387
|
+
bookmark's old position and the destination, so it is a genuine fast-forward and *Rewriting
|
|
388
|
+
pushed commits* never applies — nothing was rewritten, so there is no tip to record first.
|
|
368
389
|
- **No deferrals.** Sami, 2026-09-11, verbatim: "My rule is no deferrals." The `Fast-follow:`
|
|
369
390
|
field names naming, duplication, or wording cleanup only; anything that changes behaviour,
|
|
370
391
|
hides an error, or breaks a gate lands in this PR.
|
|
@@ -586,8 +607,8 @@ remote branch sideways onto your commit and drops theirs (jj 0.45.1:
|
|
|
586
607
|
`bookmark: legion/K [move sideways from <theirs> to <yours>]`). A clone that has not seen the other
|
|
587
608
|
push is refused by jj itself (`unexpectedly moved on the remote`).
|
|
588
609
|
|
|
589
|
-
**Rewriting pushed commits** —
|
|
590
|
-
|
|
610
|
+
**Rewriting pushed commits** — a `jj squash --into` a commit already on GitHub, or any other
|
|
611
|
+
rewrite of a commit you already pushed — leaves the pushed tip outside `::@-`, so record
|
|
591
612
|
that tip first, after a fetch and while your chain still descends from it:
|
|
592
613
|
|
|
593
614
|
```bash
|