@elalfner/relevo 0.3.1 → 0.5.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/README.es.md +3 -1
- package/README.md +3 -1
- package/package.json +1 -1
- package/scripts/agent-guard.mjs +94 -0
- package/scripts/cli.mjs +67 -6
- package/scripts/doctor.mjs +10 -0
- package/scripts/init.mjs +1 -0
- package/scripts/lib/adapters/antigravity.mjs +99 -0
- package/scripts/lib/adapters/index.mjs +6 -0
- package/scripts/lib/adapters/install.mjs +127 -0
- package/scripts/lib/adapters/source.mjs +65 -0
- package/scripts/lib/adapters/tools.mjs +52 -0
- package/scripts/lib/agent-guard.mjs +188 -0
- package/scripts/lib/agent-workflow.mjs +46 -0
- package/scripts/lib/cli.mjs +44 -12
- package/scripts/lib/config.mjs +18 -7
- package/scripts/lib/doctor.mjs +92 -4
- package/scripts/lib/go-local.mjs +31 -4
- package/scripts/lib/init-tools.mjs +97 -0
- package/scripts/lib/init.mjs +38 -2
- package/scripts/lib/messages.mjs +16 -1
- package/scripts/lib/next-step.mjs +10 -1
- package/scripts/lib/protected-paths.mjs +10 -4
- package/scripts/lib/run-usage.mjs +1 -1
- package/scripts/lib/select.mjs +118 -0
- package/scripts/lib/status-data.mjs +16 -5
- package/scripts/lib/status.mjs +20 -5
- package/scripts/lib/toolchain-check.mjs +12 -0
- package/scripts/toolchain-check.mjs +49 -0
- package/skills/init/SKILL.md +5 -2
- package/skills/review/SKILL.md +10 -0
- package/stacks/supabase.md +63 -0
- package/templates/agents/antigravity-step.yml +70 -0
- package/templates/deploy-supabase.yml +75 -0
- package/templates/workflows/relevo-digest.yml +1 -1
- package/templates/workflows/relevo-go.yml +35 -1
- package/templates/workflows/relevo-queue.yml +3 -1
- package/templates/workflows/relevo-review.yml +1 -1
- package/templates/workflows/relevo-sla.yml +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Which of the tools that a profile lists are not on the path. `probe(tool)` says whether one answers; it is injected
|
|
2
|
+
// so that a test does not need the tools. An empty list checks nothing.
|
|
3
|
+
|
|
4
|
+
/** The tools that are missing, in the order of the profile and without repeats. */
|
|
5
|
+
export function missingTools(tools, probe) {
|
|
6
|
+
const seen = new Set();
|
|
7
|
+
return (tools ?? []).filter((tool) => {
|
|
8
|
+
if (seen.has(tool)) return false;
|
|
9
|
+
seen.add(tool);
|
|
10
|
+
return !probe(tool);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Checks that the tools which the profile of a platform lists are on the path of this machine. Usage:
|
|
3
|
+
// node scripts/toolchain-check.mjs --platform <name> [--config relevo.yml] [--plugin-root <dir>]
|
|
4
|
+
// It prints { platform, stack, tools, missing } as JSON. A tool is there when `<tool> --version` starts. The cloud build
|
|
5
|
+
// runs it before the agent, so that a runner without the SDK stops in seconds and not after a full run.
|
|
6
|
+
// exit 0: every tool is there (or the profile lists none) exit 1: a tool is missing, or the profile cannot be resolved
|
|
7
|
+
// exit 2: bad usage or an invalid relevo.yml
|
|
8
|
+
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
11
|
+
import { dirname, resolve } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { loadConfig } from './lib/config.mjs';
|
|
14
|
+
import { resolveProfile } from './lib/stack-profile.mjs';
|
|
15
|
+
import { missingTools } from './lib/toolchain-check.mjs';
|
|
16
|
+
|
|
17
|
+
const arg = (name) => {
|
|
18
|
+
const i = process.argv.indexOf(`--${name}`);
|
|
19
|
+
return i === -1 ? undefined : process.argv[i + 1];
|
|
20
|
+
};
|
|
21
|
+
const fail = (message, code) => {
|
|
22
|
+
console.error(message);
|
|
23
|
+
process.exit(code);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const pluginRoot = resolve(arg('plugin-root') ?? resolve(dirname(fileURLToPath(import.meta.url)), '..'));
|
|
27
|
+
const configPath = resolve(arg('config') ?? 'relevo.yml');
|
|
28
|
+
if (!existsSync(configPath)) fail(`relevo.yml not found at ${configPath}. Run /relevo:init to create it.`, 2);
|
|
29
|
+
const loaded = loadConfig(readFileSync(configPath, 'utf8'));
|
|
30
|
+
if (!loaded.ok) fail(`relevo.yml is invalid:\n${loaded.errors.map((e) => ` ${e.message}`).join('\n')}`, 2);
|
|
31
|
+
|
|
32
|
+
const names = Object.keys(loaded.config.platforms);
|
|
33
|
+
const name = arg('platform') ?? (names.length === 1 ? names[0] : undefined);
|
|
34
|
+
if (!name) fail(`usage: toolchain-check.mjs --platform <${names.join('|')}> [--config relevo.yml] [--plugin-root dir]`, 2);
|
|
35
|
+
const platform = loaded.config.platforms[name];
|
|
36
|
+
if (!platform) fail(`unknown platform "${name}"; the platforms of relevo.yml are: ${names.join(', ')}`, 2);
|
|
37
|
+
|
|
38
|
+
const base = dirname(configPath);
|
|
39
|
+
const read = (path) => {
|
|
40
|
+
const file = path.startsWith('/') ? path : resolve(base, path);
|
|
41
|
+
return existsSync(file) ? readFileSync(file, 'utf8') : null;
|
|
42
|
+
};
|
|
43
|
+
const resolved = resolveProfile({ platform, pluginRoot, read });
|
|
44
|
+
if (!resolved.ok) fail(resolved.errors.join('\n'), 1);
|
|
45
|
+
|
|
46
|
+
const probe = (tool) => spawnSync(tool, ['--version'], { stdio: 'ignore' }).error === undefined;
|
|
47
|
+
const missing = missingTools(resolved.profile.tools, probe);
|
|
48
|
+
console.log(JSON.stringify({ platform: name, stack: resolved.profile.stack, tools: resolved.profile.tools, missing }));
|
|
49
|
+
process.exit(missing.length > 0 ? 1 : 0);
|
package/skills/init/SKILL.md
CHANGED
|
@@ -33,11 +33,12 @@ If `relevo.yml` already exists and is valid, skip this step. Otherwise ask, in s
|
|
|
33
33
|
- who builds and reviews (GitHub usernames) and who writes the specs;
|
|
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
|
-
- 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` and `
|
|
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
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;
|
|
38
38
|
- 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
39
|
- 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; it is not committed);
|
|
40
|
-
- the
|
|
40
|
+
- 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
|
+
- 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).
|
|
41
42
|
|
|
42
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.
|
|
43
44
|
|
|
@@ -53,6 +54,8 @@ Show it as it is. Each line says whether it is already `ok`, will be done, `diff
|
|
|
53
54
|
|
|
54
55
|
For each line in the plan that is an install, show its exact command and ask for a yes to that command alone. Run it only on a yes. If the person declines, continue, say that dependency is still missing, and that `go` and `ship` will not run until the OpenSpec tool is installed.
|
|
55
56
|
|
|
57
|
+
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
|
+
|
|
56
59
|
### 5. Apply
|
|
57
60
|
|
|
58
61
|
```bash
|
package/skills/review/SKILL.md
CHANGED
|
@@ -152,6 +152,16 @@ Only when `decide` exited 0.
|
|
|
152
152
|
- `evidence.policy: local_only`: delete the `evidence/` folder now. Nothing is attached anywhere.
|
|
153
153
|
- `evidence.policy: attach_to_pr`: GitHub has no API for attaching images, so you cannot upload them. List the files in `evidence/` and ask the person to drag them into the pull request description. Keep the folder until they say they did, then delete it.
|
|
154
154
|
|
|
155
|
+
### 9. Return to base branch
|
|
156
|
+
|
|
157
|
+
Restore the working tree to the base branch so the workspace remains clean on the base branch for other tasks or subsequent reviews, but only where that is possible: in the main checkout, and when no other worktree holds the base branch.
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
[ "$(git rev-parse --git-dir)" = "$(git rev-parse --git-common-dir)" ] && ! git worktree list --porcelain | grep -qx "branch refs/heads/<base>" && git switch <base>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
If the condition does not hold (a separate worktree, or the base branch is checked out elsewhere), do not switch: say which branch the working tree stays on. If `git switch` fails, say so; it never changes the verdict or the state of the issue, because the pull request already exists.
|
|
164
|
+
|
|
155
165
|
## Report
|
|
156
166
|
|
|
157
167
|
Finish with one short block: the issue, the review verdict, the checklist result, the pull request link (or why none was opened), and the issue's new state.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
stack: supabase
|
|
3
|
+
commands:
|
|
4
|
+
analyze: supabase db lint
|
|
5
|
+
test: supabase test db
|
|
6
|
+
format: npx prettier --write supabase/
|
|
7
|
+
generated: []
|
|
8
|
+
tools: [supabase, docker]
|
|
9
|
+
requires_skills:
|
|
10
|
+
- supabase
|
|
11
|
+
- supabase-postgres-best-practices
|
|
12
|
+
skills:
|
|
13
|
+
dev:
|
|
14
|
+
- supabase
|
|
15
|
+
- supabase-postgres-best-practices
|
|
16
|
+
qa:
|
|
17
|
+
- supabase
|
|
18
|
+
- supabase-postgres-best-practices
|
|
19
|
+
issue:
|
|
20
|
+
- supabase
|
|
21
|
+
- supabase-postgres-best-practices
|
|
22
|
+
review: []
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Supabase
|
|
26
|
+
|
|
27
|
+
## Verification
|
|
28
|
+
|
|
29
|
+
Run the verification commands from the platform directory: `analyze` (`supabase db lint`) and `test` (`supabase test db`). These commands require the Supabase CLI and a running Docker daemon.
|
|
30
|
+
|
|
31
|
+
Do not claim the database schema is valid, clean, or passes tests unless you ran the commands and saw the results. All tests run hermetically against local Docker containers; never point tests at a remote Supabase project.
|
|
32
|
+
|
|
33
|
+
## Questions for the issue writer
|
|
34
|
+
|
|
35
|
+
- **Authorization & RLS**: Who is permitted to select, insert, update, and delete rows in these tables? Which Postgres roles (`anon`, `authenticated`) should have access?
|
|
36
|
+
- **Relationships & Deletes**: If a parent row or user is deleted, what is the expected cascade behavior (`CASCADE`, `SET NULL`, or `RESTRICT`)?
|
|
37
|
+
- **Storage & Files**: If files are uploaded, what are the allowed MIME types, max file sizes, and who can overwrite or delete objects?
|
|
38
|
+
- **Query Performance & Filters**: Which columns will be filtered in `WHERE` clauses, sorted, or referenced in RLS policies (`auth.uid() = user_id`)?
|
|
39
|
+
|
|
40
|
+
## Hygiene for the developer
|
|
41
|
+
|
|
42
|
+
- Follow the official guidelines from `supabase` and `supabase-postgres-best-practices`.
|
|
43
|
+
- Enable Row Level Security on every public table: `ALTER TABLE <table_name> ENABLE ROW LEVEL SECURITY;`.
|
|
44
|
+
- Avoid using `auth.role()` in policies; use the `TO` clause instead (e.g. `TO authenticated` or `TO anon`).
|
|
45
|
+
- Combine `TO authenticated` with an explicit ownership predicate (e.g. `(select auth.uid()) = user_id`) to prevent broken object-level authorization (BOLA / IDOR).
|
|
46
|
+
- Always include both `USING` and `WITH CHECK` clauses on `UPDATE` policies so users cannot reassign ownership columns.
|
|
47
|
+
- Ensure all views on tables with RLS specify `WITH (security_invoker = true)` (Postgres 15+).
|
|
48
|
+
- Avoid `SECURITY DEFINER` functions in the `public` schema; if required, keep them in a private schema and validate caller identity explicitly.
|
|
49
|
+
- Create an index on every foreign key column and on every column evaluated in RLS policies.
|
|
50
|
+
- Structure all schema changes as versioned, timestamped migrations in `supabase/migrations/`.
|
|
51
|
+
- Maintain test and sample data in `supabase/seed.sql` using idempotent statements (`INSERT ... ON CONFLICT DO NOTHING;`).
|
|
52
|
+
- Write pgTAP test suites in `supabase/tests/` verifying that unauthorized or anonymous callers cannot read or modify protected rows.
|
|
53
|
+
|
|
54
|
+
## What the auditor looks for in the diff
|
|
55
|
+
|
|
56
|
+
- **Missing RLS**: Any table created in an exposed schema without `ENABLE ROW LEVEL SECURITY`.
|
|
57
|
+
- **Incomplete Update Policies**: An `UPDATE` policy that defines `USING` but omits `WITH CHECK`.
|
|
58
|
+
- **Permissive Policies**: Policies using `USING (true)` or checking only `TO authenticated` without row-ownership verification on private user data.
|
|
59
|
+
- **Unprotected Views**: Views defined without `security_invoker = true`.
|
|
60
|
+
- **Privilege Escalation**: `SECURITY DEFINER` functions placed in `public` or callable without `auth.uid()` checks.
|
|
61
|
+
- **Missing Indexes**: Foreign key constraints or RLS filter columns without an index.
|
|
62
|
+
- **Destructive Migrations**: `DROP TABLE`, `DROP COLUMN`, or `TRUNCATE` operations without explicit migration plans or rollback safety.
|
|
63
|
+
- **Exposed Secrets**: Hardcoded `service_role` keys, API tokens, or service passwords in code, migrations, or seed files.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# What `relevo-go.yml` and `relevo-queue.yml` run when `agent.build` in relevo.yml is `antigravity`. This file is not a
|
|
2
|
+
# workflow: each block below replaces, in the shared workflow, the lines between the markers with the same name, and
|
|
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 ubuntu-latest (x86-64).
|
|
5
|
+
#
|
|
6
|
+
# What Antigravity CLI cannot do on its own, and what stands in for it (docs/security.md):
|
|
7
|
+
# turn and token limits the run guard (scripts/agent-guard.mjs) reads the event stream and stops the agent; the numbers
|
|
8
|
+
# are those of relevo.yml (limits.run_max_turns, limits.run_max_tokens)
|
|
9
|
+
# cost limit none: the quota and the budget of the Google Cloud project that owns the key are the ceiling.
|
|
10
|
+
# Use a key made for Relevo, in its own project, with a quota
|
|
11
|
+
# refused tools the agent exits 0 when a tool is refused; the guard reads the refusal and fails the run
|
|
12
|
+
# closed tool list the settings file written below: the web is denied and only listed commands are allowed. It is
|
|
13
|
+
# a prefix list, not an operating-system boundary: `node` can run any script, as Bash can for Claude
|
|
14
|
+
# the key GEMINI_API_KEY is an environment variable of the agent's own commands, so the agent can read it
|
|
15
|
+
# the binary pinned by version and sha512 below; to update it, copy the url and the checksum from
|
|
16
|
+
# https://antigravity-cli-auto-updater-974169037036.us-central1.run.app/manifests/linux_amd64.json
|
|
17
|
+
|
|
18
|
+
# relevo:credential begin
|
|
19
|
+
# Without a model credential the cloud cannot build: the issue stays ready and a comment says to run it locally.
|
|
20
|
+
HAS_CREDENTIAL: ${{ secrets.GEMINI_API_KEY != '' }}
|
|
21
|
+
# relevo:credential end
|
|
22
|
+
|
|
23
|
+
# relevo:agent-step begin
|
|
24
|
+
# Antigravity CLI, at the version and with the checksum written here. The installer script of the vendor takes
|
|
25
|
+
# the latest version; this takes only the one that was tried.
|
|
26
|
+
- name: Install the Antigravity CLI
|
|
27
|
+
env:
|
|
28
|
+
AGY_VERSION: '1.4.4'
|
|
29
|
+
AGY_URL: https://storage.googleapis.com/antigravity-public/antigravity-cli/1.4.4-4783783166214144/linux-x64/cli_linux_x64.tar.gz
|
|
30
|
+
AGY_SHA512: 7ce149d350344c1046c7b59b6659bcc5d659f351bf3a07c6c085887d93427ceb89d927752e4ccd0844b53ffb91d33d6a49f72835ceb276c7cffdd13cfb8023b8
|
|
31
|
+
run: |
|
|
32
|
+
curl -fsSL -o "$RUNNER_TEMP/agy.tar.gz" "$AGY_URL"
|
|
33
|
+
echo "$AGY_SHA512 $RUNNER_TEMP/agy.tar.gz" | sha512sum --check --strict -
|
|
34
|
+
tar -xzf "$RUNNER_TEMP/agy.tar.gz" -C "$RUNNER_TEMP" antigravity
|
|
35
|
+
mkdir -p "$HOME/.local/bin"
|
|
36
|
+
install -m 0755 "$RUNNER_TEMP/antigravity" "$HOME/.local/bin/agy"
|
|
37
|
+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
38
|
+
[ "$("$HOME/.local/bin/agy" --version)" = "$AGY_VERSION" ] || { echo "::error::agy is not version $AGY_VERSION"; exit 1; }
|
|
39
|
+
|
|
40
|
+
# The Relevo plugin is rendered from the package unpacked in the step "Check the toolchain" and installed for the
|
|
41
|
+
# machine, so nothing is added to the working tree (the agent needs it clean). The settings of the run go to the
|
|
42
|
+
# user-level file of the CLI, outside the repository: a pull request cannot loosen them.
|
|
43
|
+
- name: Set up the Antigravity run
|
|
44
|
+
env:
|
|
45
|
+
PLATFORM: ${{ needs.decide.outputs.platform }}
|
|
46
|
+
run: |
|
|
47
|
+
for f in scripts/cli.mjs scripts/agent-guard.mjs; do
|
|
48
|
+
[ -f "$RUNNER_TEMP/relevo/$f" ] || { echo "::error::The Relevo package $RELEVO_VERSION lacks $f"; exit 1; }
|
|
49
|
+
done
|
|
50
|
+
node "$RUNNER_TEMP/relevo/scripts/cli.mjs" init --tools antigravity --scope machine --yes
|
|
51
|
+
node "$RUNNER_TEMP/relevo/scripts/agent-guard.mjs" settings --platform "$PLATFORM" --plugin-root "$RUNNER_TEMP/relevo" --out "$HOME/.gemini/antigravity-cli/settings.json"
|
|
52
|
+
git config user.name "github-actions[bot]"
|
|
53
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
54
|
+
|
|
55
|
+
# The issue text is data, never instructions: the skill tells the agent so. The decision the `decide` job already
|
|
56
|
+
# made travels with the prompt, encoded so that the issue title in it is never read as an instruction. Both
|
|
57
|
+
# values reach the shell as variables, never pasted into the script.
|
|
58
|
+
- id: agent
|
|
59
|
+
env:
|
|
60
|
+
OPENSPEC_TELEMETRY: '0'
|
|
61
|
+
GH_TOKEN: ${{ github.token }}
|
|
62
|
+
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
|
63
|
+
ISSUE: ${{ github.event.issue.number || inputs.issue }}
|
|
64
|
+
DECISION: ${{ needs.decide.outputs.decision_b64 }}
|
|
65
|
+
run: |
|
|
66
|
+
node "$RUNNER_TEMP/relevo/scripts/agent-guard.mjs" run \
|
|
67
|
+
--prompt "/relevo:go $ISSUE --decision $DECISION" \
|
|
68
|
+
--config relevo.yml --timeout-minutes 50 \
|
|
69
|
+
--summary "$RUNNER_TEMP/relevo-guard-summary.json" --github-output "$GITHUB_OUTPUT"
|
|
70
|
+
# relevo:agent-step end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: Deploy Supabase Migrations
|
|
2
|
+
|
|
3
|
+
# Automatically applies migrations to Supabase Cloud after Relevo pull requests are merged.
|
|
4
|
+
# - Push to base branch ('dev'): deploys to Staging.
|
|
5
|
+
# - Push to release branch ('main'): deploys to Production (under GitHub Environment protection).
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
branches:
|
|
10
|
+
- dev # Relevo base branch (Staging)
|
|
11
|
+
- main # Relevo release branch (Production)
|
|
12
|
+
paths:
|
|
13
|
+
- 'supabase/migrations/**'
|
|
14
|
+
- 'supabase/functions/**'
|
|
15
|
+
- 'supabase/config.toml'
|
|
16
|
+
workflow_dispatch:
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
deploy-staging:
|
|
20
|
+
name: Deploy to Staging
|
|
21
|
+
if: github.ref_name == 'dev'
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
environment: staging
|
|
24
|
+
steps:
|
|
25
|
+
- name: Checkout code
|
|
26
|
+
uses: actions/checkout@v4
|
|
27
|
+
|
|
28
|
+
- name: Setup Supabase CLI
|
|
29
|
+
uses: supabase/setup-cli@v1
|
|
30
|
+
with:
|
|
31
|
+
version: latest
|
|
32
|
+
|
|
33
|
+
- name: Link and Push Migrations to Staging
|
|
34
|
+
env:
|
|
35
|
+
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
|
|
36
|
+
run: |
|
|
37
|
+
supabase link --project-ref ${{ secrets.SUPABASE_PROJECT_ID_STAGING }}
|
|
38
|
+
supabase db push
|
|
39
|
+
|
|
40
|
+
- name: Deploy Edge Functions (if present)
|
|
41
|
+
if: hashFiles('supabase/functions/**') != ''
|
|
42
|
+
env:
|
|
43
|
+
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
|
|
44
|
+
run: |
|
|
45
|
+
supabase functions deploy
|
|
46
|
+
|
|
47
|
+
deploy-production:
|
|
48
|
+
name: Deploy to Production
|
|
49
|
+
if: github.ref_name == 'main'
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
# Configure the 'production' environment in GitHub Settings > Environments with required reviewers
|
|
52
|
+
# so human approval is mandatory before this job executes.
|
|
53
|
+
environment: production
|
|
54
|
+
steps:
|
|
55
|
+
- name: Checkout code
|
|
56
|
+
uses: actions/checkout@v4
|
|
57
|
+
|
|
58
|
+
- name: Setup Supabase CLI
|
|
59
|
+
uses: supabase/setup-cli@v1
|
|
60
|
+
with:
|
|
61
|
+
version: latest
|
|
62
|
+
|
|
63
|
+
- name: Link and Push Migrations to Production
|
|
64
|
+
env:
|
|
65
|
+
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
|
|
66
|
+
run: |
|
|
67
|
+
supabase link --project-ref ${{ secrets.SUPABASE_PROJECT_ID_PROD }}
|
|
68
|
+
supabase db push
|
|
69
|
+
|
|
70
|
+
- name: Deploy Edge Functions (if present)
|
|
71
|
+
if: hashFiles('supabase/functions/**') != ''
|
|
72
|
+
env:
|
|
73
|
+
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
|
|
74
|
+
run: |
|
|
75
|
+
supabase functions deploy
|
|
@@ -9,7 +9,7 @@ name: relevo-digest
|
|
|
9
9
|
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
10
10
|
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
11
11
|
env:
|
|
12
|
-
RELEVO_VERSION: '0.
|
|
12
|
+
RELEVO_VERSION: '0.5.0'
|
|
13
13
|
|
|
14
14
|
on:
|
|
15
15
|
schedule:
|
|
@@ -15,7 +15,7 @@ name: relevo-go
|
|
|
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.
|
|
18
|
+
RELEVO_VERSION: '0.5.0'
|
|
19
19
|
|
|
20
20
|
on:
|
|
21
21
|
issues:
|
|
@@ -82,8 +82,10 @@ jobs:
|
|
|
82
82
|
GH_LINKS_TOKEN: ${{ secrets.RELEVO_LINKS_TOKEN }}
|
|
83
83
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
84
84
|
ISSUE: ${{ github.event.issue.number || inputs.issue }}
|
|
85
|
+
# relevo:credential begin
|
|
85
86
|
# Without a model credential the cloud cannot build: the issue stays ready and a comment says to run it locally.
|
|
86
87
|
HAS_CREDENTIAL: ${{ secrets.ANTHROPIC_API_KEY != '' || secrets.CLAUDE_CODE_OAUTH_TOKEN != '' }}
|
|
88
|
+
# relevo:credential end
|
|
87
89
|
run: node .relevo-plugin/scripts/go-plan.mjs --repo "$GITHUB_REPOSITORY" --only "$ISSUE" --apply --claim --credential "$HAS_CREDENTIAL" --github-output "$GITHUB_OUTPUT"
|
|
88
90
|
|
|
89
91
|
# See the same step in `build`. A failure before the issue is claimed (the plugin cannot be cloned, the
|
|
@@ -164,6 +166,37 @@ jobs:
|
|
|
164
166
|
env:
|
|
165
167
|
OPENSPEC_TELEMETRY: '0'
|
|
166
168
|
|
|
169
|
+
# Stops before the agent, without spending a model turn, when the runner lacks a tool that the profile of the
|
|
170
|
+
# platform lists (the Flutter SDK, PHP and Composer, ...): otherwise the developer writes the code and QA only
|
|
171
|
+
# finds out at the end, after a full run. The package is unpacked outside the workspace on purpose: the agent needs
|
|
172
|
+
# a clean working tree. If you renamed the labels in relevo.yml, change the names below too.
|
|
173
|
+
- name: Check the toolchain
|
|
174
|
+
env:
|
|
175
|
+
GH_TOKEN: ${{ github.token }}
|
|
176
|
+
ISSUE: ${{ github.event.issue.number || inputs.issue }}
|
|
177
|
+
PLATFORM: ${{ needs.decide.outputs.platform }}
|
|
178
|
+
run: |
|
|
179
|
+
mkdir -p "$RUNNER_TEMP/relevo"
|
|
180
|
+
npm pack "@elalfner/relevo@$RELEVO_VERSION" --silent --pack-destination "$RUNNER_TEMP" > /dev/null
|
|
181
|
+
tar -xzf "$RUNNER_TEMP"/elalfner-relevo-*.tgz --strip-components=1 -C "$RUNNER_TEMP/relevo"
|
|
182
|
+
for f in scripts/toolchain-check.mjs scripts/render-message.mjs; do
|
|
183
|
+
[ -f "$RUNNER_TEMP/relevo/$f" ] || { echo "::error::The Relevo package $RELEVO_VERSION lacks $f"; exit 1; }
|
|
184
|
+
done
|
|
185
|
+
if result=$(node "$RUNNER_TEMP/relevo/scripts/toolchain-check.mjs" --platform "$PLATFORM" --plugin-root "$RUNNER_TEMP/relevo"); then
|
|
186
|
+
exit 0
|
|
187
|
+
fi
|
|
188
|
+
missing=$(printf '%s' "$result" | jq -r '.missing | join(", ")')
|
|
189
|
+
[ -n "$missing" ] || { echo "::error::The toolchain check failed: $result"; exit 1; }
|
|
190
|
+
title=$(gh issue view "$ISSUE" --json title --jq .title)
|
|
191
|
+
assignee=$(gh issue view "$ISSUE" --json assignees --jq '.assignees[0].login // empty')
|
|
192
|
+
body=$(jq -n --arg assignee "$assignee" --arg issue "$ISSUE" --arg title "$title" --arg tools "$missing" --arg platform "$PLATFORM" \
|
|
193
|
+
'{assignee: $assignee, issue: $issue, title: $title, tools: $tools, platform: $platform}' | node "$RUNNER_TEMP/relevo/scripts/render-message.mjs" toolchain_missing --config relevo.yml)
|
|
194
|
+
gh issue edit "$ISSUE" --remove-label relevo:ready --remove-label relevo:queued --remove-label relevo:building --add-label relevo:qa-failed
|
|
195
|
+
gh issue comment "$ISSUE" --body "$body"
|
|
196
|
+
echo "::error::The runner has no $missing, so nothing was built"
|
|
197
|
+
exit 1
|
|
198
|
+
|
|
199
|
+
# relevo:agent-step begin
|
|
167
200
|
- id: agent
|
|
168
201
|
uses: anthropics/claude-code-action@v1
|
|
169
202
|
env:
|
|
@@ -203,6 +236,7 @@ jobs:
|
|
|
203
236
|
--max-budget-usd 5
|
|
204
237
|
--allowedTools "Bash,Read,Write,Edit,Glob,Grep,Task"
|
|
205
238
|
--disallowedTools "WebFetch,WebSearch,Bash(curl:*),Bash(wget:*)"
|
|
239
|
+
# relevo:agent-step end
|
|
206
240
|
|
|
207
241
|
# 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
242
|
# finished, because the agent's preflight needs a clean working tree and a checkout inside it would not be.
|
|
@@ -10,7 +10,7 @@ name: relevo-queue
|
|
|
10
10
|
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
11
11
|
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
12
12
|
env:
|
|
13
|
-
RELEVO_VERSION: '0.
|
|
13
|
+
RELEVO_VERSION: '0.5.0'
|
|
14
14
|
|
|
15
15
|
on:
|
|
16
16
|
issues:
|
|
@@ -62,8 +62,10 @@ jobs:
|
|
|
62
62
|
# name, never to write, and an empty value is fine (public repositories are read without it).
|
|
63
63
|
GH_LINKS_TOKEN: ${{ secrets.RELEVO_LINKS_TOKEN }}
|
|
64
64
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
65
|
+
# relevo:credential begin
|
|
65
66
|
# Without a model credential nothing is dispatched.
|
|
66
67
|
HAS_CREDENTIAL: ${{ secrets.ANTHROPIC_API_KEY != '' || secrets.CLAUDE_CODE_OAUTH_TOKEN != '' }}
|
|
68
|
+
# relevo:credential end
|
|
67
69
|
run: |
|
|
68
70
|
result=$(node .relevo-plugin/scripts/requeue.mjs --repo "$GITHUB_REPOSITORY" --credential "$HAS_CREDENTIAL")
|
|
69
71
|
echo "$result"
|
|
@@ -10,7 +10,7 @@ name: relevo-review
|
|
|
10
10
|
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
11
11
|
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
12
12
|
env:
|
|
13
|
-
RELEVO_VERSION: '0.
|
|
13
|
+
RELEVO_VERSION: '0.5.0'
|
|
14
14
|
|
|
15
15
|
on:
|
|
16
16
|
pull_request_review:
|
|
@@ -8,7 +8,7 @@ name: relevo-sla
|
|
|
8
8
|
# The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
|
|
9
9
|
# were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
|
|
10
10
|
env:
|
|
11
|
-
RELEVO_VERSION: '0.
|
|
11
|
+
RELEVO_VERSION: '0.5.0'
|
|
12
12
|
|
|
13
13
|
on:
|
|
14
14
|
schedule:
|