@sjawhar/claude-envoy 0.0.0 → 1.0.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/.claude-plugin/plugin.json +6 -0
- package/.mcp.json +11 -0
- package/.omp-plugin/plugin.json +6 -0
- package/AGENTS.md +343 -0
- package/README.md +639 -0
- package/bin/dispatch +6 -0
- package/bin/envoy-channel.ts +9 -0
- package/bin/envoy-send.ts +33 -0
- package/dist/THIRD_PARTY_NOTICES +816 -0
- package/dist/dispatch.js +27707 -0
- package/dist/envoy-channel.js +57695 -0
- package/dist/session-hook.js +26328 -0
- package/dist/session-title-hook.js +116 -0
- package/hooks/hooks.json +60 -0
- package/hooks/session-hook.ts +512 -0
- package/hooks/session-title-hook.ts +46 -0
- package/legion/.claude-plugin/plugin.json +4 -0
- package/legion/agents/deep-worker.md +68 -0
- package/legion/agents/oracle.md +35 -0
- package/legion/agents/plan-gap-analyst.md +65 -0
- package/legion/agents/plan-reviewer.md +104 -0
- package/legion/agents/thermonuclear-code-quality.md +30 -0
- package/legion/agents/thermonuclear-deep-review.md +30 -0
- package/legion/hooks/hooks.json +89 -0
- package/legion/skills/ce-simplify-code/LICENSE +21 -0
- package/legion/skills/ce-simplify-code/SKILL.md +66 -0
- package/legion/skills/ce-simplify-code/references/personas/code-quality-reviewer.md +17 -0
- package/legion/skills/ce-simplify-code/references/personas/code-reuse-reviewer.md +7 -0
- package/legion/skills/ce-simplify-code/references/personas/efficiency-reviewer.md +11 -0
- package/legion/skills/leading-a-work-stream/SKILL.md +96 -0
- package/legion/skills/leading-a-work-stream/references/daily-plan.md +8 -0
- package/legion/skills/leading-a-work-stream/references/decision-rights.md +6 -0
- package/legion/skills/leading-a-work-stream/references/direction-and-coaching.md +13 -0
- package/legion/skills/leading-a-work-stream/references/dispatch-upkeep.md +8 -0
- package/legion/skills/leading-a-work-stream/references/sources.md +9 -0
- package/legion/skills/leading-a-work-stream/references/speed-tick.md +44 -0
- package/legion/skills/leading-a-work-stream/references/user-quotes.md +7 -0
- package/legion/skills/legion-architect/SKILL.md +86 -0
- package/legion/skills/legion-architect/references/acceptance.md +208 -0
- package/legion/skills/legion-architect/references/decomposition.md +129 -0
- package/legion/skills/legion-architect/references/design-gate.md +206 -0
- package/legion/skills/legion-architect/references/merge-notices.md +91 -0
- package/legion/skills/legion-architect/references/wake-routing.md +41 -0
- package/legion/skills/legion-controller/SKILL.md +151 -0
- package/legion/skills/legion-controller/references/admission.md +22 -0
- package/legion/skills/legion-controller/references/backlog-stewardship.md +101 -0
- package/legion/skills/legion-controller/references/daily-report.md +75 -0
- package/legion/skills/legion-controller/references/escalation.md +82 -0
- package/legion/skills/legion-controller/references/finding-asks.md +181 -0
- package/legion/skills/legion-controller/references/parked-decisions.md +198 -0
- package/legion/skills/legion-controller/references/session.md +100 -0
- package/legion/skills/legion-controller/references/slack-mentions.md +154 -0
- package/legion/skills/legion-controller/references/triage.md +96 -0
- package/legion/skills/legion-controller/references/wakes.md +132 -0
- package/legion/skills/legion-oracle/SKILL.md +48 -0
- package/legion/skills/legion-retro/SKILL.md +112 -0
- package/legion/skills/legion-retro/references/learnings.md +60 -0
- package/legion/skills/legion-retro/references/pr-body-content.md +56 -0
- package/legion/skills/legion-worker/SKILL.md +69 -0
- package/legion/skills/legion-worker/references/cleanup-deletion.md +22 -0
- package/legion/skills/legion-worker/references/conflicts-and-rewrites.md +164 -0
- package/legion/skills/legion-worker/references/default-branch-check.md +53 -0
- package/legion/skills/legion-worker/references/findings.md +199 -0
- package/legion/skills/legion-worker/references/merge-gate.md +353 -0
- package/legion/skills/legion-worker/references/pr-body.md +218 -0
- package/legion/skills/legion-worker/references/review-threads.md +77 -0
- package/legion/skills/legion-worker/references/systematic-rename.md +19 -0
- package/legion/skills/legion-worker/references/tools.md +708 -0
- package/legion/skills/thermonuclear-code-quality/LICENSE +21 -0
- package/legion/skills/thermonuclear-code-quality/SKILL.md +60 -0
- package/legion/skills/thermonuclear-deep-review/LICENSE +21 -0
- package/legion/skills/thermonuclear-deep-review/SKILL.md +80 -0
- package/package.json +49 -1
- package/scripts/build-inputs.txt +84 -0
- package/scripts/build.ts +415 -0
- package/scripts/check-packed-skills.sh +50 -0
- package/scripts/pack-skills.sh +146 -0
- package/scripts/smoke-channel.sh +178 -0
- package/scripts/smoke-clear-rebind.sh +164 -0
- package/scripts/smoke-session-title.sh +245 -0
- package/scripts/verify-build.sh +88 -0
- package/skills/agent-secrets/SKILL.md +180 -0
- package/skills/asd-ste100/LICENSE +21 -0
- package/skills/asd-ste100/SKILL.md +140 -0
- package/skills/asd-ste100/examples/before-after.md +92 -0
- package/skills/asd-ste100/references/writing-rules.md +59 -0
- package/skills/dispatch/SKILL.md +89 -0
- package/skills/dispatch-first/SKILL.md +46 -0
- package/skills/dispatch-writing-specs/SKILL.md +80 -0
- package/skills/envoy/SKILL.md +82 -0
- package/skills/envoy/references/check-settlement.md +62 -0
- package/skills/envoy/references/deliveries.md +67 -0
- package/skills/envoy/references/legion.md +14 -0
- package/skills/envoy/references/roles.md +12 -0
- package/skills/envoy/references/sources.md +41 -0
- package/skills/outside-message/SKILL.md +111 -0
- package/skills/plain-words/SKILL.md +88 -0
- package/src/channel-forwarder.ts +262 -0
- package/src/claude-channel.ts +705 -0
- package/src/claude-session.ts +38 -0
- package/src/envoy-channel-server.ts +2115 -0
- package/src/envoy-client.ts +26 -0
- package/src/envoy-off.ts +62 -0
- package/src/legion-claim.ts +211 -0
- package/src/legion-inbox.ts +110 -0
- package/src/legion-phase-hook.ts +554 -0
- package/src/legion-tool.ts +69 -0
- package/src/replay-state.ts +97 -0
- package/src/send-arguments.ts +13 -0
- package/src/session-identity.ts +165 -0
- package/src/session-state.ts +142 -0
- package/src/session-title.ts +251 -0
- package/src/subscription-topics.ts +16 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Conflicts, retargets, fingerprints, and rewriting pushed commits
|
|
2
|
+
|
|
3
|
+
Part of `skill://legion-worker`. Read it when GitHub reports the pull request `CONFLICTING`, the
|
|
4
|
+
controller asks you to resolve a conflict, the change needs something only the base has, the pull
|
|
5
|
+
request is retargeted to a new base, you compare two heads after such a merge, a push is refused
|
|
6
|
+
because the handoff import moved the branch, or you are about to rewrite a commit you already
|
|
7
|
+
pushed.
|
|
8
|
+
Every path it cites is in cybermonklabs/legion.
|
|
9
|
+
|
|
10
|
+
## Reintegrating the base
|
|
11
|
+
|
|
12
|
+
- **Merge the base forward only on a real conflict, a need only the base meets, a retarget, or a
|
|
13
|
+
parent's last round, and never with `jj rebase`.**
|
|
14
|
+
The implementer merges the base into the issue branch only when GitHub reports it `CONFLICTING`, the controller asks
|
|
15
|
+
because of a conflict, the architect's backward move names a parent's last round,
|
|
16
|
+
the change needs something that exists only on the base (a fix for its
|
|
17
|
+
only red check, or a sentence it must change), or after the pull request is retargeted to a new
|
|
18
|
+
base. Otherwise, never reintegrate the base to pick up the default branch or refresh CI. Each
|
|
19
|
+
merge is a new head, reruns CI and can void a review. A single failed CI job is re-run on its
|
|
20
|
+
own (*Never waiting on CI* in `skill://legion-worker/references/pr-body.md`). After any forward merge, build the branch and
|
|
21
|
+
record the issue's checks again at the merge head, even with no text conflict: a clean merge once changed a
|
|
22
|
+
function's arity under the branch's own callers. Before you race another tree's pull request on
|
|
23
|
+
shared files, `envoy_publish` to its implementer's role topic, ask when its next push lands, and
|
|
24
|
+
merge once, after it. A forward merge that leaves the branch's diff unchanged is a
|
|
25
|
+
confirmation, not a new round (see *The unchanged-diff check* below). The check compares the
|
|
26
|
+
pull request's own lines file by file: a changed file sends the head through the tester again,
|
|
27
|
+
and your comment names it. Before merging, record
|
|
28
|
+
the fingerprint at the current tip; after pushing the merged branch, record it at the new
|
|
29
|
+
tip; post one PR comment (Legion footer):
|
|
30
|
+
`forward merge <old-tip-sha> → <new-tip-sha>; fingerprint <before> → <after>; unchanged|changed`.
|
|
31
|
+
The fingerprint answers one question only: whether the branch's own diff changed. It cannot
|
|
32
|
+
show an instance the merge brought in from the base, so before you push the merge search its
|
|
33
|
+
head for the class of every finding of every earlier review round and record each search
|
|
34
|
+
beside the fingerprints.
|
|
35
|
+
In a parent's last round, end that comment with `parent's last round: full test round`, the line
|
|
36
|
+
the tester reads for a full round (`skill://legion-architect/references/acceptance.md`,
|
|
37
|
+
"The last round").
|
|
38
|
+
On the tmux runtime, where issue workspaces are still jj workspaces of one shared clone,
|
|
39
|
+
`jj undo`, `jj abandon` and `jj op restore` rewrite the shared operation log, so a mistake there
|
|
40
|
+
is recovered forward (`jj -R "$LEGION_WORKSPACE" new`, or `jj -R "$LEGION_WORKSPACE" restore
|
|
41
|
+
<paths>` of files). In that shared repository jj always rebases every descendant of any commit it
|
|
42
|
+
rewrites — a revset naming the root of your own chain and rewriting it in place also rewrites
|
|
43
|
+
whatever another tree has stacked on that root, whichever selector chose it (`-s`, `-b`, and
|
|
44
|
+
`-r` all rewrite descendants; `-r` only re-parents them to fill the hole, which is worse).
|
|
45
|
+
Resolve the conflict with a forward merge instead of a rewrite — merge the branch's own
|
|
46
|
+
bookmark with the destination in one new commit, so nothing existing is rewritten and nothing
|
|
47
|
+
built on your prior commits, in this tree or another, ever moves. `base` is the pull request's
|
|
48
|
+
base branch (the repository's default branch unless the pull request is stacked), read as a jj
|
|
49
|
+
string literal: jj parses a bare bookmark name as an expression, and a name pasted between
|
|
50
|
+
quotes can close them itself (`release"@origin|"stable` would select `release` and `stable`), so
|
|
51
|
+
the revset is built from the serialized name alone:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
base=$(gh pr view <n> -R <owner>/<repo> --json baseRefName -q '.baseRefName | @json') && \
|
|
55
|
+
jj -R "$LEGION_WORKSPACE" new legion/<KEY> "${base}@origin" -m "merge: resolve conflict against ${base}@origin"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Merge from the bookmark, never from `@`: a `jj split` leaves `@` an empty, undescribed
|
|
59
|
+
commit above the described one the bookmark already names, and `jj git push` refuses to push
|
|
60
|
+
any commit without a description — merging from `@` drags that undescribed commit into the
|
|
61
|
+
ancestry and the push fails (`Won't push commit … since it has no description`); the bookmark
|
|
62
|
+
is always on a described, already-pushed commit. If the merge conflicts, resolve it in that
|
|
63
|
+
one commit — edit the markers directly; there is nothing to squash, since the merge is the
|
|
64
|
+
only new commit. Then `jj -R "$LEGION_WORKSPACE" new` to move off it, and push it (*Every role
|
|
65
|
+
pushes its own commits* in `skill://legion-worker`; a merge is a code push like any other): the
|
|
66
|
+
merge descends from both the bookmark's old position and the destination, so it is a genuine
|
|
67
|
+
fast-forward and *Rewriting pushed commits* never applies.
|
|
68
|
+
- **After the handoff import moved the branch.** `legion handoffs import` moves `legion/<KEY>`
|
|
69
|
+
once, with a commit whose message is `chore(legion): <KEY>'s handoffs are on its Dispatch issue
|
|
70
|
+
now`, which removes the `.legion/` directory where handoffs lived before they moved to Dispatch.
|
|
71
|
+
A push it refuses as a moved branch is cured forward: `jj -R "$LEGION_WORKSPACE" git fetch`,
|
|
72
|
+
then `jj -R "$LEGION_WORKSPACE" new @- legion/<KEY>@origin -m "merge: legion/<KEY>@origin after
|
|
73
|
+
the handoff import"` (from `@-`, your own described tip, since your local bookmark may now
|
|
74
|
+
disagree with the remote one), `jj -R "$LEGION_WORKSPACE" new`, and the push — never a rewrite.
|
|
75
|
+
- **After a retarget.** Retargeting a pull request to a new base does not re-run Tests. Read
|
|
76
|
+
`base` again and merge the bookmark onto it in one command
|
|
77
|
+
(`base=$(gh pr view <n> -R <owner>/<repo> --json baseRefName -q '.baseRefName | @json') && jj -R "$LEGION_WORKSPACE" new legion/<KEY> "${base}@origin" -m "<message>"`) and push it the
|
|
78
|
+
ordinary way — a genuine fast-forward, never `--allow-backwards` — so the new head runs Tests
|
|
79
|
+
against the new merge result, and cite that run in the PR body.
|
|
80
|
+
|
|
81
|
+
## The unchanged-diff check
|
|
82
|
+
|
|
83
|
+
The fingerprint every role compares after a forward merge (every flag and the fileset
|
|
84
|
+
verified on jj 0.45.1):
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
base=$(gh pr view <n> -R <owner>/<repo> --json baseRefName -q '.baseRefName | @json') && \
|
|
88
|
+
cd -- "$LEGION_WORKSPACE" && jj -R "$LEGION_WORKSPACE" git fetch && \
|
|
89
|
+
fingerprint=$(set -o pipefail; jj -R "$LEGION_WORKSPACE" diff --from "fork_point(${base}@origin | <head-sha>)" --to <head-sha> \
|
|
90
|
+
--git --context 0 '~(.legion | docs/solutions)' \
|
|
91
|
+
| sed -e '/^@@/d' -e '/^index /d' | sha256sum) && echo "$fingerprint"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- `<head-sha>` is a full commit SHA; a jj commit id is the git SHA GitHub shows.
|
|
95
|
+
- `fork_point(${base}@origin | <head-sha>)` is the base the branch was cut from *at that head*:
|
|
96
|
+
the old base for the head before the merge, the new base for the merge head, so one command serves
|
|
97
|
+
both sides. `base` is the pull request's base branch as a jj string literal, as in the merge
|
|
98
|
+
above.
|
|
99
|
+
- A head the merge replaced is still addressable by its SHA in the shared workspace. The check
|
|
100
|
+
prints a fingerprint only when every step of it succeeded: the base read, the fetch and the
|
|
101
|
+
diff (`pipefail` keeps the diff's failure through `sed` and `sha256sum`, which would otherwise
|
|
102
|
+
hash empty input to `e3b0c442…`). A nonzero exit or no printed fingerprint (a failed read of the
|
|
103
|
+
base, or a SHA the workspace cannot resolve) counts as a changed diff — never as unchanged.
|
|
104
|
+
- `--context 0` drops context lines; the `sed` drops `@@` hunk headers (line positions move
|
|
105
|
+
on a merge) and `index` lines (blob ids move when the base's copy of a file changed). What
|
|
106
|
+
is left is exactly the added and removed lines per file.
|
|
107
|
+
- The single fileset `'~(.legion | docs/solutions)'` leaves out `.legion/`, where handoffs lived
|
|
108
|
+
before they moved to Dispatch (the handoff import's commit removes it), and retro's learnings:
|
|
109
|
+
paths the merge gate lets through above an approval
|
|
110
|
+
(`skill://legion-worker/references/merge-gate.md`), which change
|
|
111
|
+
between one role's verified head and the next without changing the product. This is what lets
|
|
112
|
+
each role compare against *its own* last verified head instead of trusting another role's
|
|
113
|
+
numbers. It must be one expression: jj unions positional filesets, so two separate
|
|
114
|
+
`'~.legion' '~docs/solutions'` arguments select every file and exclude nothing. Where
|
|
115
|
+
`.legion/` is gone, jj warns `No matching entries for paths: .legion` on stderr; the hash is
|
|
116
|
+
unaffected.
|
|
117
|
+
|
|
118
|
+
Where each role gets its two heads: the implementer — the tip before and after its own forward merge;
|
|
119
|
+
the tester — the commit its latest results name and the new head; the reviewer — the `commit_id` of
|
|
120
|
+
its last submitted review (`gh api --paginate repos/{owner}/{repo}/pulls/{n}/reviews --jq '.[] | {commit_id, state, user: .user.login, body}'`, whose pages concatenate in order, so the last line is the last submitted review, and whose body tells a no-fix request that answered a READY refusal from any other)
|
|
121
|
+
and the new head; the merger never computes a fingerprint — it quotes the `--summary` lines
|
|
122
|
+
in READY, and the daemon holds the head to retro's commits.
|
|
123
|
+
|
|
124
|
+
The fingerprint is not the whole check: a file the merge adds only moves the hash, exactly as an ordinary round of work does.
|
|
125
|
+
So beside it, compare the *set* of files the pull request changes against its base, before and after the merge (`gh pr diff <n> --name-only`, or `jj diff --stat` at each of the two heads), and explain every path that appears or disappears.
|
|
126
|
+
Remove a path that belongs to an already-merged pull request.
|
|
127
|
+
The case that named this rule: #2588's forward merge, made after #2446 was squashed into main, re-added changelog fragments under `packages/envoy/.changes/unreleased/` whose entries main's fold had already written into `packages/envoy/CHANGELOG.md` 27.1.0, so the next fold would have written every one of them a second time.
|
|
128
|
+
|
|
129
|
+
## Rewriting pushed commits
|
|
130
|
+
|
|
131
|
+
**Rewriting pushed commits** — a `jj squash --into` a commit already on GitHub, or any other
|
|
132
|
+
rewrite of a commit you already pushed — is the hazard *Reintegrating the base* describes, in a
|
|
133
|
+
second shape: jj rebases every descendant of any commit it rewrites, and in the one shared
|
|
134
|
+
repository a descendant can be another tree's branch stacked on your pushed commit, which then
|
|
135
|
+
moves, with its bookmark, onto a rewritten copy. So look for a descendant outside your own chain
|
|
136
|
+
first, after a fetch and while your chain still descends from the pushed tip:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
cd -- "$LEGION_WORKSPACE" && \
|
|
140
|
+
jj -R "$LEGION_WORKSPACE" git fetch && \
|
|
141
|
+
foreign=$(jj -R "$LEGION_WORKSPACE" log --no-graph -T 'commit_id.short() ++ "\n"' \
|
|
142
|
+
-r 'descendants(<the commit you are about to rewrite>) ~ ::@') && \
|
|
143
|
+
{ [ -z "$foreign" ] || { echo "not mine, and descends from the commit to rewrite: $foreign" >&2; false; }; }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`descendants(<commit>) ~ ::@` is everything built on the commit you are about to rewrite that is
|
|
147
|
+
not on your own chain. Non-empty means the rewrite would move work that is not yours: do not
|
|
148
|
+
rewrite it. Put the change in a new commit on top instead, and report the listed commits to the
|
|
149
|
+
architect.
|
|
150
|
+
|
|
151
|
+
Once the guard has cleared, rewrite, resolve, and push once, moving the bookmark backwards onto
|
|
152
|
+
the rewritten head — the one sanctioned non-fast-forward move, and the only place
|
|
153
|
+
`--allow-backwards` is ever used:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
cd -- "$LEGION_WORKSPACE" && jj -R "$LEGION_WORKSPACE" bookmark set legion/<KEY> -r @- --allow-backwards && jj -R "$LEGION_WORKSPACE" git push --bookmark legion/<KEY>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
A rewrite head is a code push like any other. `jj git push` still refuses a remote branch another
|
|
160
|
+
role moved since your fetch: report the refusal to the architect with its output, never force.
|
|
161
|
+
|
|
162
|
+
Once a base is frozen for others to stack on, never rewrite it: fixes land as new commits on top,
|
|
163
|
+
and the PR body's `Chain` line records what is frozen.
|
|
164
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Checking your commits against your issue's default branch
|
|
2
|
+
|
|
3
|
+
Your `Legion addressing` line names your issue's repository and its default branch
|
|
4
|
+
(`Your issue's repository is <owner>/<name> (default branch <name>)`) — never assume `main`. That
|
|
5
|
+
branch name is untrusted text: a repository administrator chooses it, and it reaches you as plain
|
|
6
|
+
words in a system prompt, not as a value a shell has already made safe. **Never paste it directly
|
|
7
|
+
into shell source** — not as a bare word, not inside double quotes you type yourself. A branch name
|
|
8
|
+
containing `$(…)` or backticks would run as a command the moment a shell re-parses a line built that
|
|
9
|
+
way; one containing jj revset syntax (parentheses, `|`, `::`) would be read as an expression instead
|
|
10
|
+
of a literal name.
|
|
11
|
+
|
|
12
|
+
The safe sequence reads the branch fresh from GitHub and lets `jq` do the quoting, in one command
|
|
13
|
+
whose only typed literal is the `<owner>/<repo>` your addressing line names (safe to interpolate: a
|
|
14
|
+
GitHub repository path, already validated by the daemon that minted your addressing):
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
default=$(gh api repos/<owner>/<repo> --jq '.default_branch | @json')
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`$default` now holds a JSON-quoted string, e.g. `"release/v7.1"` (quotes included). Compare it
|
|
21
|
+
against the `default branch <name>` your addressing line named — if they disagree, stop and tell
|
|
22
|
+
the architect; GitHub's live default branch and the one your record carries are not the same, and
|
|
23
|
+
guessing which is right is not yours to do. When they agree, use `$default` as-is, never re-typed:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
jj -R "$LEGION_WORKSPACE" log -r "${default}@origin..@" -T 'author.email() ++ " | " ++ committer.email() ++ " " ++ description.first_line() ++ "\n"'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Because `$default` already carries its own quote characters, jj's revset parser reads
|
|
30
|
+
`"release/v7.1"@origin..@` as a quoted string literal naming the bookmark, whatever characters the
|
|
31
|
+
branch name holds — parentheses, spaces, or shell metacharacters included — rather than as revset
|
|
32
|
+
syntax or shell code. Bash's own `"${default}@origin..@"` substitutes `$default`'s value once,
|
|
33
|
+
literally; it never re-evaluates the substituted text, so a `$(…)` sequence inside the branch name
|
|
34
|
+
never runs.
|
|
35
|
+
|
|
36
|
+
The output must show your role's App in both columns **on every commit you made** — not on the
|
|
37
|
+
whole list: earlier phases' commits are legitimately authored by their own role's App.
|
|
38
|
+
|
|
39
|
+
## Which App, and what a wrong one means
|
|
40
|
+
|
|
41
|
+
- Your role's App is the **implement** App for the implementer and the merger, and the **review**
|
|
42
|
+
App for the planner, tester, reviewer and architect; your pane's `JJ_USER` and `JJ_EMAIL` name
|
|
43
|
+
its bot account. A planner's commits authored by the review App are right.
|
|
44
|
+
- Never set `user.name` or `user.email` in any jj or Git scope (`jj config set`, `--config`,
|
|
45
|
+
`git config`): `--config` outranks the pane's identity and puts the wrong App on your commits,
|
|
46
|
+
and the repository-scoped jj config is one file shared by every issue workspace of the clone.
|
|
47
|
+
- A wrong identity on your own commit (the other App, or none) is a pane-environment problem:
|
|
48
|
+
report it to the architect, never pin it (cybermonklabs/legion
|
|
49
|
+
`docs/solutions/legion/shared-main-repo-hazards-for-concurrent-issue-workspaces.md`, Hazard 1).
|
|
50
|
+
- Another role's commit with you as committer, which you did not rewrite under the guard in
|
|
51
|
+
*Rewriting pushed commits* (`skill://legion-worker/references/conflicts-and-rewrites.md`), means
|
|
52
|
+
something rewrote commits it should not have: stop and send the architect this log. A forward
|
|
53
|
+
merge rewrites nothing, so it changes no committer.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# A finding outside your issue
|
|
2
|
+
|
|
3
|
+
Read this when you find a problem the issue you work does not cover: a defect in another
|
|
4
|
+
component, repository or tool, a gap in Legion itself, or a production check's `fail`. It holds for
|
|
5
|
+
every Legion role (each phase worker, each architect, the controller) and for each of their
|
|
6
|
+
subagents, and it replaces the filing step of `skill://dispatch-first` and `skill://dispatch`. The
|
|
7
|
+
controller fixes nothing itself: it hands a finding to the tree that owns the code, as
|
|
8
|
+
`skill://legion-controller`, "Findings, and the issues you file", says.
|
|
9
|
+
|
|
10
|
+
## Fix it where you are, else hand it over, and ask only for a decision
|
|
11
|
+
|
|
12
|
+
A finding outside your issue never becomes a `dispatch issue` you file. Take the first of these
|
|
13
|
+
three steps that fits:
|
|
14
|
+
|
|
15
|
+
1. **Fix it here.** A defect with a clear fix in code your tree touches or depends on goes into
|
|
16
|
+
your tree's own pull request, while you are in that code. The fix needs no choice a person must
|
|
17
|
+
make, and the evidence shows the defect. Say in your handoff and in the pull request body what
|
|
18
|
+
you fixed beside the issue's own work. A fix deferred to another issue is a fix nobody makes.
|
|
19
|
+
2. **Hand it over.** Any other clear defect goes to the controller with its evidence, as [Send a
|
|
20
|
+
finding to the controller](#send-a-finding-to-the-controller) says. The controller gives it to
|
|
21
|
+
the running tree whose code it is. Only when no tree owns that code does it file a Legion root.
|
|
22
|
+
3. **Ask.** A finding that needs a person's authority, taste or risk appetite is a `dispatch ask`
|
|
23
|
+
with one recommendation, as [Where it goes](#where-it-goes) says: whether the work is wanted at
|
|
24
|
+
all, which of several designs, who owns it, or a change of scope, cost or risk.
|
|
25
|
+
|
|
26
|
+
A fix you are not sure of is not step 1: hand it over, or ask when a person must decide.
|
|
27
|
+
|
|
28
|
+
- A problem inside your issue is not such a finding: fix it in your change, since nothing needed
|
|
29
|
+
for correctness is deferred.
|
|
30
|
+
- A problem inside your tree is work its root spec's acceptance needs. A phase worker's such
|
|
31
|
+
problem outside its own issue goes to the tree's architect (`skill://legion-worker`, *Stop
|
|
32
|
+
rules*), which adds it to an open pull request of the tree, or files a child when it is a
|
|
33
|
+
separately deliverable change. Anything else, in the same
|
|
34
|
+
repository or not, is a finding.
|
|
35
|
+
- Search first (`skill://dispatch-first`): when an open issue already tracks the problem, comment
|
|
36
|
+
the evidence on that issue and send or ask nothing. When an open ask on the report issue already
|
|
37
|
+
asks about it (`dispatch open-asks --project <PROJECT>` lists them), add your evidence under that
|
|
38
|
+
ask (`dispatch comment --issue <REPORT KEY> --reply-to-ask '<ask id>' --body-file -`, the
|
|
39
|
+
evidence on stdin) and open no second one.
|
|
40
|
+
|
|
41
|
+
These issues are filed:
|
|
42
|
+
|
|
43
|
+
- an architect's children of its own tree (`dispatch issue --parent`, `skill://legion-architect`)
|
|
44
|
+
- a Legion root the controller files for a clear defect no running tree owns
|
|
45
|
+
- an issue a person asked the controller to file, in a direct message, in a Slack request, or in
|
|
46
|
+
their answer to an ask
|
|
47
|
+
- the report issue itself, which the controller creates once when the project has none
|
|
48
|
+
|
|
49
|
+
`skill://legion-controller`, "Findings, and the issues you file", and
|
|
50
|
+
`skill://legion-controller/references/daily-report.md` say how.
|
|
51
|
+
|
|
52
|
+
## Send a finding to the controller
|
|
53
|
+
|
|
54
|
+
Post the evidence first as a `dispatch comment` on the report issue ([Where it goes](#where-it-goes)
|
|
55
|
+
says which), its first line exactly `Clear defect from <your role token>`. It prints
|
|
56
|
+
`Posted comment <id>`. Then send the controller one message with
|
|
57
|
+
`envoy_publish` to the controller topic your `Legion addressing` line names, with
|
|
58
|
+
`expects_reply: none`. It holds:
|
|
59
|
+
|
|
60
|
+
- your role token, from your `Legion addressing` line
|
|
61
|
+
- the defect: what went wrong, where and to whom, with the numbers, and the issue it came from
|
|
62
|
+
(`dispatch://<KEY>`)
|
|
63
|
+
- where the fix belongs: the repository and the component
|
|
64
|
+
- the fix
|
|
65
|
+
- the evidence comment, `dispatch://<REPORT KEY>/comment/<id>`
|
|
66
|
+
|
|
67
|
+
The controller hands it to the running tree whose code it is, and files a Legion root only when no
|
|
68
|
+
tree owns that code. Either way it replies under your evidence comment with where the work went.
|
|
69
|
+
You file nothing, and you wait for nothing. When the report issue is missing or closed, put the
|
|
70
|
+
evidence in the message itself. A production check's recorded result then names the defect as
|
|
71
|
+
sent to the controller.
|
|
72
|
+
|
|
73
|
+
### When no controller takes it
|
|
74
|
+
|
|
75
|
+
The controller topic is a live wake: a message published while no controller runs reaches nobody
|
|
76
|
+
at the time. `envoy_publish`'s own answer tells you whether one took it. A publish to a role
|
|
77
|
+
answers with its live `holder`. With no claim on `legion-<project>-controller` at all, the answer
|
|
78
|
+
says Envoy kept the message instead (`packages/envoy/cmd/listener`, the test "unheld role is kept
|
|
79
|
+
and answered 202 without publishing on the role topic"): the next session that claims the role is
|
|
80
|
+
handed each kept message once, within one hour of the keep. A keep is not a delivery: a
|
|
81
|
+
controller can stay away for the whole hour, and a message kept longer than an hour is dropped
|
|
82
|
+
unread, so a kept finding can reach nobody. It fails with `role legion-<project>-controller
|
|
83
|
+
holder <session> lapsed; claim retained`, naming the holder, its `holder_state` and its
|
|
84
|
+
`last_seen`, when a controller holds the role and its session has not registered within the live
|
|
85
|
+
window: an `unresponsive` controller is not a stopped one, so release nothing, take nothing from
|
|
86
|
+
it, and publish no second copy.
|
|
87
|
+
Treat a kept answer as a controller not taking it. Either way the finding has not been taken yet,
|
|
88
|
+
so make it an ask, as
|
|
89
|
+
[What the ask says](#what-the-ask-says) spells it, on the first
|
|
90
|
+
of these that holds, so a person sees it and the controller finds it when it returns:
|
|
91
|
+
|
|
92
|
+
1. **The report issue is open:** ask there, the question opening `Clear defect from <your role
|
|
93
|
+
token>` and `--ref` naming your evidence comment. Then reply `Asked dispatch://<REPORT
|
|
94
|
+
KEY>/ask/<id>` under that comment, so the controller takes the ask and not the comment. The
|
|
95
|
+
report issue's assignee sees the ask, and the controller's catch-up takes it.
|
|
96
|
+
2. **The report issue is missing or closed:** ask on your own issue, the evidence posted there as
|
|
97
|
+
a comment first. Then publish the ask's reference to your tree's architect (`skill://legion-worker`,
|
|
98
|
+
*Stop rules*). An architect holds its own. The architect holds your issue's
|
|
99
|
+
acceptance until a controller has taken the ask (`skill://legion-architect`, "Acceptance and the
|
|
100
|
+
merge"). That hold is what keeps the ask readable: an open ask on a closed issue drops out of
|
|
101
|
+
the controller's own listing (`asks.go`'s `listOpenAsks` takes `i.closed_at is null`), so an
|
|
102
|
+
issue that closed over one hides it for good.
|
|
103
|
+
3. **Your own issue is closed too** (Dispatch refuses the ask `409 ISSUE_CLOSED`, as on a
|
|
104
|
+
production check): put the report issue back in reach yourself, with the `dispatch` commands
|
|
105
|
+
below, and ask there. Never `legion({op: "set_status"})`, which the controller's report steps
|
|
106
|
+
(`skill://legion-controller/references/daily-report.md`) use: that op is the controller's
|
|
107
|
+
alone, and your session is refused `set_status is not
|
|
108
|
+
available to a phase-worker session` (`packages/pi-legion/src/tools.ts`'s `OPERATIONS`). No
|
|
109
|
+
other issue can hold an answerable ask, so this is the one case a role writes the report issue.
|
|
110
|
+
- It exists and is closed: `dispatch issue-update --issue <REPORT KEY> --status icebox`. Send
|
|
111
|
+
the status by itself. A closed issue takes a reopening status alone, and refuses it beside
|
|
112
|
+
an assignee or any other field (`api/issue_patch.go`, the `before.ClosedAt` branch). This is
|
|
113
|
+
the one lifecycle status your own prompt lets you write. The report issue is in no tree, so
|
|
114
|
+
the daemon re-asserts nothing on it: `workflow`'s `sessionStatusWrite` acts only for a
|
|
115
|
+
session that claims the issue's tree, or for an admitted root.
|
|
116
|
+
- A search finds none: create it as "Where it goes" names it, `dispatch issue --project
|
|
117
|
+
<PROJECT> --title 'Legion daily report' --spec-file -`, with the Summary
|
|
118
|
+
`skill://legion-controller/references/daily-report.md` gives, then park it with
|
|
119
|
+
`dispatch issue-update --issue <NEW KEY> --status icebox`. It carries no `legion` label.
|
|
120
|
+
- Then post the evidence there and ask there, as [What the ask says](#what-the-ask-says)
|
|
121
|
+
spells it. Say in your summary that you reopened or created it, so the controller knows.
|
|
122
|
+
|
|
123
|
+
Name the ask in your summary, and in the result a production check records.
|
|
124
|
+
|
|
125
|
+
## Where it goes
|
|
126
|
+
|
|
127
|
+
An ask goes on the controller's report issue whenever it is open: the issue the deployment
|
|
128
|
+
instructions name for Legion's reports, else the project's `Legion daily report`, which
|
|
129
|
+
`dispatch search --query '"Legion daily report"' --project <PROJECT>` finds. It stays open in
|
|
130
|
+
`icebox`, so the ask stays answerable whatever happens to the issue it came from; once an issue
|
|
131
|
+
closes, its close withdraws its open asks (a reopen brings them back), and Dispatch refuses their
|
|
132
|
+
answers (`409 ISSUE_CLOSED`). The question names the issue the finding came from by its
|
|
133
|
+
`dispatch://` reference.
|
|
134
|
+
|
|
135
|
+
When the project has no report issue yet, or a person closed it (`dispatch read` shows `Status:
|
|
136
|
+
done`, and Dispatch refuses the ask with `409 ISSUE_CLOSED`), do not create or reopen one yourself
|
|
137
|
+
(the controller does): send the finding to the controller as text, with `envoy_publish` to the
|
|
138
|
+
controller topic your `Legion addressing` line names (`expects_reply: none`), holding the problem,
|
|
139
|
+
the issue it came from, the evidence and the decision it needs. The controller creates or reopens
|
|
140
|
+
the report issue and opens the ask. When that publish fails because no controller holds the role,
|
|
141
|
+
take step 2 or 3 of [When no controller takes it](#when-no-controller-takes-it). A production
|
|
142
|
+
check's recorded result then names the finding as sent to the controller, or names the ask.
|
|
143
|
+
|
|
144
|
+
## What the ask says
|
|
145
|
+
|
|
146
|
+
The question stands alone, since the ask view shows no comment. It holds at most 2,000 characters,
|
|
147
|
+
the `Ref: <reference>` line `--ref` appends included (about 75 characters for a comment's
|
|
148
|
+
reference). Its first line names who asks: `Clear defect from <your role token>` for a clear
|
|
149
|
+
defect no controller took, else `Finding from <your role token>`. The architect's acceptance and the
|
|
150
|
+
controller's catch-up find a finding ask by that line. Then it names the concrete case before any
|
|
151
|
+
general statement of it:
|
|
152
|
+
|
|
153
|
+
- what went wrong, where and to whom: the issue it came from (`dispatch://<KEY>`), the machine,
|
|
154
|
+
file, command or session, and the numbers;
|
|
155
|
+
- where the fix would belong: the repository and component, and a plain statement that it is not
|
|
156
|
+
Legion's when it is not. A problem is not Legion's because you work in Legion: name the
|
|
157
|
+
component that misbehaved;
|
|
158
|
+
- the decision a person must make, what happens to whom under each option, and your
|
|
159
|
+
recommendation and its reason. A clear defect no controller took carries the fix instead, and its
|
|
160
|
+
options ask where the work goes, with `Hand it to the owning tree` first and recommended.
|
|
161
|
+
|
|
162
|
+
Evidence the decision does not need (full command output, logs, every file and line, the pull
|
|
163
|
+
request) goes in a comment. Post it first as a `dispatch comment` on the issue the ask goes on,
|
|
164
|
+
which prints `Posted comment <id>`, and cite that comment with `--ref`, the one reference an ask
|
|
165
|
+
takes. That issue,
|
|
166
|
+
`<ASK KEY>` below, is the report issue, except as [When no controller takes
|
|
167
|
+
it](#when-no-controller-takes-it) says:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
dispatch comment --issue '<ASK KEY>' --body-file - <<'EOF'
|
|
171
|
+
<the evidence: commands, output with its time, file:line, links>
|
|
172
|
+
EOF
|
|
173
|
+
# Keep `File it for Legion` only when your Legion addressing line lists the repository where the
|
|
174
|
+
# fix belongs. For a clear defect no controller took, replace the `File an issue` option below with
|
|
175
|
+
# 'Hand it to the owning tree: <the tree, when you know it>', the first option and recommended.
|
|
176
|
+
dispatch ask --issue '<ASK KEY>' --ref 'dispatch://<ASK KEY>/comment/<id>' --question-file - \
|
|
177
|
+
--option 'File an issue: <what it would track, and who would own it>' \
|
|
178
|
+
--option 'File it for Legion: Legion works it once it is filed' \
|
|
179
|
+
--option 'Do not file: <what stays as it is>' <<'EOF'
|
|
180
|
+
Finding from <your role token>
|
|
181
|
+
<what went wrong, where and to whom, with dispatch://<KEY> and the numbers; where the fix belongs, and that it is not Legion's when it is not; what each option does to whom; your recommendation and why>
|
|
182
|
+
EOF
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Legion refuses to record an issue on a repository the project does not map, hence that condition.
|
|
186
|
+
|
|
187
|
+
## Tell the controller about the ask
|
|
188
|
+
|
|
189
|
+
The person answers in Dispatch, and the answer reaches only the ask's followers. So once the ask is
|
|
190
|
+
open, `envoy_publish` its reference, `dispatch://<REPORT KEY>/ask/<id>` (the id from the
|
|
191
|
+
`Asked <id> on …` line), to the controller topic your `Legion addressing` line names, with
|
|
192
|
+
`expects_reply: none`. The controller follows the ask and files what the person's answer asks for;
|
|
193
|
+
you file nothing, whatever the answer says. The controller itself skips this step, since it follows
|
|
194
|
+
each ask it opens. When the listener kept that publish rather than a holder taking it, do nothing
|
|
195
|
+
more for an ask on the report issue: the controller's catch-up follows it at its next start.
|
|
196
|
+
|
|
197
|
+
An ask on the report issue blocks nothing of your own issue: its work goes on. One on your own
|
|
198
|
+
issue holds its acceptance, as step 2 of [When no controller takes
|
|
199
|
+
it](#when-no-controller-takes-it) says.
|