@elalfner/relevo 0.0.0-stage → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +19 -0
- package/LICENSE +21 -0
- package/NOTICE +51 -0
- package/README.es.md +90 -0
- package/README.md +89 -2
- package/agents/dev.md +101 -0
- package/agents/qa.md +112 -0
- package/package.json +42 -4
- package/scripts/add-corrections.mjs +21 -0
- package/scripts/app-id.mjs +19 -0
- package/scripts/archive-state.mjs +31 -0
- package/scripts/branch-state.mjs +31 -0
- package/scripts/branch-work.mjs +25 -0
- package/scripts/can-edit-issue.mjs +45 -0
- package/scripts/capture.mjs +22 -0
- package/scripts/check-archive.mjs +19 -0
- package/scripts/check-issue.mjs +41 -0
- package/scripts/checklist.mjs +29 -0
- package/scripts/cli.mjs +28 -0
- package/scripts/commit-change.mjs +50 -0
- package/scripts/conflict-scope.mjs +19 -0
- package/scripts/design-fetch.mjs +103 -0
- package/scripts/device.mjs +12 -0
- package/scripts/digest.mjs +50 -0
- package/scripts/doctor.mjs +47 -0
- package/scripts/failure-comment.mjs +30 -0
- package/scripts/fix-guard.mjs +38 -0
- package/scripts/fix-stash.mjs +57 -0
- package/scripts/fix-state.mjs +28 -0
- package/scripts/fix-tasks.mjs +22 -0
- package/scripts/go-local.mjs +49 -0
- package/scripts/go-plan.mjs +82 -0
- package/scripts/init.mjs +109 -0
- package/scripts/intent-to-add.mjs +20 -0
- package/scripts/issue-coverage.mjs +30 -0
- package/scripts/issue-hash.mjs +44 -0
- package/scripts/issue-sections.mjs +42 -0
- package/scripts/label-change.mjs +35 -0
- package/scripts/lib/appid.mjs +12 -0
- package/scripts/lib/archive-check.mjs +29 -0
- package/scripts/lib/archive-state.mjs +15 -0
- package/scripts/lib/branch-work.mjs +13 -0
- package/scripts/lib/branch.mjs +43 -0
- package/scripts/lib/can-edit-issue.mjs +34 -0
- package/scripts/lib/checklist.mjs +68 -0
- package/scripts/lib/cli.mjs +82 -0
- package/scripts/lib/commit-change.mjs +57 -0
- package/scripts/lib/config.mjs +296 -0
- package/scripts/lib/design-fetch.mjs +156 -0
- package/scripts/lib/device.mjs +67 -0
- package/scripts/lib/doctor.mjs +636 -0
- package/scripts/lib/execution-mode.mjs +24 -0
- package/scripts/lib/failure-comment.mjs +19 -0
- package/scripts/lib/fix-guard.mjs +23 -0
- package/scripts/lib/fix-stash.mjs +17 -0
- package/scripts/lib/fix.mjs +31 -0
- package/scripts/lib/github.mjs +161 -0
- package/scripts/lib/go-local.mjs +102 -0
- package/scripts/lib/init.mjs +328 -0
- package/scripts/lib/intent-to-add.mjs +32 -0
- package/scripts/lib/issue-check.mjs +63 -0
- package/scripts/lib/issue-coverage.mjs +47 -0
- package/scripts/lib/issue-hash.mjs +25 -0
- package/scripts/lib/issue-parse.mjs +194 -0
- package/scripts/lib/issue-sections.mjs +43 -0
- package/scripts/lib/labels.mjs +65 -0
- package/scripts/lib/language.mjs +9 -0
- package/scripts/lib/logaudit.mjs +123 -0
- package/scripts/lib/messages.mjs +341 -0
- package/scripts/lib/next-step.mjs +153 -0
- package/scripts/lib/openspec-commands.mjs +44 -0
- package/scripts/lib/openspec-status.mjs +16 -0
- package/scripts/lib/plan.mjs +256 -0
- package/scripts/lib/platform-of.mjs +32 -0
- package/scripts/lib/platform-scope.mjs +32 -0
- package/scripts/lib/platforms.mjs +90 -0
- package/scripts/lib/preflight.mjs +71 -0
- package/scripts/lib/protected-paths.mjs +102 -0
- package/scripts/lib/requeue.mjs +23 -0
- package/scripts/lib/review-adapters.mjs +37 -0
- package/scripts/lib/review-brief.mjs +43 -0
- package/scripts/lib/review-events.mjs +56 -0
- package/scripts/lib/review-state.mjs +157 -0
- package/scripts/lib/reviewers.mjs +39 -0
- package/scripts/lib/role-skills.mjs +112 -0
- package/scripts/lib/run-flags.mjs +16 -0
- package/scripts/lib/run-usage.mjs +65 -0
- package/scripts/lib/scan.mjs +73 -0
- package/scripts/lib/ship.mjs +161 -0
- package/scripts/lib/sla.mjs +90 -0
- package/scripts/lib/stack-profile.mjs +169 -0
- package/scripts/lib/staging.mjs +21 -0
- package/scripts/lib/status-data.mjs +109 -0
- package/scripts/lib/status.mjs +199 -0
- package/scripts/lib/tasks-file.mjs +81 -0
- package/scripts/lib/web-review.mjs +98 -0
- package/scripts/lib/wip-commit.mjs +24 -0
- package/scripts/lib/yaml-lite.mjs +157 -0
- package/scripts/needs-reviewer.mjs +41 -0
- package/scripts/next-step.mjs +39 -0
- package/scripts/pick-reviewer.mjs +42 -0
- package/scripts/platform-of.mjs +49 -0
- package/scripts/preflight.mjs +32 -0
- package/scripts/render-message.mjs +35 -0
- package/scripts/requeue.mjs +53 -0
- package/scripts/review-adapter.mjs +32 -0
- package/scripts/review-brief.mjs +29 -0
- package/scripts/review-event.mjs +70 -0
- package/scripts/review-sla.mjs +41 -0
- package/scripts/review-state.mjs +171 -0
- package/scripts/review-who.mjs +21 -0
- package/scripts/role-skills.mjs +53 -0
- package/scripts/run-usage.mjs +22 -0
- package/scripts/scan-diff.mjs +16 -0
- package/scripts/ship-check.mjs +54 -0
- package/scripts/spec-ready.mjs +40 -0
- package/scripts/stack-profile.mjs +67 -0
- package/scripts/stage.mjs +14 -0
- package/scripts/status.mjs +47 -0
- package/scripts/usage-limit.mjs +17 -0
- package/scripts/validate-config.mjs +44 -0
- package/scripts/web-review.mjs +134 -0
- package/skills/doctor/SKILL.md +28 -0
- package/skills/fix/SKILL.md +129 -0
- package/skills/go/SKILL.md +250 -0
- package/skills/init/SKILL.md +72 -0
- package/skills/issue/SKILL.md +128 -0
- package/skills/issue/references/update.md +15 -0
- package/skills/review/SKILL.md +157 -0
- package/skills/ship/SKILL.md +128 -0
- package/skills/status/SKILL.md +27 -0
- package/skills/think/SKILL.md +76 -0
- package/stacks/flutter.md +63 -0
- package/stacks/laravel.md +65 -0
- package/stacks/vue.md +70 -0
- package/templates/issue.md +99 -0
- package/templates/relevo.yml +59 -0
- package/templates/workflows/relevo-digest.yml +54 -0
- package/templates/workflows/relevo-go.yml +268 -0
- package/templates/workflows/relevo-queue.yml +72 -0
- package/templates/workflows/relevo-review.yml +60 -0
- package/templates/workflows/relevo-sla.yml +53 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
## Summary
|
|
2
|
+
|
|
3
|
+
<!-- What this does, who it is for and why, in two or three sentences. -->
|
|
4
|
+
|
|
5
|
+
## Outcome
|
|
6
|
+
|
|
7
|
+
<!-- Optional. The observable result that shows the issue is done, in one or two sentences. -->
|
|
8
|
+
|
|
9
|
+
## Scenarios
|
|
10
|
+
|
|
11
|
+
- **WHEN** <the situation>
|
|
12
|
+
- **THEN** <the result a person can observe>
|
|
13
|
+
|
|
14
|
+
## Edge cases
|
|
15
|
+
|
|
16
|
+
- <unusual input or state>: <what must happen>
|
|
17
|
+
|
|
18
|
+
## Out of scope
|
|
19
|
+
|
|
20
|
+
- <what is deliberately left out, with the issue that covers it when there is one>
|
|
21
|
+
|
|
22
|
+
<!-- Optional sections: delete what does not apply. Headings stay in English; write the contents in the language of the repository. -->
|
|
23
|
+
|
|
24
|
+
## Decisions
|
|
25
|
+
|
|
26
|
+
<!-- Optional. One block per decision the agreement settled, with an id. Words that were rejected go under Discarded. -->
|
|
27
|
+
|
|
28
|
+
### D1. <decision title>
|
|
29
|
+
Decided: <what was decided>
|
|
30
|
+
Why: <the reason>
|
|
31
|
+
Discarded: <the alternative> (<why not>)
|
|
32
|
+
|
|
33
|
+
## Constraints
|
|
34
|
+
|
|
35
|
+
<!-- Optional. A concrete limit per line, with an id: security, performance, data, integration, business rule or accessibility. -->
|
|
36
|
+
|
|
37
|
+
- C1: <a concrete limit>
|
|
38
|
+
|
|
39
|
+
## Code pointers
|
|
40
|
+
|
|
41
|
+
<!-- Optional. What the exploration found in the code; the builder checks it before trusting it. -->
|
|
42
|
+
|
|
43
|
+
- `<path/to/file>`: <why it matters>
|
|
44
|
+
|
|
45
|
+
## Verification
|
|
46
|
+
|
|
47
|
+
<!-- Optional. What only a person can check on a device or in a browser. Test data by name, never secrets. -->
|
|
48
|
+
|
|
49
|
+
- <what only a person can check, and with which data>
|
|
50
|
+
|
|
51
|
+
## Assumptions
|
|
52
|
+
|
|
53
|
+
<!-- Optional. What the person approved without being sure. -->
|
|
54
|
+
|
|
55
|
+
- <an assumption>
|
|
56
|
+
|
|
57
|
+
## Open questions
|
|
58
|
+
|
|
59
|
+
<!-- Anything written here sends the issue back to its writer: answer it first, then delete the section. -->
|
|
60
|
+
|
|
61
|
+
## Screens and behaviour
|
|
62
|
+
|
|
63
|
+
<!-- Where it appears, what it looks like, when it shows or hides, animation and feedback, loading and error states, reference image. -->
|
|
64
|
+
|
|
65
|
+
## Data contract
|
|
66
|
+
|
|
67
|
+
<!-- For each endpoint or data source: method and path, how the caller authenticates, request fields, a realistic response with types, and which values may be null. -->
|
|
68
|
+
|
|
69
|
+
## Design reference
|
|
70
|
+
|
|
71
|
+
<!-- Optional. What the screen must look like: one bullet per reference, saying what must match (structure, spacing, typography, colour, states). A file of this repository, a picture pasted into this issue, a file of a linked repository written owner/repo:path, or example code in a fenced block. At most five. -->
|
|
72
|
+
|
|
73
|
+
- `<path/to/reference>`: <what must match>
|
|
74
|
+
|
|
75
|
+
## Documentation
|
|
76
|
+
|
|
77
|
+
<!-- Optional. Only when the agreement settled terms or decisions to record; leave it out otherwise. Exact text, nothing but glossary entries and decision records. -->
|
|
78
|
+
|
|
79
|
+
- Add these entries to `GLOSSARY.md`, under its existing heading, after the existing ones, exactly as written. They are vocabulary only; nothing else in that file changes.
|
|
80
|
+
|
|
81
|
+
**<term>**:
|
|
82
|
+
<definition>
|
|
83
|
+
_Avoid_: <words to avoid>
|
|
84
|
+
|
|
85
|
+
<!-- Optional. Only when relevo.yml has several platforms: the one platform this issue is for. A feature that needs two platforms is two issues linked with Depends-on. -->
|
|
86
|
+
|
|
87
|
+
Platform: <name of a platform in relevo.yml>
|
|
88
|
+
|
|
89
|
+
<!-- Optional. Only when the change needs a new dependency: the manifest and lock files it may touch. Without this line an unattended run refuses to change them. -->
|
|
90
|
+
|
|
91
|
+
Allow-paths: <dependency files the change may touch, for example pubspec.yaml, pubspec.lock>
|
|
92
|
+
|
|
93
|
+
<!-- Optional. Related issues of the same idea; it only informs. -->
|
|
94
|
+
|
|
95
|
+
Related: #<number>
|
|
96
|
+
|
|
97
|
+
<!-- Optional. #<number> is an issue of this repository; owner/repo#<number> is an issue of a repository listed in links of relevo.yml. -->
|
|
98
|
+
|
|
99
|
+
Depends-on: #<number>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# relevo.yml: one per repository. /relevo:init creates and updates this file.
|
|
2
|
+
# Validate it with: node scripts/validate-config.mjs relevo.yml
|
|
3
|
+
# Never put API keys or tokens here; they belong in the repository secret store.
|
|
4
|
+
|
|
5
|
+
branches:
|
|
6
|
+
base: dev # integration branch: feature pull requests target it
|
|
7
|
+
release: main # promoted from base in a separate pull request
|
|
8
|
+
|
|
9
|
+
tracker: github # github (more later)
|
|
10
|
+
git_provider: github # github (more later)
|
|
11
|
+
platforms: # what is built and where; one entry per stack (see docs/stacks.md)
|
|
12
|
+
app:
|
|
13
|
+
stack: flutter # the profile in stacks/<stack>.md of the plugin, or in .relevo/stacks/
|
|
14
|
+
path: . # folder of the repository where this platform lives
|
|
15
|
+
targets: [android] # flutter: android | ios | web
|
|
16
|
+
# checks: # replace a verification command of the profile, for example:
|
|
17
|
+
# test: fvm flutter test
|
|
18
|
+
|
|
19
|
+
links: [] # repositories an issue may depend on (Depends-on: owner/repo#N); private ones need the secret RELEVO_LINKS_TOKEN
|
|
20
|
+
|
|
21
|
+
team:
|
|
22
|
+
members: [] # GitHub usernames of the people who build and review
|
|
23
|
+
spec_writer: null # writes the specs; never reviews or merges
|
|
24
|
+
approvers: [] # only used when review.mode is "roles"
|
|
25
|
+
|
|
26
|
+
review:
|
|
27
|
+
mode: peer # none (solo) | peer | roles
|
|
28
|
+
merger: approver # author | approver: who runs /relevo:ship
|
|
29
|
+
autopilot: off # off | precheck | verify: how much of the on-device review the agent may drive (the person always closes it)
|
|
30
|
+
allowed_origins: [] # web origins a driven review may visit besides localhost, e.g. https://staging.example.com
|
|
31
|
+
|
|
32
|
+
limits:
|
|
33
|
+
built_unreviewed_per_person: 2 # built issues one person may have waiting
|
|
34
|
+
concurrent_agents: 3 # unattended runs at the same time
|
|
35
|
+
review_sla_hours: 8 # reminder, then escalation after the same time again
|
|
36
|
+
run_max_turns: 100 # per unattended run
|
|
37
|
+
building_alert_hours: 3 # hours without activity before an issue on `building` is reported as stuck
|
|
38
|
+
run_max_cost: 5 # REQUIRED: US dollars per unattended run; choose your own
|
|
39
|
+
|
|
40
|
+
qa:
|
|
41
|
+
retries: 1 # developer attempts after QA rejects
|
|
42
|
+
rules_from: CLAUDE.md # project rules QA reads at every run
|
|
43
|
+
|
|
44
|
+
evidence:
|
|
45
|
+
policy: attach_to_pr # attach_to_pr | local_only (use for sensitive data)
|
|
46
|
+
|
|
47
|
+
digest:
|
|
48
|
+
issue: null # number of the issue where the daily digest is posted as a comment; subscribe your chat tool to it
|
|
49
|
+
|
|
50
|
+
design:
|
|
51
|
+
images: false # true: a picture pasted into an issue is downloaded and shown to the model (it may hold customer data); files in the repository are always available
|
|
52
|
+
|
|
53
|
+
language: en # language of hand-off messages (en, es)
|
|
54
|
+
trust: private-team # private-team | public
|
|
55
|
+
|
|
56
|
+
# Rename any label here; keys are fixed, names are yours.
|
|
57
|
+
# labels:
|
|
58
|
+
# ready: relevo:ready
|
|
59
|
+
# built: relevo:built
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Relevo: posts one digest per working day, as a comment on the issue named by digest.issue in relevo.yml.
|
|
2
|
+
# It lists who is waiting on what and the alerts, and posts nothing when nothing is waiting.
|
|
3
|
+
# Subscribe your chat tool to that issue to receive it. Copy to .github/workflows/relevo-digest.yml.
|
|
4
|
+
# Change the time below to suit your team (cron is in UTC).
|
|
5
|
+
# The scripts come from the published package @elalfner/relevo, at the version pinned in RELEVO_VERSION.
|
|
6
|
+
|
|
7
|
+
name: relevo-digest
|
|
8
|
+
|
|
9
|
+
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
10
|
+
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
11
|
+
env:
|
|
12
|
+
RELEVO_VERSION: '0.3.1'
|
|
13
|
+
|
|
14
|
+
on:
|
|
15
|
+
schedule:
|
|
16
|
+
- cron: '0 13 * * 1-5'
|
|
17
|
+
workflow_dispatch:
|
|
18
|
+
|
|
19
|
+
concurrency:
|
|
20
|
+
group: relevo-digest
|
|
21
|
+
cancel-in-progress: false
|
|
22
|
+
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
issues: write
|
|
26
|
+
pull-requests: read
|
|
27
|
+
|
|
28
|
+
jobs:
|
|
29
|
+
digest:
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
timeout-minutes: 10
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
|
|
35
|
+
- uses: actions/setup-node@v4
|
|
36
|
+
with:
|
|
37
|
+
node-version: 22
|
|
38
|
+
|
|
39
|
+
- name: Install the Relevo scripts
|
|
40
|
+
# The published package at the version pinned above; no repository is cloned for them. A package without the
|
|
41
|
+
# script this job runs stops the run here, before anything is changed.
|
|
42
|
+
run: |
|
|
43
|
+
mkdir -p .relevo-plugin
|
|
44
|
+
npm pack "@elalfner/relevo@$RELEVO_VERSION" --silent --pack-destination "$RUNNER_TEMP" > /dev/null
|
|
45
|
+
tar -xzf "$RUNNER_TEMP"/elalfner-relevo-*.tgz --strip-components=1 -C .relevo-plugin
|
|
46
|
+
for f in scripts/digest.mjs; do
|
|
47
|
+
[ -f ".relevo-plugin/$f" ] || { echo "::error::The Relevo package $RELEVO_VERSION lacks $f"; exit 1; }
|
|
48
|
+
done
|
|
49
|
+
|
|
50
|
+
- name: Post the digest
|
|
51
|
+
env:
|
|
52
|
+
GH_TOKEN: ${{ github.token }}
|
|
53
|
+
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
54
|
+
run: node .relevo-plugin/scripts/digest.mjs --repo "$GITHUB_REPOSITORY"
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Relevo: builds an issue when someone with write access labels it `relevo:ready`.
|
|
2
|
+
# Copy to .github/workflows/relevo-go.yml. If you renamed the ready label in relevo.yml, change it below too.
|
|
3
|
+
#
|
|
4
|
+
# Two jobs. `decide` is light (Node and the plugin's scripts, about half a minute): it runs the gates, claims the
|
|
5
|
+
# issue and comments when there is nothing to build. Only when the decision is `start` does `build` install the
|
|
6
|
+
# toolchain and run the agent, so an issue that is blocked, queued or incomplete never pays for that setup.
|
|
7
|
+
#
|
|
8
|
+
# Needs: a model credential secret (ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN), and a relevo.yml at the
|
|
9
|
+
# repository root.
|
|
10
|
+
# Keep the numbers in claude_args in line with limits.run_max_turns and limits.run_max_cost.
|
|
11
|
+
# Where the numbers disagree the lower one wins, so this file can only make a run cheaper than relevo.yml says.
|
|
12
|
+
|
|
13
|
+
name: relevo-go
|
|
14
|
+
|
|
15
|
+
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
16
|
+
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
17
|
+
env:
|
|
18
|
+
RELEVO_VERSION: '0.3.1'
|
|
19
|
+
|
|
20
|
+
on:
|
|
21
|
+
issues:
|
|
22
|
+
types: [labeled]
|
|
23
|
+
# The queue workflow (relevo-queue.yml) starts waiting issues through this trigger.
|
|
24
|
+
workflow_dispatch:
|
|
25
|
+
inputs:
|
|
26
|
+
issue:
|
|
27
|
+
description: Issue number to build
|
|
28
|
+
required: true
|
|
29
|
+
|
|
30
|
+
# One run per issue: a repeated label event waits for the first run and then finds the issue already building.
|
|
31
|
+
concurrency:
|
|
32
|
+
group: relevo-go-${{ github.event.issue.number || inputs.issue }}
|
|
33
|
+
cancel-in-progress: false
|
|
34
|
+
|
|
35
|
+
permissions:
|
|
36
|
+
contents: read
|
|
37
|
+
issues: write
|
|
38
|
+
pull-requests: read
|
|
39
|
+
|
|
40
|
+
jobs:
|
|
41
|
+
decide:
|
|
42
|
+
# The repository variable RELEVO_CLOUD_GO set to `off` pauses the cloud: a ready label starts nothing and the issue
|
|
43
|
+
# stays ready for a local run (/relevo:go <n>). It is read at run time, so changing it needs no pull request.
|
|
44
|
+
if: vars.RELEVO_CLOUD_GO != 'off' && (github.event_name == 'workflow_dispatch' || (github.event.label.name == 'relevo:ready' && github.event.sender.type == 'User'))
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
timeout-minutes: 10
|
|
47
|
+
outputs:
|
|
48
|
+
action: ${{ steps.plan.outputs.action }}
|
|
49
|
+
decision_b64: ${{ steps.plan.outputs.decision_b64 }}
|
|
50
|
+
# The platform the issue is for (empty unless the decision is `start`): the build job installs only its toolchain.
|
|
51
|
+
platform: ${{ steps.plan.outputs.platform }}
|
|
52
|
+
stack: ${{ steps.plan.outputs.stack }}
|
|
53
|
+
path: ${{ steps.plan.outputs.path }}
|
|
54
|
+
steps:
|
|
55
|
+
# The repository holds relevo.yml; the plugin holds the scripts that apply the gates.
|
|
56
|
+
- uses: actions/checkout@v4
|
|
57
|
+
|
|
58
|
+
- uses: actions/setup-node@v4
|
|
59
|
+
with:
|
|
60
|
+
node-version: 22
|
|
61
|
+
|
|
62
|
+
- name: Install the Relevo scripts
|
|
63
|
+
# The published package at the version pinned above; no repository is cloned for them. A package without the
|
|
64
|
+
# script this job runs stops the run here, before anything is changed.
|
|
65
|
+
run: |
|
|
66
|
+
mkdir -p .relevo-plugin
|
|
67
|
+
npm pack "@elalfner/relevo@$RELEVO_VERSION" --silent --pack-destination "$RUNNER_TEMP" > /dev/null
|
|
68
|
+
tar -xzf "$RUNNER_TEMP"/elalfner-relevo-*.tgz --strip-components=1 -C .relevo-plugin
|
|
69
|
+
for f in scripts/go-plan.mjs; do
|
|
70
|
+
[ -f ".relevo-plugin/$f" ] || { echo "::error::The Relevo package $RELEVO_VERSION lacks $f"; exit 1; }
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
# Applies the gates and, when the decision is `start`, claims the issue (relevo:building). Any other decision
|
|
74
|
+
# (needs-spec, blocked, queued, denied, no milestone, no assignee) is applied here with its comment.
|
|
75
|
+
- name: Gates and claim
|
|
76
|
+
id: plan
|
|
77
|
+
env:
|
|
78
|
+
GH_TOKEN: ${{ github.token }}
|
|
79
|
+
# Only needed when `links` in relevo.yml lists a private repository: a read-only token (issues and pull
|
|
80
|
+
# requests) stored as the secret RELEVO_LINKS_TOKEN. It is used only to read the issues the dependencies
|
|
81
|
+
# name, never to write, and an empty value is fine (public repositories are read without it).
|
|
82
|
+
GH_LINKS_TOKEN: ${{ secrets.RELEVO_LINKS_TOKEN }}
|
|
83
|
+
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
84
|
+
ISSUE: ${{ github.event.issue.number || inputs.issue }}
|
|
85
|
+
# Without a model credential the cloud cannot build: the issue stays ready and a comment says to run it locally.
|
|
86
|
+
HAS_CREDENTIAL: ${{ secrets.ANTHROPIC_API_KEY != '' || secrets.CLAUDE_CODE_OAUTH_TOKEN != '' }}
|
|
87
|
+
run: node .relevo-plugin/scripts/go-plan.mjs --repo "$GITHUB_REPOSITORY" --only "$ISSUE" --apply --claim --credential "$HAS_CREDENTIAL" --github-output "$GITHUB_OUTPUT"
|
|
88
|
+
|
|
89
|
+
# See the same step in `build`. A failure before the issue is claimed (the plugin cannot be cloned, the
|
|
90
|
+
# scripts fail) leaves it ready or queued with nobody told.
|
|
91
|
+
- name: Report a run that ended without an outcome
|
|
92
|
+
if: always()
|
|
93
|
+
env:
|
|
94
|
+
GH_TOKEN: ${{ github.token }}
|
|
95
|
+
ISSUE: ${{ github.event.issue.number || inputs.issue }}
|
|
96
|
+
JOB_STATUS: ${{ job.status }}
|
|
97
|
+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
98
|
+
run: |
|
|
99
|
+
labels=$(gh issue view "$ISSUE" --json labels --jq '.labels[].name')
|
|
100
|
+
if [ "$JOB_STATUS" = "success" ]; then
|
|
101
|
+
exit 0
|
|
102
|
+
fi
|
|
103
|
+
if ! printf '%s\n' "$labels" | grep -qxE 'relevo:(ready|queued|building)'; then
|
|
104
|
+
exit 0
|
|
105
|
+
fi
|
|
106
|
+
assignee=$(gh issue view "$ISSUE" --json assignees --jq '.assignees[0].login // empty')
|
|
107
|
+
gh issue edit "$ISSUE" --remove-label relevo:ready --remove-label relevo:queued --remove-label relevo:building --add-label relevo:qa-failed
|
|
108
|
+
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."
|
|
109
|
+
|
|
110
|
+
build:
|
|
111
|
+
needs: decide
|
|
112
|
+
if: needs.decide.outputs.action == 'start'
|
|
113
|
+
runs-on: ubuntu-latest
|
|
114
|
+
timeout-minutes: 60
|
|
115
|
+
permissions:
|
|
116
|
+
contents: write
|
|
117
|
+
issues: write
|
|
118
|
+
pull-requests: read
|
|
119
|
+
steps:
|
|
120
|
+
- uses: actions/checkout@v4
|
|
121
|
+
with:
|
|
122
|
+
fetch-depth: 0
|
|
123
|
+
|
|
124
|
+
- uses: actions/setup-node@v4
|
|
125
|
+
with:
|
|
126
|
+
node-version: 22
|
|
127
|
+
|
|
128
|
+
# The agent runs the project's own checks (QA runs them), so the toolchain of the stack of the platform of the
|
|
129
|
+
# issue is installed here. The `decide` job tells which one (`needs.decide.outputs.stack`) and where it lives
|
|
130
|
+
# (`needs.decide.outputs.path`), so an issue for the front does not install the back's tools: a step whose stack
|
|
131
|
+
# is not the issue's is skipped and costs nothing. The names are the `stack` of relevo.yml. Change the versions
|
|
132
|
+
# and the caching to suit the project; `/relevo:doctor` fails when a Flutter or Laravel platform has no active
|
|
133
|
+
# step below, because every build would fail QA after spending the run.
|
|
134
|
+
- if: needs.decide.outputs.stack == 'flutter'
|
|
135
|
+
uses: subosito/flutter-action@v2
|
|
136
|
+
with:
|
|
137
|
+
channel: stable
|
|
138
|
+
cache: true
|
|
139
|
+
- if: needs.decide.outputs.stack == 'laravel'
|
|
140
|
+
uses: shivammathur/setup-php@v2
|
|
141
|
+
with:
|
|
142
|
+
php-version: '8.3'
|
|
143
|
+
tools: composer
|
|
144
|
+
# Vue needs only Node, which this job installed above; to cache the packages, uncomment:
|
|
145
|
+
# - if: needs.decide.outputs.stack == 'vue'
|
|
146
|
+
# uses: actions/setup-node@v4
|
|
147
|
+
# with:
|
|
148
|
+
# node-version: 22
|
|
149
|
+
# cache: npm
|
|
150
|
+
# cache-dependency-path: ${{ needs.decide.outputs.path }}/package-lock.json
|
|
151
|
+
# Another stack: add a step guarded by its name, and its dependencies below.
|
|
152
|
+
# Installing the dependencies is the project's choice, because the package manager and the lockfile vary. Do it
|
|
153
|
+
# from the folder of the platform, for example:
|
|
154
|
+
# - if: needs.decide.outputs.stack == 'vue'
|
|
155
|
+
# working-directory: ${{ needs.decide.outputs.path }}
|
|
156
|
+
# run: npm ci --ignore-scripts
|
|
157
|
+
# Install dependencies without package scripts where the tool offers it, so a dependency cannot run code
|
|
158
|
+
# while it is installed: `npm ci --ignore-scripts`, `pnpm install --frozen-lockfile --ignore-scripts`,
|
|
159
|
+
# `composer install --no-scripts`. `flutter pub get` and `dart pub get` run no install-time scripts of
|
|
160
|
+
# the packages, so they need no such option.
|
|
161
|
+
|
|
162
|
+
- name: Install OpenSpec
|
|
163
|
+
run: npm install --global @fission-ai/openspec
|
|
164
|
+
env:
|
|
165
|
+
OPENSPEC_TELEMETRY: '0'
|
|
166
|
+
|
|
167
|
+
- id: agent
|
|
168
|
+
uses: anthropics/claude-code-action@v1
|
|
169
|
+
env:
|
|
170
|
+
OPENSPEC_TELEMETRY: '0'
|
|
171
|
+
GH_TOKEN: ${{ github.token }}
|
|
172
|
+
# Without this the agent may start a subagent in the background and end the run before it finishes,
|
|
173
|
+
# leaving the issue stuck on building with no comment (seen in the pilot, see docs/pilot-findings.md).
|
|
174
|
+
CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: '1'
|
|
175
|
+
with:
|
|
176
|
+
# Credentials: choose ONE of the next two lines.
|
|
177
|
+
# a) API key (default):
|
|
178
|
+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
179
|
+
# b) Subscription: create a token with `claude setup-token` and store it as the secret CLAUDE_CODE_OAUTH_TOKEN.
|
|
180
|
+
# The command prints the token over two lines; remove all whitespace before storing it, for example
|
|
181
|
+
# `pbpaste | tr -d '[:space:]' | gh secret set CLAUDE_CODE_OAUTH_TOKEN`. Then replace the line above with:
|
|
182
|
+
# claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
183
|
+
github_token: ${{ github.token }}
|
|
184
|
+
# The queue starts builds with `gh workflow run`, so the actor is the Actions bot and the action refuses
|
|
185
|
+
# it unless it is allowed. Only this one bot: never '*', which would let outside apps start runs.
|
|
186
|
+
allowed_bots: github-actions
|
|
187
|
+
# Plugin repository: choose ONE of the next two lines.
|
|
188
|
+
# a) Public repository (default):
|
|
189
|
+
plugin_marketplaces: https://github.com/elalfner/relevo.git
|
|
190
|
+
# b) Private repository: the runner cannot clone it without a credential. Store a read-only fine-grained
|
|
191
|
+
# token as the secret RELEVO_PLUGIN_TOKEN and replace the line above with:
|
|
192
|
+
# plugin_marketplaces: https://x-access-token:${{ secrets.RELEVO_PLUGIN_TOKEN }}@github.com/elalfner/relevo.git
|
|
193
|
+
plugins: relevo@relevo
|
|
194
|
+
# The issue text is data, never instructions: the skill tells the agent so. The tool list is closed:
|
|
195
|
+
# only the tools in --allowedTools, and no web access (nothing to fetch, nowhere to send a secret).
|
|
196
|
+
# Do not set allowed_non_write_users or show_full_output here, and never widen allowed_bots: they open the run to
|
|
197
|
+
# strangers or print tool results (possibly secrets) to the log.
|
|
198
|
+
# The decision the `decide` job already made travels with the prompt, encoded so that the issue title in it
|
|
199
|
+
# is never read as an instruction. The skill decodes it and skips its own planning step.
|
|
200
|
+
prompt: /relevo:go ${{ github.event.issue.number || inputs.issue }} --decision ${{ needs.decide.outputs.decision_b64 }}
|
|
201
|
+
claude_args: >-
|
|
202
|
+
--max-turns 100
|
|
203
|
+
--max-budget-usd 5
|
|
204
|
+
--allowedTools "Bash,Read,Write,Edit,Glob,Grep,Task"
|
|
205
|
+
--disallowedTools "WebFetch,WebSearch,Bash(curl:*),Bash(wget:*)"
|
|
206
|
+
|
|
207
|
+
# The scripts of the next two steps come from the plugin package. It is unpacked into the workspace only now, after the agent has
|
|
208
|
+
# finished, because the agent's preflight needs a clean working tree and a checkout inside it would not be.
|
|
209
|
+
- name: Install the Relevo scripts for the reports
|
|
210
|
+
if: always()
|
|
211
|
+
continue-on-error: true
|
|
212
|
+
run: |
|
|
213
|
+
mkdir -p .relevo-plugin
|
|
214
|
+
npm pack "@elalfner/relevo@$RELEVO_VERSION" --silent --pack-destination "$RUNNER_TEMP" > /dev/null
|
|
215
|
+
tar -xzf "$RUNNER_TEMP"/elalfner-relevo-*.tgz --strip-components=1 -C .relevo-plugin
|
|
216
|
+
for f in scripts/run-usage.mjs scripts/usage-limit.mjs; do
|
|
217
|
+
[ -f ".relevo-plugin/$f" ] || { echo "::error::The Relevo package $RELEVO_VERSION lacks $f"; exit 1; }
|
|
218
|
+
done
|
|
219
|
+
|
|
220
|
+
# What the run used, read from the agent's execution log. The format of that log is not documented, so the
|
|
221
|
+
# reader is tolerant: when it finds nothing it posts nothing, and this step can never fail the job.
|
|
222
|
+
- name: Report what the run used
|
|
223
|
+
if: always()
|
|
224
|
+
continue-on-error: true
|
|
225
|
+
env:
|
|
226
|
+
GH_TOKEN: ${{ github.token }}
|
|
227
|
+
ISSUE: ${{ github.event.issue.number || inputs.issue }}
|
|
228
|
+
EXECUTION_FILE: ${{ steps.agent.outputs.execution_file }}
|
|
229
|
+
# With a subscription token and no API key the cost is notional.
|
|
230
|
+
ONLY_SUBSCRIPTION: ${{ secrets.ANTHROPIC_API_KEY == '' && secrets.CLAUDE_CODE_OAUTH_TOKEN != '' }}
|
|
231
|
+
run: |
|
|
232
|
+
[ -n "$EXECUTION_FILE" ] || exit 0
|
|
233
|
+
flag=""; [ "$ONLY_SUBSCRIPTION" = "true" ] && flag="--subscription"
|
|
234
|
+
line=$(node .relevo-plugin/scripts/run-usage.mjs --file "$EXECUTION_FILE" $flag)
|
|
235
|
+
[ -n "$line" ] && gh issue comment "$ISSUE" --body "$line"
|
|
236
|
+
exit 0
|
|
237
|
+
|
|
238
|
+
# A run can end without telling anyone: the plugin cannot be cloned, the login fails, preflight stops, the
|
|
239
|
+
# run reached --max-turns or --max-budget-usd (the runner cuts it; the agent cannot see those counters and
|
|
240
|
+
# does not stop itself), or the agent ends its session early (all seen in the pilot, see docs/pilot-findings.md). If the issue is still
|
|
241
|
+
# ready or building after the agent step, no outcome was recorded, so say so on the issue.
|
|
242
|
+
# If you renamed the labels in relevo.yml, change the names below too.
|
|
243
|
+
- name: Report a run that ended without an outcome
|
|
244
|
+
if: always()
|
|
245
|
+
env:
|
|
246
|
+
GH_TOKEN: ${{ github.token }}
|
|
247
|
+
ISSUE: ${{ github.event.issue.number || inputs.issue }}
|
|
248
|
+
JOB_STATUS: ${{ job.status }}
|
|
249
|
+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
250
|
+
EXECUTION_FILE: ${{ steps.agent.outputs.execution_file }}
|
|
251
|
+
run: |
|
|
252
|
+
labels=$(gh issue view "$ISSUE" --json labels --jq '.labels[].name')
|
|
253
|
+
# A run the queue dispatched that fails before it claims the issue leaves it `queued`: report that
|
|
254
|
+
# too, or the queue would dispatch it again every 30 minutes. A run that ends normally leaves it queued
|
|
255
|
+
# on purpose (no capacity), so that case is only reported when the job failed.
|
|
256
|
+
if ! printf '%s\n' "$labels" | grep -qxE 'relevo:(ready|building)'; then
|
|
257
|
+
if ! { [ "$JOB_STATUS" != "success" ] && printf '%s\n' "$labels" | grep -qx 'relevo:queued'; }; then
|
|
258
|
+
exit 0
|
|
259
|
+
fi
|
|
260
|
+
fi
|
|
261
|
+
assignee=$(gh issue view "$ISSUE" --json assignees --jq '.assignees[0].login // empty')
|
|
262
|
+
gh issue edit "$ISSUE" --remove-label relevo:ready --remove-label relevo:queued --remove-label relevo:building --add-label relevo:qa-failed
|
|
263
|
+
# A usage limit, a quota or an empty credit balance is not a failure of the code: say so.
|
|
264
|
+
if [ -n "$EXECUTION_FILE" ] && node .relevo-plugin/scripts/usage-limit.mjs --file "$EXECUTION_FILE"; then
|
|
265
|
+
gh issue comment "$ISSUE" --body "${assignee:+@$assignee }The run stopped because the usage limit (or the credit) of the model account was reached, so nothing was built. Nothing is wrong with the code. Label the issue \`relevo:ready\` again later, after removing \`relevo:qa-failed\`: $RUN_URL"
|
|
266
|
+
else
|
|
267
|
+
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."
|
|
268
|
+
fi
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Relevo: re-evaluates the queue and starts the issues that are now eligible.
|
|
2
|
+
# Copy to .github/workflows/relevo-queue.yml next to relevo-go.yml (it starts builds through that workflow).
|
|
3
|
+
#
|
|
4
|
+
# Runs when an issue is closed or its labels change (a merge, a built issue leaving that state) and on a
|
|
5
|
+
# schedule, so a lost event does not stall the queue. Needs a relevo.yml at the repository root.
|
|
6
|
+
# The scripts come from the published package @elalfner/relevo, at the version pinned in RELEVO_VERSION.
|
|
7
|
+
|
|
8
|
+
name: relevo-queue
|
|
9
|
+
|
|
10
|
+
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
11
|
+
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
12
|
+
env:
|
|
13
|
+
RELEVO_VERSION: '0.3.1'
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
issues:
|
|
17
|
+
types: [closed, labeled, unlabeled]
|
|
18
|
+
schedule:
|
|
19
|
+
- cron: '*/30 * * * *'
|
|
20
|
+
workflow_dispatch:
|
|
21
|
+
|
|
22
|
+
# One evaluation at a time; a second one waits and then sees the first one's labels.
|
|
23
|
+
concurrency:
|
|
24
|
+
group: relevo-queue
|
|
25
|
+
cancel-in-progress: false
|
|
26
|
+
|
|
27
|
+
permissions:
|
|
28
|
+
contents: read
|
|
29
|
+
issues: write
|
|
30
|
+
pull-requests: read
|
|
31
|
+
actions: write
|
|
32
|
+
|
|
33
|
+
jobs:
|
|
34
|
+
requeue:
|
|
35
|
+
# Paused together with the go workflow by the repository variable RELEVO_CLOUD_GO set to `off`.
|
|
36
|
+
if: vars.RELEVO_CLOUD_GO != 'off'
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
timeout-minutes: 10
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
|
|
42
|
+
- uses: actions/setup-node@v4
|
|
43
|
+
with:
|
|
44
|
+
node-version: 22
|
|
45
|
+
|
|
46
|
+
- name: Install the Relevo scripts
|
|
47
|
+
# The published package at the version pinned above; no repository is cloned for them. A package without the
|
|
48
|
+
# script this job runs stops the run here, before anything is changed.
|
|
49
|
+
run: |
|
|
50
|
+
mkdir -p .relevo-plugin
|
|
51
|
+
npm pack "@elalfner/relevo@$RELEVO_VERSION" --silent --pack-destination "$RUNNER_TEMP" > /dev/null
|
|
52
|
+
tar -xzf "$RUNNER_TEMP"/elalfner-relevo-*.tgz --strip-components=1 -C .relevo-plugin
|
|
53
|
+
for f in scripts/requeue.mjs; do
|
|
54
|
+
[ -f ".relevo-plugin/$f" ] || { echo "::error::The Relevo package $RELEVO_VERSION lacks $f"; exit 1; }
|
|
55
|
+
done
|
|
56
|
+
|
|
57
|
+
- name: Evaluate the queue and dispatch builds
|
|
58
|
+
env:
|
|
59
|
+
GH_TOKEN: ${{ github.token }}
|
|
60
|
+
# Only needed when `links` in relevo.yml lists a private repository: a read-only token (issues and pull
|
|
61
|
+
# requests) stored as the secret RELEVO_LINKS_TOKEN. It is used only to read the issues the dependencies
|
|
62
|
+
# name, never to write, and an empty value is fine (public repositories are read without it).
|
|
63
|
+
GH_LINKS_TOKEN: ${{ secrets.RELEVO_LINKS_TOKEN }}
|
|
64
|
+
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
65
|
+
# Without a model credential nothing is dispatched.
|
|
66
|
+
HAS_CREDENTIAL: ${{ secrets.ANTHROPIC_API_KEY != '' || secrets.CLAUDE_CODE_OAUTH_TOKEN != '' }}
|
|
67
|
+
run: |
|
|
68
|
+
result=$(node .relevo-plugin/scripts/requeue.mjs --repo "$GITHUB_REPOSITORY" --credential "$HAS_CREDENTIAL")
|
|
69
|
+
echo "$result"
|
|
70
|
+
for issue in $(echo "$result" | node -e 'JSON.parse(require("fs").readFileSync(0,"utf8")).start.forEach(n=>console.log(n))'); do
|
|
71
|
+
gh workflow run relevo-go.yml --repo "$GITHUB_REPOSITORY" -f issue="$issue"
|
|
72
|
+
done
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Relevo: keeps the issue label in step with its pull request.
|
|
2
|
+
# A review that requests changes sets `relevo:changes-requested` and mentions the author; a new push returns the
|
|
3
|
+
# issue to `relevo:pr-open`; an approval mentions the person who will run /relevo:ship.
|
|
4
|
+
# Copy to .github/workflows/relevo-review.yml. Needs a relevo.yml at the repository root.
|
|
5
|
+
# Pull requests from forks are skipped explicitly (and would only get a read-only token anyway).
|
|
6
|
+
# The scripts come from the published package @elalfner/relevo, at the version pinned in RELEVO_VERSION.
|
|
7
|
+
|
|
8
|
+
name: relevo-review
|
|
9
|
+
|
|
10
|
+
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
11
|
+
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
12
|
+
env:
|
|
13
|
+
RELEVO_VERSION: '0.3.1'
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
pull_request_review:
|
|
17
|
+
types: [submitted]
|
|
18
|
+
pull_request:
|
|
19
|
+
types: [synchronize]
|
|
20
|
+
|
|
21
|
+
concurrency:
|
|
22
|
+
group: relevo-review-${{ github.event.pull_request.number }}
|
|
23
|
+
cancel-in-progress: false
|
|
24
|
+
|
|
25
|
+
permissions:
|
|
26
|
+
contents: read
|
|
27
|
+
issues: write
|
|
28
|
+
pull-requests: read
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
review:
|
|
32
|
+
if: startsWith(github.event.pull_request.head.ref, 'feature/') && github.event.pull_request.head.repo.full_name == github.repository
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
timeout-minutes: 5
|
|
35
|
+
steps:
|
|
36
|
+
# The base branch holds relevo.yml; the pull request code is never checked out or run.
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
with:
|
|
39
|
+
ref: ${{ github.event.pull_request.base.ref }}
|
|
40
|
+
|
|
41
|
+
- uses: actions/setup-node@v4
|
|
42
|
+
with:
|
|
43
|
+
node-version: 22
|
|
44
|
+
|
|
45
|
+
- name: Install the Relevo scripts
|
|
46
|
+
# The published package at the version pinned above; no repository is cloned for them. A package without the
|
|
47
|
+
# script this job runs stops the run here, before anything is changed.
|
|
48
|
+
run: |
|
|
49
|
+
mkdir -p .relevo-plugin
|
|
50
|
+
npm pack "@elalfner/relevo@$RELEVO_VERSION" --silent --pack-destination "$RUNNER_TEMP" > /dev/null
|
|
51
|
+
tar -xzf "$RUNNER_TEMP"/elalfner-relevo-*.tgz --strip-components=1 -C .relevo-plugin
|
|
52
|
+
for f in scripts/review-event.mjs; do
|
|
53
|
+
[ -f ".relevo-plugin/$f" ] || { echo "::error::The Relevo package $RELEVO_VERSION lacks $f"; exit 1; }
|
|
54
|
+
done
|
|
55
|
+
|
|
56
|
+
- name: Update the label
|
|
57
|
+
env:
|
|
58
|
+
GH_TOKEN: ${{ github.token }}
|
|
59
|
+
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
60
|
+
run: node .relevo-plugin/scripts/review-event.mjs --repo "$GITHUB_REPOSITORY"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Relevo: reminds the reviewer when a pull request waits longer than limits.review_sla_hours, and escalates
|
|
2
|
+
# to the spec writer (adding the needs-reviewer label) after the same time again.
|
|
3
|
+
# Copy to .github/workflows/relevo-sla.yml. Needs a relevo.yml at the repository root.
|
|
4
|
+
# The scripts come from the published package @elalfner/relevo, at the version pinned in RELEVO_VERSION.
|
|
5
|
+
|
|
6
|
+
name: relevo-sla
|
|
7
|
+
|
|
8
|
+
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
9
|
+
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
10
|
+
env:
|
|
11
|
+
RELEVO_VERSION: '0.3.1'
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
schedule:
|
|
15
|
+
- cron: '17 * * * *'
|
|
16
|
+
workflow_dispatch:
|
|
17
|
+
|
|
18
|
+
concurrency:
|
|
19
|
+
group: relevo-sla
|
|
20
|
+
cancel-in-progress: false
|
|
21
|
+
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
issues: write
|
|
25
|
+
pull-requests: write
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
sla:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
timeout-minutes: 10
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
|
|
34
|
+
- uses: actions/setup-node@v4
|
|
35
|
+
with:
|
|
36
|
+
node-version: 22
|
|
37
|
+
|
|
38
|
+
- name: Install the Relevo scripts
|
|
39
|
+
# The published package at the version pinned above; no repository is cloned for them. A package without the
|
|
40
|
+
# script this job runs stops the run here, before anything is changed.
|
|
41
|
+
run: |
|
|
42
|
+
mkdir -p .relevo-plugin
|
|
43
|
+
npm pack "@elalfner/relevo@$RELEVO_VERSION" --silent --pack-destination "$RUNNER_TEMP" > /dev/null
|
|
44
|
+
tar -xzf "$RUNNER_TEMP"/elalfner-relevo-*.tgz --strip-components=1 -C .relevo-plugin
|
|
45
|
+
for f in scripts/review-sla.mjs; do
|
|
46
|
+
[ -f ".relevo-plugin/$f" ] || { echo "::error::The Relevo package $RELEVO_VERSION lacks $f"; exit 1; }
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
- name: Remind and escalate
|
|
50
|
+
env:
|
|
51
|
+
GH_TOKEN: ${{ github.token }}
|
|
52
|
+
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
53
|
+
run: node .relevo-plugin/scripts/review-sla.mjs --repo "$GITHUB_REPOSITORY"
|