@elalfner/relevo 0.9.1 → 0.9.3
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/lib/doctor.mjs +2 -2
- package/scripts/lib/init.mjs +1 -1
- package/scripts/lib/ship-github.mjs +24 -8
- package/scripts/lib/ship-unattended.mjs +3 -2
- package/skills/init/SKILL.md +1 -1
- package/templates/workflows/relevo-digest.yml +2 -2
- package/templates/workflows/relevo-go.yml +9 -4
- package/templates/workflows/relevo-queue.yml +2 -2
- package/templates/workflows/relevo-review.yml +2 -2
- package/templates/workflows/relevo-ship.yml +6 -2
- package/templates/workflows/relevo-sla.yml +2 -2
package/package.json
CHANGED
package/scripts/lib/doctor.mjs
CHANGED
|
@@ -670,7 +670,7 @@ export function runChecks(f) {
|
|
|
670
670
|
if (f.protectionError) {
|
|
671
671
|
r.push(warn('repo.protection', `could not read the branch rules (${f.protectionError}), so they were not checked`, 'run doctor again; if it keeps failing, check your access to the repository'));
|
|
672
672
|
} else if (f.protectionUnavailable) {
|
|
673
|
-
r.push(
|
|
673
|
+
r.push(note('repo.protection', `no branch protection is available on this plan (${f.protectionUnavailable}): approval and merge are only enforced by the written confirmation in \`ship\`; making the repository public or upgrading the plan would add it`));
|
|
674
674
|
} else {
|
|
675
675
|
[['base', f.config?.branches?.base ?? 'base'], ['release', f.config?.branches?.release ?? 'release']].forEach(([key, name]) => {
|
|
676
676
|
const p = f.protection?.[key];
|
|
@@ -767,7 +767,7 @@ function shipChecks(f) {
|
|
|
767
767
|
|
|
768
768
|
// The action that sets up the toolchain of a stack in the build job of the go workflow. Vue needs only Node, which the
|
|
769
769
|
// job installs for Relevo itself, and a stack that is not listed here is not judged.
|
|
770
|
-
const TOOLCHAIN_ACTIONS = { flutter: { action: 'subosito/flutter-action', step: 'the Flutter step' }, laravel: { action: 'shivammathur/setup-php', step: 'the Laravel step' } };
|
|
770
|
+
const TOOLCHAIN_ACTIONS = { flutter: { action: 'subosito/flutter-action', step: 'the Flutter step' }, laravel: { action: 'shivammathur/setup-php', step: 'the Laravel step' }, supabase: { action: 'supabase/setup-cli', step: 'the Supabase step' } };
|
|
771
771
|
|
|
772
772
|
/**
|
|
773
773
|
* Does the go workflow install the toolchain of each configured platform? Without it every cloud build fails QA after
|
package/scripts/lib/init.mjs
CHANGED
|
@@ -334,7 +334,7 @@ export function planInit({ facts, answers, templates, current, admin, upgrades =
|
|
|
334
334
|
items.push(item(`workflow.${file}`, 'differs', `.github/workflows/${file} differs from the template and could not be checked for an update (${up.message}); it is left as it is`));
|
|
335
335
|
} else {
|
|
336
336
|
// The cloud build only passes QA when the build job installs the toolchain of the stack, which the template does.
|
|
337
|
-
const toolchain = file === 'relevo-go.yml' ? '; check that its build job installs the toolchain of your stacks, as the template does for Flutter and
|
|
337
|
+
const toolchain = file === 'relevo-go.yml' ? '; check that its build job installs the toolchain of your stacks, as the template does for Flutter, Laravel and Supabase' : '';
|
|
338
338
|
items.push(item(`workflow.${file}`, 'differs', `.github/workflows/${file} exists and differs from the template; it is left as it is (compare it by hand${toolchain})`));
|
|
339
339
|
}
|
|
340
340
|
});
|
|
@@ -35,7 +35,17 @@ export function loadPr(gh, repo, branch) {
|
|
|
35
35
|
return open ? JSON.parse(gh(['pr', 'view', String(open.number), '--repo', repo, '--json', PR_FIELDS])) : null;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const
|
|
38
|
+
const errorText = (err) => `${err?.stderr ?? ''}\n${err?.message ?? err}`;
|
|
39
|
+
const notFound = (err) => /404|Not Found/i.test(errorText(err));
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* True when GitHub refuses a branch-rules call because of the plan or the visibility of the repository (a private
|
|
43
|
+
* repository on the free plan has no rulesets and no protection), not because of the token or a fault of its own.
|
|
44
|
+
*/
|
|
45
|
+
export function planRefusal(err) {
|
|
46
|
+
const text = errorText(err);
|
|
47
|
+
return /HTTP 403/.test(text) && /Upgrade to GitHub|make this repository public/i.test(text);
|
|
48
|
+
}
|
|
39
49
|
|
|
40
50
|
/** True when every file that changed from `baseline` to `head` is under `openspec/`, and something changed. */
|
|
41
51
|
export function changedOnlySpecs(gh, repo, baseline, head) {
|
|
@@ -62,16 +72,22 @@ export function baselineChecks(gh, repo, baseline, head) {
|
|
|
62
72
|
|
|
63
73
|
/**
|
|
64
74
|
* Whether merging into `branch` requires status checks, by a ruleset or by classic branch protection.
|
|
65
|
-
* Returns { required: boolean, contexts: [names] }. A branch without protection requires none
|
|
75
|
+
* Returns { required: boolean, contexts: [names] }. A branch without protection requires none, and neither does one
|
|
76
|
+
* whose rules the plan of the repository does not offer. Any other failure to read them is thrown: checks that exist
|
|
77
|
+
* must not be skipped because they could not be read.
|
|
66
78
|
*/
|
|
67
79
|
export function requiredChecks(gh, repo, branch) {
|
|
68
80
|
const contexts = new Set();
|
|
69
81
|
let required = false;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
82
|
+
try {
|
|
83
|
+
const rules = JSON.parse(gh(['api', `repos/${repo}/rules/branches/${encodeURIComponent(branch)}`]));
|
|
84
|
+
(Array.isArray(rules) ? rules : []).filter((r) => r.type === 'required_status_checks').forEach((r) => {
|
|
85
|
+
required = true;
|
|
86
|
+
(r.parameters?.required_status_checks ?? []).forEach((c) => contexts.add(c.context));
|
|
87
|
+
});
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (!planRefusal(err)) throw err;
|
|
90
|
+
}
|
|
75
91
|
try {
|
|
76
92
|
const protection = JSON.parse(gh(['api', `repos/${repo}/branches/${encodeURIComponent(branch)}/protection`]));
|
|
77
93
|
const classic = protection.required_status_checks;
|
|
@@ -81,7 +97,7 @@ export function requiredChecks(gh, repo, branch) {
|
|
|
81
97
|
(classic.checks ?? []).forEach((c) => contexts.add(c.context));
|
|
82
98
|
}
|
|
83
99
|
} catch (err) {
|
|
84
|
-
if (!notFound(err)) throw err;
|
|
100
|
+
if (!notFound(err) && !planRefusal(err)) throw err;
|
|
85
101
|
}
|
|
86
102
|
return { required, contexts: [...contexts] };
|
|
87
103
|
}
|
|
@@ -262,8 +262,9 @@ export async function runUnattendedShip(ctx) {
|
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
264
|
return { outcome: 'merged', pr: pr.number, commit };
|
|
265
|
-
} catch (
|
|
266
|
-
|
|
265
|
+
} catch (caught) {
|
|
266
|
+
// An error the run did not foresee (a refusal of GitHub, for example) stops it like any other reason, with its message.
|
|
267
|
+
const err = caught instanceof Stop ? caught : new Stop('ship_stopped', { reasons: `unexpected error: ${firstLines(caught?.stderr || caught?.message || caught)}` });
|
|
267
268
|
removeShipLabel();
|
|
268
269
|
gh(['issue', 'comment', n, '--repo', repo, '--body', renderMessage(err.kind, { actor, issue: n, ...err.data }, config.language)]);
|
|
269
270
|
return { outcome: 'stopped', kind: err.kind, ...err.data };
|
package/skills/init/SKILL.md
CHANGED
|
@@ -34,7 +34,7 @@ If `relevo.yml` already exists and is valid, skip this step. Otherwise ask, in s
|
|
|
34
34
|
- who builds and reviews (GitHub usernames) and who writes the specs;
|
|
35
35
|
- the review mode: `none` (a solo project), `peer` or `roles`;
|
|
36
36
|
- the language of the hand-off messages (`en` or `es`);
|
|
37
|
-
- 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
|
|
37
|
+
- 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 supported: each issue names the platform it is for with a `Platform:` line, and `go` builds that one;
|
|
38
38
|
- 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;
|
|
39
39
|
- 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 `linux-build`): 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;
|
|
40
40
|
- 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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# relevo-template: 0.9.
|
|
1
|
+
# relevo-template: 0.9.3
|
|
2
2
|
# Relevo: posts one digest per working day, as a comment on the issue named by digest.issue in relevo.yml.
|
|
3
3
|
# It lists who is waiting on what and the alerts, and posts nothing when nothing is waiting.
|
|
4
4
|
# Subscribe your chat tool to that issue to receive it. Copy to .github/workflows/relevo-digest.yml.
|
|
@@ -14,7 +14,7 @@ name: relevo-digest
|
|
|
14
14
|
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
15
15
|
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
16
16
|
env:
|
|
17
|
-
RELEVO_VERSION: '0.9.
|
|
17
|
+
RELEVO_VERSION: '0.9.3'
|
|
18
18
|
|
|
19
19
|
on:
|
|
20
20
|
schedule:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# relevo-template: 0.9.
|
|
1
|
+
# relevo-template: 0.9.3
|
|
2
2
|
# Relevo: builds an issue when someone with write access labels it `relevo:ready`.
|
|
3
3
|
# Copy to .github/workflows/relevo-go.yml. If you renamed the ready label in relevo.yml, change it below too.
|
|
4
4
|
#
|
|
@@ -20,7 +20,7 @@ name: relevo-go
|
|
|
20
20
|
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
21
21
|
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
22
22
|
env:
|
|
23
|
-
RELEVO_VERSION: '0.9.
|
|
23
|
+
RELEVO_VERSION: '0.9.3'
|
|
24
24
|
# The version of OpenSpec that the job installs for itself.
|
|
25
25
|
OPENSPEC_VERSION: '1.14.3'
|
|
26
26
|
|
|
@@ -143,8 +143,8 @@ jobs:
|
|
|
143
143
|
# issue is installed here. The `decide` job tells which one (`needs.decide.outputs.stack`) and where it lives
|
|
144
144
|
# (`needs.decide.outputs.path`), so an issue for the front does not install the back's tools: a step whose stack
|
|
145
145
|
# is not the issue's is skipped and costs nothing. The names are the `stack` of relevo.yml. Change the versions
|
|
146
|
-
# and the caching to suit the project; `/relevo:doctor` fails when a Flutter or
|
|
147
|
-
# step below, because every build would fail QA after spending the run.
|
|
146
|
+
# and the caching to suit the project; `/relevo:doctor` fails when a Flutter, Laravel or Supabase platform has no
|
|
147
|
+
# active step below, because every build would fail QA after spending the run.
|
|
148
148
|
- if: needs.decide.outputs.stack == 'flutter'
|
|
149
149
|
uses: subosito/flutter-action@v2
|
|
150
150
|
with:
|
|
@@ -155,6 +155,11 @@ jobs:
|
|
|
155
155
|
with:
|
|
156
156
|
php-version: '8.3'
|
|
157
157
|
tools: composer
|
|
158
|
+
# Supabase: the CLI runs the checks of the profile against containers on the runner, which already has Docker.
|
|
159
|
+
- if: needs.decide.outputs.stack == 'supabase'
|
|
160
|
+
uses: supabase/setup-cli@v1
|
|
161
|
+
with:
|
|
162
|
+
version: latest
|
|
158
163
|
# Vue needs only Node, which this job installed above; to cache the packages, uncomment:
|
|
159
164
|
# - if: needs.decide.outputs.stack == 'vue'
|
|
160
165
|
# uses: actions/setup-node@v4
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# relevo-template: 0.9.
|
|
1
|
+
# relevo-template: 0.9.3
|
|
2
2
|
# Relevo: re-evaluates the queue and starts the issues that are now eligible.
|
|
3
3
|
# Copy to .github/workflows/relevo-queue.yml next to relevo-go.yml (it starts builds through that workflow).
|
|
4
4
|
#
|
|
@@ -15,7 +15,7 @@ name: relevo-queue
|
|
|
15
15
|
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
16
16
|
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
17
17
|
env:
|
|
18
|
-
RELEVO_VERSION: '0.9.
|
|
18
|
+
RELEVO_VERSION: '0.9.3'
|
|
19
19
|
|
|
20
20
|
on:
|
|
21
21
|
issues:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# relevo-template: 0.9.
|
|
1
|
+
# relevo-template: 0.9.3
|
|
2
2
|
# Relevo: keeps the issue label in step with its pull request.
|
|
3
3
|
# A review that requests changes sets `relevo:changes-requested` and mentions the author; a new push returns the
|
|
4
4
|
# issue to `relevo:pr-open`; an approval mentions the person who will run /relevo:ship.
|
|
@@ -15,7 +15,7 @@ name: relevo-review
|
|
|
15
15
|
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
16
16
|
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
17
17
|
env:
|
|
18
|
-
RELEVO_VERSION: '0.9.
|
|
18
|
+
RELEVO_VERSION: '0.9.3'
|
|
19
19
|
|
|
20
20
|
on:
|
|
21
21
|
pull_request_review:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# relevo-template: 0.9.
|
|
1
|
+
# relevo-template: 0.9.3
|
|
2
2
|
# Relevo: ships an issue when a person applies the `relevo:ship` label to it.
|
|
3
3
|
# Copy to .github/workflows/relevo-ship.yml. If you renamed the ship label in relevo.yml, change it below too.
|
|
4
4
|
#
|
|
@@ -27,7 +27,7 @@ name: relevo-ship
|
|
|
27
27
|
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
28
28
|
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
29
29
|
env:
|
|
30
|
-
RELEVO_VERSION: '0.9.
|
|
30
|
+
RELEVO_VERSION: '0.9.3'
|
|
31
31
|
# The version of OpenSpec that the job installs for itself.
|
|
32
32
|
OPENSPEC_VERSION: '1.14.3'
|
|
33
33
|
|
|
@@ -44,6 +44,10 @@ permissions:
|
|
|
44
44
|
contents: write
|
|
45
45
|
issues: write
|
|
46
46
|
pull-requests: write
|
|
47
|
+
# To read the checks of the approved head (the status check rollup of a commit); without them the run stops on
|
|
48
|
+
# "Resource not accessible by integration".
|
|
49
|
+
checks: read
|
|
50
|
+
statuses: read
|
|
47
51
|
# To start the queue (relevo-queue.yml) after the close, which the workflow token's close does not start by itself.
|
|
48
52
|
actions: write
|
|
49
53
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# relevo-template: 0.9.
|
|
1
|
+
# relevo-template: 0.9.3
|
|
2
2
|
# Relevo: reminds the reviewer when a pull request waits longer than limits.review_sla_hours, and escalates
|
|
3
3
|
# to the spec writer (adding the needs-reviewer label) after the same time again.
|
|
4
4
|
# Copy to .github/workflows/relevo-sla.yml. Needs a relevo.yml at the repository root.
|
|
@@ -13,7 +13,7 @@ name: relevo-sla
|
|
|
13
13
|
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
14
14
|
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
15
15
|
env:
|
|
16
|
-
RELEVO_VERSION: '0.9.
|
|
16
|
+
RELEVO_VERSION: '0.9.3'
|
|
17
17
|
|
|
18
18
|
on:
|
|
19
19
|
schedule:
|