@elalfner/relevo 0.5.0 → 0.7.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/dev-server-guard.mjs +32 -0
- package/scripts/extra-server.mjs +78 -0
- package/scripts/go-worktree.mjs +40 -0
- package/scripts/lib/dev-server-guard.mjs +49 -0
- package/scripts/lib/doctor.mjs +14 -1
- package/scripts/lib/extra-server.mjs +84 -0
- package/scripts/lib/go-local.mjs +4 -3
- package/scripts/lib/go-worktree.mjs +85 -0
- package/scripts/lib/init.mjs +2 -2
- package/scripts/lib/review-adapters.mjs +15 -2
- package/scripts/lib/review-brief.mjs +12 -4
- package/scripts/lib/review-state.mjs +30 -4
- package/scripts/lib/web-preflight.mjs +87 -0
- package/scripts/lib/web-review.mjs +82 -1
- package/scripts/lib/worktree-path.mjs +11 -0
- package/scripts/review-adapter.mjs +5 -3
- package/scripts/review-brief.mjs +3 -2
- package/scripts/review-state.mjs +12 -5
- package/scripts/web-preflight.mjs +47 -0
- package/scripts/web-review.mjs +80 -29
- package/skills/go/SKILL.md +13 -1
- package/skills/init/SKILL.md +2 -2
- package/skills/review/SKILL.md +48 -10
- package/stacks/flutter.md +45 -7
- package/templates/workflows/relevo-digest.yml +1 -1
- package/templates/workflows/relevo-go.yml +1 -1
- package/templates/workflows/relevo-queue.yml +1 -1
- package/templates/workflows/relevo-review.yml +1 -1
- package/templates/workflows/relevo-sla.yml +1 -1
package/skills/review/SKILL.md
CHANGED
|
@@ -19,6 +19,7 @@ Scripts live in `${CLAUDE_PLUGIN_ROOT}/scripts/`. Run them with `node`. They hol
|
|
|
19
19
|
- Never patch code during the review. A defect found here goes through `/relevo:fix`.
|
|
20
20
|
- Never waive a finding, and never suggest or hint that one could be waived: a waiver is the person's decision, written by them as `waive <number> <reason>`.
|
|
21
21
|
- Never commit the `evidence/` folder, and never force-push.
|
|
22
|
+
- Never read the project's environment files (`.env`, `.env.local` and the like); only `.env.example` and the specification give variable names. Never stop, restart or signal a server you did not start with `extra-server.mjs`, above all the one the person started.
|
|
22
23
|
|
|
23
24
|
The only `adb` commands you may run are: list devices, clear or read the log (`logcat`), and the screenshot taken by `capture.mjs`.
|
|
24
25
|
|
|
@@ -31,7 +32,8 @@ Stop with a clear message, changing nothing, if any of these fails:
|
|
|
31
32
|
- `node ${CLAUDE_PLUGIN_ROOT}/scripts/validate-config.mjs relevo.yml` exits non-zero. Show its output.
|
|
32
33
|
- `git check-ignore -q evidence/` fails: the evidence folder is not ignored by git. Tell the person to add `evidence/` to `.gitignore`.
|
|
33
34
|
- The issue is not `built` (or `in-review`, to resume). Say which state it is in and what to do (`/relevo:go`, `/relevo:fix`).
|
|
34
|
-
- `node ${CLAUDE_PLUGIN_ROOT}/scripts/review-adapter.mjs --platform <platform>` (the platform of the issue, from `platform-of.mjs`) exits 1: no review is possible for that platform and target; show the `reason` and stop, changing nothing. If it prints `needs_autopilot: true` and `review.autopilot` is `off`, stop and
|
|
35
|
+
- `node ${CLAUDE_PLUGIN_ROOT}/scripts/review-adapter.mjs --platform <platform>` (the platform of the issue, from `platform-of.mjs`) exits 1: no review is possible for that platform and target; show the `reason` and stop, changing nothing. If it prints `needs_autopilot: true` and `review.autopilot` is `off`, stop and show its `advice` as it is: it recommends `verify`, names `precheck` as the cautious option, and is already in the language of `relevo.yml`.
|
|
36
|
+
- Not a stop, a warning: if `git status --porcelain -- relevo.yml` prints anything, tell the person that `relevo.yml` has uncommitted changes, that this review uses them as they are, and that they belong on the base branch through a commit of their own. Never stage or commit `relevo.yml` in this review.
|
|
35
37
|
- For an adapter whose kind is `device`: `node ${CLAUDE_PLUGIN_ROOT}/scripts/device.mjs` exits non-zero. Show its message: it says what to connect or authorise. Keep the serial it prints as `<serial>`. A `browser` adapter needs no device.
|
|
36
38
|
|
|
37
39
|
Find the repository (`$GITHUB_REPOSITORY`, or `gh repo view --json nameWithOwner -q .nameWithOwner`), the issue's branch (`git branch -r --list "origin/feature/$ARGUMENTS-*"`) and its change folder: the one `openspec list --json` names that starts with `$ARGUMENTS-` (`openspec/changes/<name>`).
|
|
@@ -77,14 +79,28 @@ If `evidence/review-state.json` exists for this issue, resume: show the confirme
|
|
|
77
79
|
|
|
78
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.
|
|
79
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):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/web-preflight.mjs --url <address>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
It prints JSON and exits by what is missing. Tell the person in their language:
|
|
89
|
+
|
|
90
|
+
- `3`: there is no `node_modules`; ask them to run `npm install` first, and stop until they say it is done.
|
|
91
|
+
- `4` or `5`: Playwright or its Chromium is missing. Ask for permission to install them; only on a yes run `npm install --no-save playwright` and `npx playwright install chromium`, then `git diff --quiet -- package-lock.json || git restore -- package-lock.json` (npm can rewrite the lock file even with `--no-save`), and run the preflight again.
|
|
92
|
+
- `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
|
+
- `0`: ready.
|
|
94
|
+
|
|
80
95
|
### 3. Brief the person, then stop
|
|
81
96
|
|
|
82
|
-
Tell the person to start the app on the device themselves (for example `flutter run -d <serial>` in another terminal). Then show:
|
|
97
|
+
Tell the person to start the app on the device themselves (for example `flutter run -d <serial>` in another terminal). For a `browser` adapter, tell them the exact command to start it in their own terminal: the `start` of the preflight, with the variable names of its `env` and the address under review. Then show:
|
|
83
98
|
|
|
84
|
-
0. The **brief**, computed and not written by you: save the issue body and the `built` comment outside the working tree (`gh issue view $ARGUMENTS --json title,body,comments`), then `node ${CLAUDE_PLUGIN_ROOT}/scripts/review-brief.mjs --issue $ARGUMENTS --title "<title>" --body-file <body> --built-file <the built comment> --language <language>`. It prints about five lines: what was asked, the outcome, what to check first and how many lines there are to check. Show them as they are.
|
|
85
|
-
1. A **shot list**: two or three screenshots worth taking, named like `01_initial`, `02_action`, `03_result`, taken from the scenarios. For a change with nothing visible, say there are no screenshots to take and the log will be audited anyway.
|
|
99
|
+
0. The **brief**, computed and not written by you: save the issue body and the `built` comment outside the working tree (`gh issue view $ARGUMENTS --json title,body,comments`), then `node ${CLAUDE_PLUGIN_ROOT}/scripts/review-brief.mjs --issue $ARGUMENTS --title "<title>" --body-file <body> --built-file <the built comment> --language <language> --autopilot <review.autopilot>`. It prints about five lines: what was asked, the outcome, what to check first and how many lines there are to check, and, with `precheck` or `verify`, a fixed note that the person confirms every line. Show them as they are.
|
|
100
|
+
1. A **shot list**: two or three screenshots worth taking, named like `01_initial`, `02_action`, `03_result`, taken from the scenarios. For a change with nothing visible, say there are no screenshots to take and the log will be audited anyway. In a browser review, each shot also names what proves the page shows that state and no other shot's (the text of the alert, the heading, the URL): it becomes the check of its capture.
|
|
86
101
|
2. The **checklist**: `node ${CLAUDE_PLUGIN_ROOT}/scripts/checklist.mjs openspec/changes/<change folder>`, one numbered line per scenario, edge cases included. Point out any step the person must do outside the app, because you will not do it.
|
|
87
|
-
3.
|
|
102
|
+
3. With `review.autopilot` `precheck` or `verify`, the **plan of the walk**, so the person does not have to ask: one line per checklist line you walk yourself, `<n>. <method>: <how>`, where the method is `browser` (a Playwright assertion), `command` (an npm script of `package.json`, such as typecheck, lint, test or build), `read` (code, CI or docs) or `server <port>` (a run against a server you start with another configuration: say which variables are set or removed). Then the lines left to the person. Every configuration you will start is listed here, before the person confirms the data (Autopilot, step 1), so that confirmation covers them too.
|
|
103
|
+
4. What they can write: `capture <name>`, `ok <numbers>` (or `ok all`), `fail <number> <what went wrong>`, `retest <numbers>`, `done`. They may write in any language; understand the intent.
|
|
88
104
|
|
|
89
105
|
**Then end your turn.** Make no further tool calls until the person writes. Do not advance on your own and do not run the log audit early.
|
|
90
106
|
|
|
@@ -92,7 +108,7 @@ Tell the person to start the app on the device themselves (for example `flutter
|
|
|
92
108
|
|
|
93
109
|
Handle each message as it comes:
|
|
94
110
|
|
|
95
|
-
- `capture <name>`: `node ${CLAUDE_PLUGIN_ROOT}/scripts/capture.mjs <name>`, then record it with `review-state.mjs capture <path>`. Reply with the saved path only. If the script rejects the name, show its message and ask for another.
|
|
111
|
+
- `capture <name>`: `node ${CLAUDE_PLUGIN_ROOT}/scripts/capture.mjs <name>`, then record it with `review-state.mjs capture <path>`. Reply with the saved path only. If the script rejects the name, show its message and ask for another. In a browser review there is no device to capture: add the shot, with its check, to the shot list and to the review script, which then needs QA again (Autopilot, step 4).
|
|
96
112
|
- `ok <numbers>`: `review-state.mjs set <n> ok` for each. `fail <n> <note>`: `review-state.mjs set <n> fail <note>`. `retest <numbers>`: `review-state.mjs set <n> pending` for each, so the line must be confirmed again (useful after a fix).
|
|
97
113
|
- A bug or something that looks wrong: do not edit anything. Record it as a `fail` and say the fix goes through `/relevo:fix $ARGUMENTS`; the review can resume afterwards.
|
|
98
114
|
- `waive <number> <reason>`: the person's decision, available once the audit has findings; run `review-state.mjs waive <number> <reason>`. It refuses an empty reason and an inconclusive audit; show its message.
|
|
@@ -102,14 +118,21 @@ After **each** message of the person, keep the device log from being lost to its
|
|
|
102
118
|
|
|
103
119
|
Never go to step 5 without an explicit `done` (or the equivalent in the person's language).
|
|
104
120
|
|
|
121
|
+
Captures belong to this step: the shot list is taken while the person tests, before the agreement, and `review-state.mjs capture` refuses after `agree`. If the person asks for screenshots after agreeing, point to the files already in `evidence/` and do not drive again.
|
|
122
|
+
|
|
105
123
|
### Autopilot (only when `review.autopilot` is `precheck` or `verify`)
|
|
106
124
|
|
|
107
125
|
When it is `off` skip this section: the person does everything. The person always confirms the lines and gives the agreement; nothing here replaces that.
|
|
108
126
|
|
|
109
127
|
1. **Confirm the data.** Before any driven step, ask the person to confirm in writing that the app points to data that is not production. Record it only after they say so: `node ${CLAUDE_PLUGIN_ROOT}/scripts/review-state.mjs confirm-env`. Without it `precheck` and `auto` are refused.
|
|
110
|
-
2. **Walk the lines.** `precheck`: after you walk a line, record your own result with `review-state.mjs precheck <n> ok|fail <note>`; the line stays pending until the person writes `ok`. `verify`: in addition, when the `THEN` of a line was proved by an assertion, a semantic finder or the log, record `review-state.mjs auto <n> <what proved it>`; the person confirms the other lines. A line about appearance, animation or fidelity to an image is never marked automatically.
|
|
128
|
+
2. **Walk the lines.** `precheck`: after you walk a line, record your own result with `review-state.mjs precheck <n> ok|fail <note>`; the line stays pending until the person writes `ok`. `verify`: in addition, when the `THEN` of a line was proved by an assertion, a semantic finder or the log, or by the exit code of a project command, record `review-state.mjs auto <n> <what proved it>`; the person confirms the other lines. A line about appearance, animation or fidelity to an image is never marked automatically. A line checked only by reading code, CI or docs is recorded with `precheck` and what you found, never with `auto`.
|
|
129
|
+
- **Project commands.** Run the npm script of `package.json` that the line names (typecheck, lint, test, build), never a script that fixes or formats files. Run `git status --porcelain` before and after: if a tracked file changed, restore it with `git restore -- <file>` and tell the person which file changed. Record the command and its exit code as what proved the line.
|
|
111
130
|
3. **Flutter on a device.** Drive through the Dart and Flutter MCP tools: `launch_app`, `connect_dart_tooling_daemon`, then `flutter_driver` (`get_health`, `waitFor`, `tap`, `enter_text`, `scroll`, `screenshot`), `get_runtime_errors`, `get_app_logs`, and `hot_reload` after a micro-fix. It needs `lib/main_driver.dart` with `enableFlutterDriverExtension()`. Rules: never pass `timeout` to `flutter_driver` (it breaks the MCP server); find widgets only by semantic finders (text, key, tooltip, type), never by coordinates; if a finder fails twice, stop driving and go back to manual for that line; the log of the MCP server is the source of the audit, and if it does not answer or is empty the audit is inconclusive.
|
|
112
|
-
4. **A browser (Vue, or Flutter on the web).** Write a Playwright script from the scenarios to `evidence/review-web.mjs`: it exports a default async function that receives `{ page, baseUrl, line, credentials }`, uses `node:assert/strict`, and calls `line(<checklist number>, "<what is asserted>", async () => { ... })` once per line it can check. Every `THEN` it covers must have an assertion that would fail if the feature did not exist (text, URL, response, absence of errors), not a screenshot. A test login is read with `credentials("NAME")` where `NAME` is an environment variable the person names; you never read, print or store its value.
|
|
131
|
+
4. **A browser (Vue, or Flutter on the web).** Write a Playwright script from the scenarios to `evidence/review-web.mjs`: it exports a default async function that receives `{ page, baseUrl, line, credentials, capture }`, uses `node:assert/strict`, and calls `line(<checklist number>, "<what is asserted>", async () => { ... })` once per line it can check. Every `THEN` it covers must have an assertion that would fail if the feature did not exist (text, URL, response, absence of errors), not a screenshot. A test login is read with `credentials("NAME")` where `NAME` is an environment variable the person names; you never read, print or store its value.
|
|
132
|
+
- **Screenshots.** Turn the shot list into `capture("<name>", async () => { <the check of that shot> }, { fullPage: true })` calls. The check runs first, and when it fails no screenshot is taken and the capture is reported as failed. `capture` is the only way to take one: the runner refuses `page.screenshot`, any file path, and anything the script prints goes to `script-console.txt`, never to you. You never open the PNG files; their paths are all you see.
|
|
133
|
+
- **QA.** Then launch `relevo:qa` on the script, read-only, with the shot list: it rejects a `capture` name that is not on the shot list, and a check that would also hold for another shot (for example only `[role="alert"]` for two different errors). Only if it answers `QA PASSED`, record that exact script with `node ${CLAUDE_PLUGIN_ROOT}/scripts/review-state.mjs qa-pass evidence/review-web.mjs`. Any change to the script afterwards, even one port, needs `relevo:qa` and `qa-pass` again: the runner refuses a script whose content QA did not pass.
|
|
134
|
+
- **The normal run.** Run `node ${CLAUDE_PLUGIN_ROOT}/scripts/web-preflight.mjs --url <the address of the app>` again: it must exit 0. Then run `node ${CLAUDE_PLUGIN_ROOT}/scripts/web-review.mjs --script evidence/review-web.mjs --url <the address of the app> --allow-origins <review.allowed_origins> --secret-env <the names> --out evidence`. The runner itself refuses before any browser starts when the state has no confirmation of the data, the autopilot is `off` or the review is already agreed. The browser is its own, with a clean profile, and can reach only `localhost`, the address under review and `review.allowed_origins`; a refused script is not run in any other way. Its `auto-ok` lines are recorded as in step 2, its `auto-fail` lines as `precheck <n> fail` with the message, its captures are already recorded in the review state, and `evidence/web-log.txt` is the log of step 5 (audit with `--package web`). When it says `the page itself failed`, that is the cause to tell the person, not the selector timeout after it. The script checks are a tripwire, not a sandbox: if it touches anything sensitive, show it to the person before it runs.
|
|
135
|
+
- **Runs with another configuration** (the `server <port>` lines of the plan). Start each server yourself on a free localhost port with `node ${CLAUDE_PLUGIN_ROOT}/scripts/extra-server.mjs start --port <port> [--set NAME=value] [--unset NAME] -- npx vite --port <port> --strictPort`. It refuses a busy port and refuses `--unset` while an environment file exists (the dev server loads it by itself): then that line is pre-checked by reading the code or left for the person. Wait until it answers with `web-preflight.mjs --url http://localhost:<port> --wait 30000`, confirm it with `extra-server.mjs check --port <port>`, and run the same script with `--url http://localhost:<port> --out evidence/run-<port>`: each run has an output folder of its own, and these logs are never audited, because they show errors on purpose. Stop each one afterwards with `extra-server.mjs stop --port <port>`. The server the person started is never stopped or restarted.
|
|
113
136
|
5. **Never** drive in the person's own browser, on an origin that is not allowed, with coordinates, with `adb shell input`, Appium or Maestro, or send a message or make a call in another app.
|
|
114
137
|
|
|
115
138
|
### 5. Audit the log
|
|
@@ -119,6 +142,12 @@ package=$(node ${CLAUDE_PLUGIN_ROOT}/scripts/app-id.mjs)
|
|
|
119
142
|
adb -s <serial> logcat -d -v threadtime | node ${CLAUDE_PLUGIN_ROOT}/scripts/review-state.mjs audit --accumulated --package "$package"
|
|
120
143
|
```
|
|
121
144
|
|
|
145
|
+
For a `browser` adapter there is no device: audit only the log of the normal run, never one of `evidence/run-<port>/`:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/review-state.mjs audit evidence/web-log.txt --package web
|
|
149
|
+
```
|
|
150
|
+
|
|
122
151
|
`app-id.mjs` prints the application id of the project. The audit then looks only at the lines written by that app's processes, so other apps on the device are neither audited nor quoted. If `app-id.mjs` finds no id, run the audit without `--package`.
|
|
123
152
|
|
|
124
153
|
It prints `REVIEW PASSED`, `REVIEW REJECTED` with the numbered findings, or `REVIEW INCONCLUSIVE`, and writes `evidence/review-report.md`. **Inconclusive** means the log was empty or had no line of the app: it proves nothing, it is not a pass and it cannot be waived. Ask the person to start the app again, repeat the steps, and audit once more.
|
|
@@ -150,11 +179,20 @@ Only when `decide` exited 0.
|
|
|
150
179
|
### 8. Evidence
|
|
151
180
|
|
|
152
181
|
- `evidence.policy: local_only`: delete the `evidence/` folder now. Nothing is attached anywhere.
|
|
153
|
-
- `evidence.policy: attach_to_pr`: GitHub has no API for attaching images, so you cannot upload them. List the
|
|
182
|
+
- `evidence.policy: attach_to_pr`: GitHub has no API for attaching images, so you cannot upload them. List only the images, in every run folder (`find evidence -name '*.png'`), and ask the person to drag them into the pull request description; the logs, results and scripts repeat what the body already says. Keep the folder until they say they did, then delete it. When there is no image, delete the folder without asking and say there was nothing to attach.
|
|
154
183
|
|
|
155
184
|
### 9. Return to base branch
|
|
156
185
|
|
|
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,
|
|
186
|
+
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, when no other worktree holds the base branch, and when no dev server of the address under review runs from the working tree (switching would remove its files under it and break it until it is restarted).
|
|
187
|
+
|
|
188
|
+
For a `browser` adapter, first stop the servers you started, then ask the guard:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/extra-server.mjs stop --all
|
|
192
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/dev-server-guard.mjs --url <the address of the app>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Switch only when the guard exits 0. On 1 a server runs from the working tree; on 3 the guard cannot tell. In both cases do not switch: say why, and that the person can stop their server and run `git switch <base>` themselves.
|
|
158
196
|
|
|
159
197
|
```bash
|
|
160
198
|
[ "$(git rev-parse --git-dir)" = "$(git rev-parse --git-common-dir)" ] && ! git worktree list --porcelain | grep -qx "branch refs/heads/<base>" && git switch <base>
|
package/stacks/flutter.md
CHANGED
|
@@ -4,7 +4,7 @@ commands:
|
|
|
4
4
|
analyze: flutter analyze --no-pub
|
|
5
5
|
test: flutter test --reporter=compact --no-pub
|
|
6
6
|
format: dart format
|
|
7
|
-
generated: ['*.g.dart', '*.freezed.dart']
|
|
7
|
+
generated: ['*.g.dart', '*.freezed.dart', '*.mocks.dart']
|
|
8
8
|
tools: [flutter]
|
|
9
9
|
requires_skills: []
|
|
10
10
|
skills:
|
|
@@ -22,29 +22,67 @@ targets:
|
|
|
22
22
|
|
|
23
23
|
## Verification
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Run the commands of this profile from the folder of the platform: `analyze`, `test` and `format`. Run `format` on the files you changed, never on generated files.
|
|
26
|
+
|
|
27
|
+
When the Dart and Flutter MCP server is connected, you may use the tools that write nothing: `analyze_files` for analysis, `lsp` and `read_package_uris` to check an API or a symbol instead of guessing it, and `run_tests` when the server enables it (it is off by default). When a tool is missing, use the command of this profile. No agent uses `dart_format`, `dart_fix`, `pub`, `hot_reload`, `hot_restart` or `launch_app` during a run: they write files or drive an app, and `dart_format` formats the whole project, generated files included.
|
|
28
|
+
|
|
29
|
+
When annotated models or mocks changed, run `dart run build_runner build --delete-conflicting-outputs` before the tests.
|
|
26
30
|
|
|
27
31
|
Do not claim the code compiles, analyses clean or passes tests unless you ran the tools and saw it. If neither the tooling server nor Flutter is available, say so plainly in your report.
|
|
28
32
|
|
|
33
|
+
## Architecture
|
|
34
|
+
|
|
35
|
+
Follow the architecture that the project's rules declare (MVVM, bloc, riverpod or another). When they declare none, write new code with the structure of Flutter's architecture guide: UI and data in separate layers; data reached through repositories, which call stateless services that wrap each API or plugin; dependencies passed by constructor and kept private; no data logic in widgets. Command and Result objects, and any state-management package, are not required by this profile; use them when the project's rules do.
|
|
36
|
+
|
|
29
37
|
## Hygiene for the developer
|
|
30
38
|
|
|
39
|
+
Lifecycle and state:
|
|
40
|
+
|
|
31
41
|
- Check `mounted` before using a `BuildContext` or calling `setState` after an `await`.
|
|
32
|
-
- Dispose every controller, stream subscription and listener you create.
|
|
42
|
+
- Dispose every controller, stream subscription and listener you create. A `ChangeNotifierProvider(create:)` disposes its model itself.
|
|
43
|
+
- Never call an API, create a future or show a dialog inside `build()`; start them in `initState` or in an event handler.
|
|
33
44
|
- Avoid the null-assertion operator unless a prior check makes it provably safe.
|
|
34
45
|
- Give every screen loading, empty and error states.
|
|
35
46
|
- Prevent double submission of actions.
|
|
36
|
-
|
|
47
|
+
|
|
48
|
+
Layout:
|
|
49
|
+
|
|
50
|
+
- Keep layouts from overflowing on small screens: wrap a long `Text` or other unconstrained child of a `Row` or `Column` in `Expanded` or `Flexible`.
|
|
51
|
+
- Give a `ListView` or `GridView` inside a `Column` a bound (`Expanded` or a fixed height), and a `TextField` inside a `Row` a width (`Expanded` or `SizedBox`).
|
|
52
|
+
- Put `Expanded` and `Flexible` directly in a `Row`, `Column` or `Flex`, and `Positioned` directly in a `Stack`.
|
|
53
|
+
- Give each scroll view its own `ScrollController`.
|
|
54
|
+
|
|
55
|
+
Performance:
|
|
56
|
+
|
|
57
|
+
- Build long lists and grids with the builder constructors, not with a list of children.
|
|
58
|
+
- Keep `build()` cheap, and call `setState` as low in the tree as the change allows.
|
|
59
|
+
- Split large widgets into smaller widget classes rather than helper methods that return widgets.
|
|
60
|
+
- Keep `Opacity` and clipping out of animations; use `AnimatedOpacity` or a semi-transparent colour.
|
|
61
|
+
- Move heavy work, such as decoding a large JSON response, off the UI isolate with `Isolate.run`. On the web it still runs on the main thread.
|
|
62
|
+
|
|
63
|
+
Web:
|
|
64
|
+
|
|
65
|
+
- Use `package:web` and `dart:js_interop`; `dart:html` and `package:js` prevent the Wasm build.
|
|
66
|
+
- Do not use `dart:io` or `Platform` in code that runs on the web; check `kIsWeb` or use a conditional import.
|
|
67
|
+
|
|
68
|
+
Tests:
|
|
69
|
+
|
|
37
70
|
- Write unit and widget tests for the scenarios the platform allows. A scenario that can only be seen on a device or in another app is covered by correct code, and is verified later in the on-device review.
|
|
71
|
+
- Make fakes of repositories and services for tests; a mock is also acceptable. A plugin throws `MissingPluginException` in unit and widget tests, so put it behind the project's own class and fake that class.
|
|
38
72
|
|
|
39
73
|
## What the auditor looks for in the diff
|
|
40
74
|
|
|
41
|
-
- **Async and lifecycle**: a `BuildContext` use or `setState` after an `await` without a `mounted` check
|
|
75
|
+
- **Async and lifecycle**: a `BuildContext` use or `setState` after an `await` without a `mounted` check; a network call, a new future, `setState` or `showDialog` inside `build()`.
|
|
42
76
|
- **Leaks**: a controller, stream subscription or listener created and never disposed.
|
|
43
77
|
- **Null safety**: an unchecked null-assertion operator that can throw at runtime.
|
|
44
|
-
- **Layout**: unconstrained content that can overflow on a small screen.
|
|
78
|
+
- **Layout**: unconstrained content that can overflow on a small screen; a `ListView` or `GridView` in a `Column` without a bound; a `TextField` in a `Row` without a width; `Expanded`, `Flexible` or `Positioned` outside a compatible parent; one `ScrollController` attached to several scroll views.
|
|
45
79
|
- **States**: missing loading, empty or error states; an action that can be triggered twice.
|
|
80
|
+
- **Performance**: a long list built from a list of children; costly work in `build()`; `setState` high in the tree for a local change; `Opacity` or clipping inside an animation; a large JSON decode on the UI isolate.
|
|
81
|
+
- **Web**: an import of `dart:html` or `package:js`; `dart:io` or `Platform` in code that runs on the web.
|
|
82
|
+
- **Tests**: a test that calls a real plugin, network or service instead of a fake or a mock.
|
|
83
|
+
- **Architecture**: report only what breaks a rule the project wrote down. Whatever the project declares, an HTTP or GraphQL client called from a widget is a finding.
|
|
46
84
|
- **Android specifics**: an external-app launch with no handling for the app being missing, permission use without handling denial, behaviour on the back button.
|
|
47
|
-
- **Generated code**: leave generated files (`*.g.dart`, `*.freezed.dart`) out of the review.
|
|
85
|
+
- **Generated code**: leave generated files (`*.g.dart`, `*.freezed.dart`, `*.mocks.dart`) out of the review.
|
|
48
86
|
|
|
49
87
|
## Targets
|
|
50
88
|
|
|
@@ -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.7.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.7.0'
|
|
19
19
|
|
|
20
20
|
on:
|
|
21
21
|
issues:
|
|
@@ -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.7.0'
|
|
14
14
|
|
|
15
15
|
on:
|
|
16
16
|
issues:
|
|
@@ -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.7.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.7.0'
|
|
12
12
|
|
|
13
13
|
on:
|
|
14
14
|
schedule:
|