@elalfner/relevo 0.7.0 → 0.8.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 +1 -1
- package/package.json +1 -1
- package/scripts/init.mjs +3 -1
- package/scripts/lib/can-edit-issue.mjs +4 -1
- package/scripts/lib/config.mjs +19 -1
- package/scripts/lib/doctor.mjs +136 -5
- package/scripts/lib/github.mjs +15 -5
- package/scripts/lib/init.mjs +63 -3
- package/scripts/lib/labels.mjs +4 -0
- package/scripts/lib/messages.mjs +109 -0
- package/scripts/lib/next-step.mjs +12 -0
- package/scripts/lib/plan.mjs +29 -6
- package/scripts/lib/preflight.mjs +1 -1
- package/scripts/lib/review-events.mjs +6 -3
- package/scripts/lib/ship-github.mjs +87 -0
- package/scripts/lib/ship-unattended.mjs +271 -0
- package/scripts/lib/ship.mjs +15 -3
- package/scripts/lib/toolchain-check.mjs +6 -0
- package/scripts/lib/web-preflight.mjs +50 -4
- package/scripts/review-event.mjs +10 -1
- package/scripts/ship-check.mjs +6 -4
- package/scripts/ship-unattended.mjs +45 -0
- package/scripts/toolchain-check.mjs +3 -3
- package/scripts/web-hook.mjs +28 -0
- package/scripts/web-preflight.mjs +6 -5
- package/scripts/web-review.mjs +1 -1
- package/skills/doctor/SKILL.md +3 -2
- package/skills/go/SKILL.md +4 -2
- package/skills/init/SKILL.md +7 -2
- package/skills/review/SKILL.md +4 -3
- package/skills/ship/SKILL.md +3 -1
- package/stacks/vue.md +2 -2
- package/templates/agents/antigravity-step.yml +8 -5
- package/templates/relevo.yml +4 -0
- package/templates/workflows/relevo-digest.yml +10 -1
- package/templates/workflows/relevo-go.yml +25 -5
- package/templates/workflows/relevo-queue.yml +10 -1
- package/templates/workflows/relevo-review.yml +10 -1
- package/templates/workflows/relevo-ship.yml +135 -0
- package/templates/workflows/relevo-sla.yml +10 -1
package/skills/go/SKILL.md
CHANGED
|
@@ -88,6 +88,8 @@ node ${CLAUDE_PLUGIN_ROOT}/scripts/preflight.mjs
|
|
|
88
88
|
|
|
89
89
|
It checks the configuration, that OpenSpec runs and finds a root for this repository, and that the working tree is clean. If it exits non-zero, stop with a clear message and change nothing: show its output, which names each failed check and its fix.
|
|
90
90
|
|
|
91
|
+
When the failed check is `openspec` (the command line does not run), stop there. Show the install command it gives and do not go on from memory or from reading the code: never write anything under `openspec/changes/` by hand, and never imitate what `openspec` would have produced.
|
|
92
|
+
|
|
91
93
|
Find the repository name: `$GITHUB_REPOSITORY`, or `gh repo view --json nameWithOwner -q .nameWithOwner`.
|
|
92
94
|
|
|
93
95
|
### 1. Plan and claim
|
|
@@ -110,7 +112,7 @@ This reads the queue from GitHub, applies the gates and limits, carries out labe
|
|
|
110
112
|
|
|
111
113
|
Read the decision for the issue in the JSON it prints:
|
|
112
114
|
|
|
113
|
-
- `start`: continue. It gives you `branch`, `change_id`, `base`, `assignee`, `title`, `qa_retries`, `run_max_turns`, `run_max_cost`, `issue_hash` (the text of the issue when it was claimed), `allow_paths` (dependency files and folders the issue lets the change touch), and the platform the issue is for: `platform` (its name), `stack`, `path` (its folder) and `targets`.
|
|
115
|
+
- `start`: continue. It gives you `branch`, `change_id`, `base`, `assignee`, `title`, `qa_retries`, `run_max_turns`, `run_max_cost`, `issue_hash` (the text of the issue when it was claimed), `allow_paths` (dependency files and folders the issue lets the change touch), and the platform the issue is for: `platform` (its name), `stack`, `path` (its folder) and `targets`. When a valid urgent label let the issue start over the assignee's capacity it also has `urgent` (true) and `built_waiting` (how many built issues the assignee will have waiting).
|
|
114
116
|
- anything else (`needs-spec`, `block`, `queue`, `denied`, `no-milestone`, `no-assignee`, `skip`): that is a normal outcome, already applied. Report the decision and its reason in one line, and stop. Do not build.
|
|
115
117
|
|
|
116
118
|
### 2. Branch
|
|
@@ -245,7 +247,7 @@ Do not add links to this session or attribution lines to the message.
|
|
|
245
247
|
|
|
246
248
|
1. `git push -u origin <branch>` (a normal push, never forced).
|
|
247
249
|
2. Move the issue to `built`: `label-change.mjs --to built --current "..."`, applied with `gh issue edit`.
|
|
248
|
-
3. Post one comment with `render-message.mjs built`, mentioning the assignee. Fill: `assignee`, `issue`, `title`, `tasks_done` and `tasks_total` (`progress.complete` and `progress.total` of `openspec instructions apply --change "<change_id>" --json`), `branch`, `issue_hash`, `scenarios` (`node ${CLAUDE_PLUGIN_ROOT}/scripts/checklist.mjs openspec/changes/<change_id> --count`), `risk` if QA or the developer pointed at a riskiest area, and `checklist` (the output of `node ${CLAUDE_PLUGIN_ROOT}/scripts/checklist.mjs openspec/changes/<change_id>`).
|
|
250
|
+
3. Post one comment with `render-message.mjs built`, mentioning the assignee. Fill: `assignee`, `issue`, `title`, `tasks_done` and `tasks_total` (`progress.complete` and `progress.total` of `openspec instructions apply --change "<change_id>" --json`), `branch`, `issue_hash`, `scenarios` (`node ${CLAUDE_PLUGIN_ROOT}/scripts/checklist.mjs openspec/changes/<change_id> --count`), `risk` if QA or the developer pointed at a riskiest area, `urgent` and `built_waiting` copied from the decision when it has them (they add the line that says the issue started ahead of the queue; leave both out otherwise), and `checklist` (the output of `node ${CLAUDE_PLUGIN_ROOT}/scripts/checklist.mjs openspec/changes/<change_id>`).
|
|
249
251
|
|
|
250
252
|
The comment tells the assignee the next command (`/relevo:review <issue>`).
|
|
251
253
|
|
package/skills/init/SKILL.md
CHANGED
|
@@ -34,13 +34,14 @@ If `relevo.yml` already exists and is valid, skip this step. Otherwise ask, in s
|
|
|
34
34
|
- the review mode: `none` (a solo project), `peer` or `roles`;
|
|
35
35
|
- the language of the hand-off messages (`en` or `es`);
|
|
36
36
|
- the platforms: for each one a name, its stack (look at the project to propose one: the profiles in `${CLAUDE_PLUGIN_ROOT}/stacks/` are the stacks Relevo ships with: `flutter`, `vue`, `laravel` and `supabase`), the folder where it lives (`.` for a repository with one platform, `apps/web` in a monorepo) and, for a stack that has targets, which ones (for Flutter: `android`, `ios`, `web`; for Vue: `web`; Laravel and Supabase have none). Several platforms in one repository are written down, but `go` builds only a repository with one for now;
|
|
37
|
-
- where `go` builds: in the cloud, on each person's machine, or both (the default), and, for the cloud, with an API key from the Anthropic Console (the default) or with a subscription token. A subscription's quota is shared by every run and by the interactive use of the account, so `init` sets `limits.concurrent_agents` to 1 for it. Nothing about this goes in `relevo.yml` apart from that number: the installed workflows, the secret that exists and the repository variable `RELEVO_CLOUD_GO` (`off` pauses the cloud at once, with no pull request) decide it;
|
|
37
|
+
- where `go` builds: in the cloud, on each person's machine, or both (the default), and, for the cloud, with an API key from the Anthropic Console (the default) or with a subscription token. A subscription's quota is shared by every run and by the interactive use of the account, so `init` sets `limits.concurrent_agents` to 1 for it. Nothing about this goes in `relevo.yml` apart from that number: the installed workflows, the secret that exists and the repository variable `RELEVO_CLOUD_GO` (`off` pauses the cloud at once, with no pull request) decide it. Whatever the answer, the ship workflow (`relevo-ship.yml`) is added: it ships an approved issue when a person applies the `relevo:ship` label, runs no model and needs no model credential, and the repository variable `RELEVO_CLOUD_SHIP` (`off`) pauses it;
|
|
38
|
+
- whether the repository runs its GitHub Actions on a self-hosted runner. Ask nothing more when the answer is no: the workflows run on `ubuntu-latest` and `relevo.yml` gets no `runner` key. On a yes, ask for the runner's labels (for example `self-hosted` and `Front`): they become `runner.labels` in `relevo.yml` and the `runs-on` of every workflow `init` adds. Say that Relevo assumes a self-hosted runner keeps its files between jobs, whatever the person says about it, and that the agent's code runs with the permissions of the runner's user; do not ask whether the runner is ephemeral;
|
|
38
39
|
- the repositories this one depends on, if any (`owner/repo`, for example the back end of a front end): they are written to `links`, the closed list that `Depends-on: owner/repo#N` is checked against;
|
|
39
40
|
- whether to create a commented `.relevo/stacks/<stack>.md` for each stack, where the project can add its own hygiene to the plugin's profile (optional; `init` creates it but does not commit it: it is left for the person to commit, because the cloud and teammates read it from git);
|
|
40
41
|
- which agent builds in the cloud, only when the person says they use Antigravity: `claude` (the default) or `antigravity`. With `antigravity` the model key is `GEMINI_API_KEY`, a key made only for Relevo in its own Google Cloud project with a quota, and the limit asked below is a number of tokens (`limits.run_max_tokens`) because that agent has no cost limit; `docs/security.md` says what else it cannot enforce;
|
|
41
42
|
- the cost limit per unattended run in US dollars: the template's 5 is a placeholder the person must accept or change (with `antigravity`, the token limit instead).
|
|
42
43
|
|
|
43
|
-
Write the answers as JSON to a temporary file **outside the repository**, with the keys `members`, `specWriter`, `reviewMode`, `language`, `platforms` (a map of name to `{ stack, path, targets }`), `links` (a list of `owner/repo`, possibly none), `runs` (`cloud`, `local` or `both`), `credential` (`api_key` or `subscription`), `stackProfiles` (the stacks that get a profile file, possibly none) and, if given, `runMaxCost`. Never put a secret in it.
|
|
44
|
+
Write the answers as JSON to a temporary file **outside the repository**, with the keys `members`, `specWriter`, `reviewMode`, `language`, `platforms` (a map of name to `{ stack, path, targets }`), `links` (a list of `owner/repo`, possibly none), `runs` (`cloud`, `local` or `both`), `credential` (`api_key` or `subscription`), `runnerLabels` (a list of runner labels, only for a self-hosted runner), `stackProfiles` (the stacks that get a profile file, possibly none) and, if given, `runMaxCost`. Never put a secret in it.
|
|
44
45
|
|
|
45
46
|
### 3. Show the plan
|
|
46
47
|
|
|
@@ -56,6 +57,10 @@ For each line in the plan that is an install, show its exact command and ask for
|
|
|
56
57
|
|
|
57
58
|
One install is not a dependency: `install.plugin-project` declares the Relevo plugin in the project, so that a teammate who clones the repository is offered it, even when the plugin is already installed for the machine. Show its exact command (the two `claude plugin` commands at the project scope) and run it only on a yes. If it ran, `.claude/settings.json` is new or changed: say that it is left for the person to commit. If the person declines, nothing else changes, and `/relevo:doctor` keeps warning about it. When the plan says that file is not valid JSON, do not touch it: tell the person to fix it.
|
|
58
59
|
|
|
60
|
+
`install.web-hook` adds a `SessionStart` hook to `.claude/settings.json` that installs OpenSpec at the start of a session of Claude Code on the web, where every new container lacks it. Show the hook the plan names (it runs nothing outside a remote session and nothing when `openspec` already runs) and ask for a yes to it alone. On a yes, run `node ${CLAUDE_PLUGIN_ROOT}/scripts/web-hook.mjs` from the repository root: it adds the hook next to the other settings and hooks of the file and changes nothing else, and it writes nothing when the file is not valid JSON. Say that `.claude/settings.json` is left for the person to commit. If the person declines, write nothing for it: `/relevo:doctor` keeps warning about it in a web session. When the plan says the file is not valid JSON, do not touch it.
|
|
61
|
+
|
|
62
|
+
For a Vue platform reviewed in a browser, the plan has two installs, in this order: `install.playwright.<platform>` adds Playwright to the platform's development dependencies, and `install.chromium.<platform>` downloads its Chromium. Run each from the repository root exactly as shown (the command enters the platform folder itself). If Playwright was added, `package.json` and the lock file of that platform changed: say that they are left for the person to commit with the other new files, on the base branch, so that every review finds Playwright. If the person declines either one, continue and say that a browser review of that platform stops until it is installed, and that `/relevo:doctor` keeps warning about it. A `review.autopilot` line among the manual steps means the existing `relevo.yml` keeps the autopilot `off`: say that a browser review needs `precheck` or `verify`, and leave the file as it is.
|
|
63
|
+
|
|
59
64
|
### 5. Apply
|
|
60
65
|
|
|
61
66
|
```bash
|
package/skills/review/SKILL.md
CHANGED
|
@@ -79,7 +79,7 @@ If `evidence/review-state.json` exists for this issue, resume: show the confirme
|
|
|
79
79
|
|
|
80
80
|
Otherwise: `node ${CLAUDE_PLUGIN_ROOT}/scripts/review-state.mjs init openspec/changes/<change folder> --autopilot <review.autopilot>`, then clear the log so the audit only sees this session: `adb -s <serial> logcat -c`. Then ask the person to start the app, or restart it if it was already open, so that the log names its process and the audit can ignore other apps on the device.
|
|
81
81
|
|
|
82
|
-
For a `browser` adapter there is no `adb` and no device log: after `init`, run the preflight against the address under review (the one the person gives; for Vue, `http://localhost:5173` when they give none):
|
|
82
|
+
For a `browser` adapter there is no `adb` and no device log: after `init`, run the preflight from the folder of the platform, against the address under review (the one the person gives; for Vue, `http://localhost:5173` when they give none):
|
|
83
83
|
|
|
84
84
|
```bash
|
|
85
85
|
node ${CLAUDE_PLUGIN_ROOT}/scripts/web-preflight.mjs --url <address>
|
|
@@ -87,8 +87,9 @@ node ${CLAUDE_PLUGIN_ROOT}/scripts/web-preflight.mjs --url <address>
|
|
|
87
87
|
|
|
88
88
|
It prints JSON and exits by what is missing. Tell the person in their language:
|
|
89
89
|
|
|
90
|
-
- `3
|
|
91
|
-
- `4
|
|
90
|
+
- `3` (there is no `node_modules`) or `7` (the installed dependencies do not match `package-lock.json`, for example after the merge changed it): when `install` is `null` there is no lock file to install from; ask the person to install the dependencies themselves and stop until they say it is done. Otherwise show the `install` command exactly as printed and ask for a yes to that command alone; never compose another one. Only on a yes run it from the folder of the platform, then `git status --porcelain -- package.json <the lock file>`: if either changed, restore it with `git restore --` and say which. Then run the preflight again. If the install fails, show its error and stop: do not retry it with package scripts or another command, and do not drive. If the person says no, stop without driving; the issue stays as it is. After an install, if the person's dev server is already running, tell them to restart it before you check the address again; never restart it yourself.
|
|
91
|
+
- `4`: Playwright does not resolve from the project. Install nothing: say that Playwright is missing from the development dependencies of the project, that `/relevo:init` adds it once for everyone (or `npm install -D playwright` in the folder of the platform, committed on the base branch), and stop without driving.
|
|
92
|
+
- `5`: the Chromium of Playwright is missing. It lives outside the project: ask for permission, and only on a yes run `npx playwright install chromium` from the folder of the platform, then run the preflight again.
|
|
92
93
|
- `6`: the app does not answer, or one of its modules answers 400 or more (`detail` names it). Keep its `start` and `env` for step 3: the person starts the app there. If the app was already running and a module fails, tell them to restart their server; never do it yourself.
|
|
93
94
|
- `0`: ready.
|
|
94
95
|
|
package/skills/ship/SKILL.md
CHANGED
|
@@ -99,7 +99,7 @@ Do not add links to this session or attribution lines to the commit.
|
|
|
99
99
|
|
|
100
100
|
### 4. Check again
|
|
101
101
|
|
|
102
|
-
The push may start new checks or dismiss an approval. Wait for any running checks (`gh pr checks <number> --repo <owner/name> --watch`), then run `ship-check.mjs` again, exactly as in step 1 and adding `--baseline <baseline>`, so an approval given on the head before the archive still counts. If it now says the pull request has conflicts, see below. If it says anything else to stop, show the reasons and stop. The most common one is an approval dismissed by the push: tell the person that the repository must not dismiss approvals on new commits (`/relevo:doctor` checks it).
|
|
102
|
+
The push may start new checks or dismiss an approval. Wait for any running checks (`gh pr checks <number> --repo <owner/name> --watch`), then run `ship-check.mjs` again, exactly as in step 1 and adding `--baseline <baseline>`, so an approval given on the head before the archive still counts. When the archive commit has no checks of its own and changes only `openspec/`, `ship-check` judges the checks of `<baseline>` instead, and its summary says `checksFrom: baseline`; do not ask about `no-ci` then. If it now says the pull request has conflicts, see below. If it says anything else to stop, show the reasons and stop. The most common one is an approval dismissed by the push: tell the person that the repository must not dismiss approvals on new commits (`/relevo:doctor` checks it).
|
|
103
103
|
|
|
104
104
|
**The archive left conflicts.** If the only reason is `CONFLICTING`, the base branch changed the specifications the archive wrote. Recover with normal pushes only, never a force-push:
|
|
105
105
|
|
|
@@ -126,3 +126,5 @@ The subject is the pull request title, which already ends with the issue number.
|
|
|
126
126
|
Finish with one short block: the issue, the pull request, the merge commit, that the issue is closed and the labels removed.
|
|
127
127
|
|
|
128
128
|
End with this reminder: promoting `<base>` to `<release>` is a separate pull request, with a version tag. `ship` does not do it.
|
|
129
|
+
|
|
130
|
+
When the repository has the ship workflow (`.github/workflows/relevo-ship.yml`), add one line: next time, the person who ships can apply the `relevo:ship` label to the issue instead, and GitHub ships it with the same checks, without a local copy.
|
package/stacks/vue.md
CHANGED
|
@@ -20,7 +20,7 @@ targets:
|
|
|
20
20
|
|
|
21
21
|
## Verification
|
|
22
22
|
|
|
23
|
-
Run the commands of this profile from the folder of the platform: `analyze` (type check, then linter), `test` and `format`. They use the versions pinned in the project (`npx --no-install`), so the dependencies must be installed; if they are not, say so instead of installing anything new.
|
|
23
|
+
Run the commands of this profile from the folder of the platform: `analyze` (type check, then linter), `test` and `format`. They use the versions pinned in the project (`npx --no-install`), so the dependencies must be installed; if they are not, say so instead of installing anything new. Only the review may install them: it offers the frozen install of the project's lock file (`npm ci --ignore-scripts`) and runs it on a yes.
|
|
24
24
|
|
|
25
25
|
Do not claim the code type-checks, lints or passes tests unless you ran the commands and saw the result. The commands written here are read-only on purpose: the `lint` script that `npm create vue` generates uses `--fix` and rewrites files, so do not run `npm run lint` as a check. Only `format` writes.
|
|
26
26
|
|
|
@@ -57,7 +57,7 @@ Do not spend time re-checking what `analyze` already enforces: read its output.
|
|
|
57
57
|
|
|
58
58
|
## Targets
|
|
59
59
|
|
|
60
|
-
- `web`: needs a browser; no extra tool. The review of a web target is always driven by the agent (`review.autopilot` must be `precheck` or `verify`) with a Playwright script that `qa` reads first; Playwright is a dependency of the project
|
|
60
|
+
- `web`: needs a browser; no extra tool. The review of a web target is always driven by the agent (`review.autopilot` must be `precheck` or `verify`) with a Playwright script that `qa` reads first; Playwright is a development dependency of the project: `/relevo:init` offers to add it (`npm install -D playwright`) and its Chromium (`npx playwright install chromium`), and the review never installs Playwright itself.
|
|
61
61
|
|
|
62
62
|
## Questions for the issue writer
|
|
63
63
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# What `relevo-go.yml` and `relevo-queue.yml` run when `agent.build` in relevo.yml is `antigravity`. This file is not a
|
|
2
2
|
# workflow: each block below replaces, in the shared workflow, the lines between the markers with the same name, and
|
|
3
3
|
# nothing else of the workflow changes (a test checks it). Edit the shared workflow for anything that is not about the
|
|
4
|
-
# agent. Written for
|
|
4
|
+
# agent. Written for an x86-64 Linux runner (the binary below is linux-x64).
|
|
5
5
|
#
|
|
6
6
|
# What Antigravity CLI cannot do on its own, and what stands in for it (docs/security.md):
|
|
7
7
|
# turn and token limits the run guard (scripts/agent-guard.mjs) reads the event stream and stops the agent; the numbers
|
|
@@ -32,10 +32,13 @@
|
|
|
32
32
|
curl -fsSL -o "$RUNNER_TEMP/agy.tar.gz" "$AGY_URL"
|
|
33
33
|
echo "$AGY_SHA512 $RUNNER_TEMP/agy.tar.gz" | sha512sum --check --strict -
|
|
34
34
|
tar -xzf "$RUNNER_TEMP/agy.tar.gz" -C "$RUNNER_TEMP" antigravity
|
|
35
|
-
mkdir -p "$
|
|
36
|
-
install -m 0755 "$RUNNER_TEMP/antigravity" "$
|
|
37
|
-
echo "$
|
|
38
|
-
|
|
35
|
+
mkdir -p "$RUNNER_TEMP/bin" "$RUNNER_TEMP/home"
|
|
36
|
+
install -m 0755 "$RUNNER_TEMP/antigravity" "$RUNNER_TEMP/bin/agy"
|
|
37
|
+
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
|
|
38
|
+
# The CLI, the plugin of the machine and the settings of the run live in a home folder of this job, so a runner
|
|
39
|
+
# that persists keeps none of them. Every later step of the job sees this HOME.
|
|
40
|
+
echo "HOME=$RUNNER_TEMP/home" >> "$GITHUB_ENV"
|
|
41
|
+
[ "$("$RUNNER_TEMP/bin/agy" --version)" = "$AGY_VERSION" ] || { echo "::error::agy is not version $AGY_VERSION"; exit 1; }
|
|
39
42
|
|
|
40
43
|
# The Relevo plugin is rendered from the package unpacked in the step "Check the toolchain" and installed for the
|
|
41
44
|
# machine, so nothing is added to the working tree (the agent needs it clean). The settings of the run go to the
|
package/templates/relevo.yml
CHANGED
|
@@ -28,6 +28,7 @@ review:
|
|
|
28
28
|
merger: approver # author | approver: who runs /relevo:ship
|
|
29
29
|
autopilot: off # off | precheck | verify: how much of the on-device review the agent may drive (the person always closes it)
|
|
30
30
|
allowed_origins: [] # web origins a driven review may visit besides localhost, e.g. https://staging.example.com
|
|
31
|
+
ship_without_ci: false # true: the relevo:ship label may merge when no check ran (/relevo:ship asks instead)
|
|
31
32
|
|
|
32
33
|
limits:
|
|
33
34
|
built_unreviewed_per_person: 2 # built issues one person may have waiting
|
|
@@ -37,6 +38,9 @@ limits:
|
|
|
37
38
|
building_alert_hours: 3 # hours without activity before an issue on `building` is reported as stuck
|
|
38
39
|
run_max_cost: 5 # REQUIRED: US dollars per unattended run; choose your own
|
|
39
40
|
|
|
41
|
+
# runner: # where the Relevo workflows run; leave it out for GitHub-hosted (ubuntu-latest)
|
|
42
|
+
# labels: [self-hosted, Front] # a self-hosted runner is assumed to persist between jobs (docs/execution.md)
|
|
43
|
+
|
|
40
44
|
qa:
|
|
41
45
|
retries: 1 # developer attempts after QA rejects
|
|
42
46
|
rules_from: CLAUDE.md # project rules QA reads at every run
|
|
@@ -4,12 +4,16 @@
|
|
|
4
4
|
# Change the time below to suit your team (cron is in UTC).
|
|
5
5
|
# The scripts come from the published package @elalfner/relevo, at the version pinned in RELEVO_VERSION.
|
|
6
6
|
|
|
7
|
+
# Runner: `runs-on` comes from `runner.labels` in relevo.yml (`/relevo:init` writes it into this file, `/relevo:doctor` checks it).
|
|
8
|
+
# The runner needs git, gh and jq; Node and the tools of the job are installed per job. A self-hosted runner is assumed to
|
|
9
|
+
# persist between jobs, so the job puts its tools under $RUNNER_TEMP and removes what it adds to the workspace.
|
|
10
|
+
|
|
7
11
|
name: relevo-digest
|
|
8
12
|
|
|
9
13
|
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
10
14
|
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
11
15
|
env:
|
|
12
|
-
RELEVO_VERSION: '0.
|
|
16
|
+
RELEVO_VERSION: '0.8.0'
|
|
13
17
|
|
|
14
18
|
on:
|
|
15
19
|
schedule:
|
|
@@ -52,3 +56,8 @@ jobs:
|
|
|
52
56
|
GH_TOKEN: ${{ github.token }}
|
|
53
57
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
54
58
|
run: node .relevo-plugin/scripts/digest.mjs --repo "$GITHUB_REPOSITORY"
|
|
59
|
+
|
|
60
|
+
# A runner may keep its workspace between jobs (a self-hosted one does): remove what this job added to it.
|
|
61
|
+
- name: Remove what this job added
|
|
62
|
+
if: always()
|
|
63
|
+
run: rm -rf .relevo-plugin
|
|
@@ -10,12 +10,18 @@
|
|
|
10
10
|
# Keep the numbers in claude_args in line with limits.run_max_turns and limits.run_max_cost.
|
|
11
11
|
# Where the numbers disagree the lower one wins, so this file can only make a run cheaper than relevo.yml says.
|
|
12
12
|
|
|
13
|
+
# Runner: `runs-on` comes from `runner.labels` in relevo.yml (`/relevo:init` writes it into this file, `/relevo:doctor` checks it).
|
|
14
|
+
# The runner needs git, gh and jq; Node and the tools of the job are installed per job. A self-hosted runner is assumed to
|
|
15
|
+
# persist between jobs, so the job puts its tools under $RUNNER_TEMP and removes what it adds to the workspace.
|
|
16
|
+
|
|
13
17
|
name: relevo-go
|
|
14
18
|
|
|
15
19
|
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
16
20
|
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
17
21
|
env:
|
|
18
|
-
RELEVO_VERSION: '0.
|
|
22
|
+
RELEVO_VERSION: '0.8.0'
|
|
23
|
+
# The version of OpenSpec that the job installs for itself.
|
|
24
|
+
OPENSPEC_VERSION: '1.14.3'
|
|
19
25
|
|
|
20
26
|
on:
|
|
21
27
|
issues:
|
|
@@ -109,6 +115,11 @@ jobs:
|
|
|
109
115
|
gh issue edit "$ISSUE" --remove-label relevo:ready --remove-label relevo:queued --remove-label relevo:building --add-label relevo:qa-failed
|
|
110
116
|
gh issue comment "$ISSUE" --body "${assignee:+@$assignee }The run failed before it could decide what to do (job status: $JOB_STATUS), so nothing was built. Read the log to see why: $RUN_URL. When the cause is fixed, remove \`relevo:qa-failed\` and apply \`relevo:ready\` again."
|
|
111
117
|
|
|
118
|
+
# A runner may keep its workspace between jobs (a self-hosted one does): remove what this job added to it.
|
|
119
|
+
- name: Remove what this job added
|
|
120
|
+
if: always()
|
|
121
|
+
run: rm -rf .relevo-plugin
|
|
122
|
+
|
|
112
123
|
build:
|
|
113
124
|
needs: decide
|
|
114
125
|
if: needs.decide.outputs.action == 'start'
|
|
@@ -162,7 +173,10 @@ jobs:
|
|
|
162
173
|
# the packages, so they need no such option.
|
|
163
174
|
|
|
164
175
|
- name: Install OpenSpec
|
|
165
|
-
|
|
176
|
+
# Under the temporary folder of the job, at the pinned version: nothing global is left on a runner that persists.
|
|
177
|
+
run: |
|
|
178
|
+
npm install --prefix "$RUNNER_TEMP/openspec" "@fission-ai/openspec@$OPENSPEC_VERSION" --no-fund --no-audit
|
|
179
|
+
echo "$RUNNER_TEMP/openspec/node_modules/.bin" >> "$GITHUB_PATH"
|
|
166
180
|
env:
|
|
167
181
|
OPENSPEC_TELEMETRY: '0'
|
|
168
182
|
|
|
@@ -185,12 +199,13 @@ jobs:
|
|
|
185
199
|
if result=$(node "$RUNNER_TEMP/relevo/scripts/toolchain-check.mjs" --platform "$PLATFORM" --plugin-root "$RUNNER_TEMP/relevo"); then
|
|
186
200
|
exit 0
|
|
187
201
|
fi
|
|
188
|
-
|
|
202
|
+
# No jq here: the tool that is missing may be jq itself, and the comment must still be posted.
|
|
203
|
+
missing=$(printf '%s' "$result" | node -e 'console.log(JSON.parse(require("fs").readFileSync(0, "utf8")).missing.join(", "))')
|
|
189
204
|
[ -n "$missing" ] || { echo "::error::The toolchain check failed: $result"; exit 1; }
|
|
190
205
|
title=$(gh issue view "$ISSUE" --json title --jq .title)
|
|
191
206
|
assignee=$(gh issue view "$ISSUE" --json assignees --jq '.assignees[0].login // empty')
|
|
192
|
-
body=$(
|
|
193
|
-
|
|
207
|
+
body=$(ASSIGNEE="$assignee" TITLE="$title" TOOLS="$missing" node -e 'const e = process.env; console.log(JSON.stringify({assignee: e.ASSIGNEE, issue: e.ISSUE, title: e.TITLE, tools: e.TOOLS, platform: e.PLATFORM}))' \
|
|
208
|
+
| node "$RUNNER_TEMP/relevo/scripts/render-message.mjs" toolchain_missing --config relevo.yml)
|
|
194
209
|
gh issue edit "$ISSUE" --remove-label relevo:ready --remove-label relevo:queued --remove-label relevo:building --add-label relevo:qa-failed
|
|
195
210
|
gh issue comment "$ISSUE" --body "$body"
|
|
196
211
|
echo "::error::The runner has no $missing, so nothing was built"
|
|
@@ -300,3 +315,8 @@ jobs:
|
|
|
300
315
|
else
|
|
301
316
|
gh issue comment "$ISSUE" --body "${assignee:+@$assignee }The run ended without a result (job status: $JOB_STATUS), so nothing was built or reported. Read the log to see why: $RUN_URL. When the cause is fixed, remove \`relevo:qa-failed\` and apply \`relevo:ready\` again."
|
|
302
317
|
fi
|
|
318
|
+
|
|
319
|
+
# A runner may keep its workspace between jobs (a self-hosted one does): remove what this job added to it.
|
|
320
|
+
- name: Remove what this job added
|
|
321
|
+
if: always()
|
|
322
|
+
run: rm -rf .relevo-plugin
|
|
@@ -5,12 +5,16 @@
|
|
|
5
5
|
# schedule, so a lost event does not stall the queue. Needs a relevo.yml at the repository root.
|
|
6
6
|
# The scripts come from the published package @elalfner/relevo, at the version pinned in RELEVO_VERSION.
|
|
7
7
|
|
|
8
|
+
# Runner: `runs-on` comes from `runner.labels` in relevo.yml (`/relevo:init` writes it into this file, `/relevo:doctor` checks it).
|
|
9
|
+
# The runner needs git, gh and jq; Node and the tools of the job are installed per job. A self-hosted runner is assumed to
|
|
10
|
+
# persist between jobs, so the job puts its tools under $RUNNER_TEMP and removes what it adds to the workspace.
|
|
11
|
+
|
|
8
12
|
name: relevo-queue
|
|
9
13
|
|
|
10
14
|
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
11
15
|
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
12
16
|
env:
|
|
13
|
-
RELEVO_VERSION: '0.
|
|
17
|
+
RELEVO_VERSION: '0.8.0'
|
|
14
18
|
|
|
15
19
|
on:
|
|
16
20
|
issues:
|
|
@@ -72,3 +76,8 @@ jobs:
|
|
|
72
76
|
for issue in $(echo "$result" | node -e 'JSON.parse(require("fs").readFileSync(0,"utf8")).start.forEach(n=>console.log(n))'); do
|
|
73
77
|
gh workflow run relevo-go.yml --repo "$GITHUB_REPOSITORY" -f issue="$issue"
|
|
74
78
|
done
|
|
79
|
+
|
|
80
|
+
# A runner may keep its workspace between jobs (a self-hosted one does): remove what this job added to it.
|
|
81
|
+
- name: Remove what this job added
|
|
82
|
+
if: always()
|
|
83
|
+
run: rm -rf .relevo-plugin
|
|
@@ -5,12 +5,16 @@
|
|
|
5
5
|
# Pull requests from forks are skipped explicitly (and would only get a read-only token anyway).
|
|
6
6
|
# The scripts come from the published package @elalfner/relevo, at the version pinned in RELEVO_VERSION.
|
|
7
7
|
|
|
8
|
+
# Runner: `runs-on` comes from `runner.labels` in relevo.yml (`/relevo:init` writes it into this file, `/relevo:doctor` checks it).
|
|
9
|
+
# The runner needs git, gh and jq; Node and the tools of the job are installed per job. A self-hosted runner is assumed to
|
|
10
|
+
# persist between jobs, so the job puts its tools under $RUNNER_TEMP and removes what it adds to the workspace.
|
|
11
|
+
|
|
8
12
|
name: relevo-review
|
|
9
13
|
|
|
10
14
|
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
11
15
|
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
12
16
|
env:
|
|
13
|
-
RELEVO_VERSION: '0.
|
|
17
|
+
RELEVO_VERSION: '0.8.0'
|
|
14
18
|
|
|
15
19
|
on:
|
|
16
20
|
pull_request_review:
|
|
@@ -58,3 +62,8 @@ jobs:
|
|
|
58
62
|
GH_TOKEN: ${{ github.token }}
|
|
59
63
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
60
64
|
run: node .relevo-plugin/scripts/review-event.mjs --repo "$GITHUB_REPOSITORY"
|
|
65
|
+
|
|
66
|
+
# A runner may keep its workspace between jobs (a self-hosted one does): remove what this job added to it.
|
|
67
|
+
- name: Remove what this job added
|
|
68
|
+
if: always()
|
|
69
|
+
run: rm -rf .relevo-plugin
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Relevo: ships an issue when a person applies the `relevo:ship` label to it.
|
|
2
|
+
# Copy to .github/workflows/relevo-ship.yml. If you renamed the ship label in relevo.yml, change it below too.
|
|
3
|
+
#
|
|
4
|
+
# It runs the steps of /relevo:ship with the same rules, and no model: it needs no model credential. The person who
|
|
5
|
+
# applied the label is the one judged (they must be the one who ships under review.merger), and the label is their
|
|
6
|
+
# written confirmation. It archives the specification on the branch, squash-merges the pull request, closes the issue
|
|
7
|
+
# and removes its labels. When anything stops it, it comments why on the issue and removes the label. Nothing from the
|
|
8
|
+
# pull request's branch is run: the scripts come from the published package and relevo.yml from the base branch.
|
|
9
|
+
#
|
|
10
|
+
# Checks: a push with the workflow token starts no workflows, so the archive commit gets no checks of its own. The
|
|
11
|
+
# checks of the approved head count for it, because it changes only openspec/. If the base branch REQUIRES status
|
|
12
|
+
# checks, GitHub refuses that merge: create a GitHub App for this repository (contents, pull requests and issues:
|
|
13
|
+
# read and write), store its id as the secret RELEVO_SHIP_APP_ID and its private key as RELEVO_SHIP_APP_KEY. The run
|
|
14
|
+
# then pushes, merges and closes with the App's token, so your CI runs on the archive commit, and waits for it. The App
|
|
15
|
+
# is also worth it when you rely on workflows that run on pushes to the base branch. Never add it to a bypass list.
|
|
16
|
+
#
|
|
17
|
+
# The repository variable RELEVO_CLOUD_SHIP set to `off` pauses it: the label then does nothing.
|
|
18
|
+
# This workflow runs from the default branch; it works only once it is there.
|
|
19
|
+
|
|
20
|
+
# Runner: `runs-on` comes from `runner.labels` in relevo.yml (`/relevo:init` writes it into this file, `/relevo:doctor` checks it).
|
|
21
|
+
# The runner needs git, gh and jq; Node and the tools of the job are installed per job. A self-hosted runner is assumed to
|
|
22
|
+
# persist between jobs, so the job puts its tools under $RUNNER_TEMP and removes what it adds to the workspace.
|
|
23
|
+
|
|
24
|
+
name: relevo-ship
|
|
25
|
+
|
|
26
|
+
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
27
|
+
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
28
|
+
env:
|
|
29
|
+
RELEVO_VERSION: '0.8.0'
|
|
30
|
+
# The version of OpenSpec that the job installs for itself.
|
|
31
|
+
OPENSPEC_VERSION: '1.14.3'
|
|
32
|
+
|
|
33
|
+
on:
|
|
34
|
+
issues:
|
|
35
|
+
types: [labeled]
|
|
36
|
+
|
|
37
|
+
# One ship per issue: a second label event waits for the first run and then finds the label gone or the issue closed.
|
|
38
|
+
concurrency:
|
|
39
|
+
group: relevo-ship-${{ github.event.issue.number }}
|
|
40
|
+
cancel-in-progress: false
|
|
41
|
+
|
|
42
|
+
permissions:
|
|
43
|
+
contents: write
|
|
44
|
+
issues: write
|
|
45
|
+
pull-requests: write
|
|
46
|
+
# To start the queue (relevo-queue.yml) after the close, which the workflow token's close does not start by itself.
|
|
47
|
+
actions: write
|
|
48
|
+
|
|
49
|
+
jobs:
|
|
50
|
+
ship:
|
|
51
|
+
# Any sender: a label applied by a bot is removed by the script without shipping anything.
|
|
52
|
+
if: vars.RELEVO_CLOUD_SHIP != 'off' && github.event.label.name == 'relevo:ship' && !github.event.issue.pull_request
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
# The run may wait up to 30 minutes for the checks of the archive commit.
|
|
55
|
+
timeout-minutes: 45
|
|
56
|
+
env:
|
|
57
|
+
HAS_APP: ${{ secrets.RELEVO_SHIP_APP_ID != '' && secrets.RELEVO_SHIP_APP_KEY != '' }}
|
|
58
|
+
steps:
|
|
59
|
+
# Only with the App: a token for this repository, used to push, merge and close.
|
|
60
|
+
- name: App token
|
|
61
|
+
id: app
|
|
62
|
+
if: env.HAS_APP == 'true'
|
|
63
|
+
uses: actions/create-github-app-token@v1
|
|
64
|
+
with:
|
|
65
|
+
app-id: ${{ secrets.RELEVO_SHIP_APP_ID }}
|
|
66
|
+
private-key: ${{ secrets.RELEVO_SHIP_APP_KEY }}
|
|
67
|
+
|
|
68
|
+
# The default branch, which holds this workflow and names the base branch. The script switches to the feature
|
|
69
|
+
# branch in this checkout only to run git and OpenSpec on it; it never installs or runs anything from it.
|
|
70
|
+
- uses: actions/checkout@v4
|
|
71
|
+
with:
|
|
72
|
+
path: work
|
|
73
|
+
fetch-depth: 0
|
|
74
|
+
token: ${{ steps.app.outputs.token || github.token }}
|
|
75
|
+
|
|
76
|
+
- uses: actions/setup-node@v4
|
|
77
|
+
with:
|
|
78
|
+
node-version: 22
|
|
79
|
+
|
|
80
|
+
- name: Install the Relevo scripts
|
|
81
|
+
# The published package at the version pinned above, unpacked outside the checkout so that the archive sees
|
|
82
|
+
# a clean working tree. A package without a script this job runs stops the run here, before anything changes.
|
|
83
|
+
run: |
|
|
84
|
+
mkdir -p "$RUNNER_TEMP/relevo"
|
|
85
|
+
npm pack "@elalfner/relevo@$RELEVO_VERSION" --silent --pack-destination "$RUNNER_TEMP" > /dev/null
|
|
86
|
+
tar -xzf "$RUNNER_TEMP"/elalfner-relevo-*.tgz --strip-components=1 -C "$RUNNER_TEMP/relevo"
|
|
87
|
+
for f in scripts/ship-unattended.mjs scripts/scan-diff.mjs scripts/check-archive.mjs scripts/stage.mjs scripts/fix-tasks.mjs scripts/conflict-scope.mjs scripts/render-message.mjs; do
|
|
88
|
+
[ -f "$RUNNER_TEMP/relevo/$f" ] || { echo "::error::The Relevo package $RELEVO_VERSION lacks $f"; exit 1; }
|
|
89
|
+
done
|
|
90
|
+
|
|
91
|
+
- name: Install OpenSpec
|
|
92
|
+
# Under the temporary folder of the job, at the pinned version: nothing global is left on a runner that persists.
|
|
93
|
+
run: |
|
|
94
|
+
npm install --prefix "$RUNNER_TEMP/openspec" "@fission-ai/openspec@$OPENSPEC_VERSION" --no-fund --no-audit
|
|
95
|
+
echo "$RUNNER_TEMP/openspec/node_modules/.bin" >> "$GITHUB_PATH"
|
|
96
|
+
env:
|
|
97
|
+
OPENSPEC_TELEMETRY: '0'
|
|
98
|
+
|
|
99
|
+
- name: Ship
|
|
100
|
+
working-directory: work
|
|
101
|
+
env:
|
|
102
|
+
GH_TOKEN: ${{ github.token }}
|
|
103
|
+
GH_PUSH_TOKEN: ${{ steps.app.outputs.token || github.token }}
|
|
104
|
+
OPENSPEC_TELEMETRY: '0'
|
|
105
|
+
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
106
|
+
ISSUE: ${{ github.event.issue.number }}
|
|
107
|
+
ACTOR: ${{ github.event.sender.login }}
|
|
108
|
+
ACTOR_TYPE: ${{ github.event.sender.type }}
|
|
109
|
+
run: |
|
|
110
|
+
git config user.name 'github-actions[bot]'
|
|
111
|
+
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
|
|
112
|
+
node "$RUNNER_TEMP/relevo/scripts/ship-unattended.mjs" --repo "$GITHUB_REPOSITORY" --issue "$ISSUE" --actor "$ACTOR" --actor-type "$ACTOR_TYPE" --work .
|
|
113
|
+
|
|
114
|
+
# A run that fails before it could comment (the package cannot be installed, a git or GitHub error) leaves the
|
|
115
|
+
# label on the issue with nobody told. If the label is still there, remove it and say so.
|
|
116
|
+
# If you renamed the ship label in relevo.yml, change the name below too.
|
|
117
|
+
- name: Report a run that ended without an outcome
|
|
118
|
+
if: always()
|
|
119
|
+
env:
|
|
120
|
+
GH_TOKEN: ${{ github.token }}
|
|
121
|
+
ISSUE: ${{ github.event.issue.number }}
|
|
122
|
+
ACTOR: ${{ github.event.sender.login }}
|
|
123
|
+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
124
|
+
run: |
|
|
125
|
+
labels=$(gh issue view "$ISSUE" --json labels --jq '.labels[].name')
|
|
126
|
+
if ! printf '%s\n' "$labels" | grep -qx 'relevo:ship'; then
|
|
127
|
+
exit 0
|
|
128
|
+
fi
|
|
129
|
+
gh issue edit "$ISSUE" --remove-label relevo:ship
|
|
130
|
+
body=""
|
|
131
|
+
if [ -f "$RUNNER_TEMP/relevo/scripts/render-message.mjs" ]; then
|
|
132
|
+
body=$(jq -n --arg issue "$ISSUE" --arg run "$RUN_URL" --arg actor "$ACTOR" '{issue: $issue, run: $run, actor: $actor}' | node "$RUNNER_TEMP/relevo/scripts/render-message.mjs" ship_failed --config work/relevo.yml) || body=""
|
|
133
|
+
fi
|
|
134
|
+
[ -n "$body" ] || body="@$ACTOR #$ISSUE was not shipped: the run ended without an outcome. Read the log to see why: $RUN_URL"
|
|
135
|
+
gh issue comment "$ISSUE" --body "$body"
|
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
# Copy to .github/workflows/relevo-sla.yml. Needs a relevo.yml at the repository root.
|
|
4
4
|
# The scripts come from the published package @elalfner/relevo, at the version pinned in RELEVO_VERSION.
|
|
5
5
|
|
|
6
|
+
# Runner: `runs-on` comes from `runner.labels` in relevo.yml (`/relevo:init` writes it into this file, `/relevo:doctor` checks it).
|
|
7
|
+
# The runner needs git, gh and jq; Node and the tools of the job are installed per job. A self-hosted runner is assumed to
|
|
8
|
+
# persist between jobs, so the job puts its tools under $RUNNER_TEMP and removes what it adds to the workspace.
|
|
9
|
+
|
|
6
10
|
name: relevo-sla
|
|
7
11
|
|
|
8
12
|
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
9
13
|
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
10
14
|
env:
|
|
11
|
-
RELEVO_VERSION: '0.
|
|
15
|
+
RELEVO_VERSION: '0.8.0'
|
|
12
16
|
|
|
13
17
|
on:
|
|
14
18
|
schedule:
|
|
@@ -51,3 +55,8 @@ jobs:
|
|
|
51
55
|
GH_TOKEN: ${{ github.token }}
|
|
52
56
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
53
57
|
run: node .relevo-plugin/scripts/review-sla.mjs --repo "$GITHUB_REPOSITORY"
|
|
58
|
+
|
|
59
|
+
# A runner may keep its workspace between jobs (a self-hosted one does): remove what this job added to it.
|
|
60
|
+
- name: Remove what this job added
|
|
61
|
+
if: always()
|
|
62
|
+
run: rm -rf .relevo-plugin
|