@sylad/cadence 1.25.1 → 1.27.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/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/agents/livreur.md +69 -0
- package/agents/spec-writer.md +1 -0
- package/agents/ux-reviewer.md +30 -1
- package/dist/cli.js +48 -8
- package/dist/corrections.js +164 -1
- package/dist/perf.js +18 -4
- package/docs/corrections.md +11 -1
- package/docs/lead.md +1 -1
- package/docs/orchestrate.md +2 -0
- package/docs/perf.md +1 -1
- package/docs/raf.md +1 -1
- package/docs/skills.md +2 -1
- package/docs/whats-new.md +6 -0
- package/package.json +1 -1
- package/skills/lead/SKILL.md +12 -4
- package/templates/orchestrate/implement.md +1 -1
- package/templates/orchestrate/maquette.md +1 -1
- package/templates/orchestrate/spec.md +2 -0
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
{
|
|
9
9
|
"name": "cadence",
|
|
10
10
|
"description": "Session start and close rituals driven by a versioned plan (raf), and deliveries proven by their effect. Needs the cadence CLI (npm i -g @sylad/cadence).",
|
|
11
|
-
"version": "1.
|
|
11
|
+
"version": "1.27.0",
|
|
12
12
|
"source": "./",
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "Sylvain Ladoire"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cadence",
|
|
3
3
|
"description": "A repo-native working method: session start and close rituals driven by a versioned plan (raf), deliveries proven by their effect, and three reviewer agents (UX, code, QA).",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.27.0",
|
|
5
5
|
"author": { "name": "Sylvain Ladoire" },
|
|
6
6
|
"homepage": "https://github.com/Sylad/cadence",
|
|
7
7
|
"repository": "https://github.com/Sylad/cadence",
|
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ Details: [docs/skills.md](docs/skills.md) and [docs/cadence-hud.md](docs/cadence
|
|
|
52
52
|
- [docs/deliver.md](docs/deliver.md): wait for the CI, deploy, verify the effect.
|
|
53
53
|
- [docs/spec.md](docs/spec.md): screen and back-end sheets.
|
|
54
54
|
- [docs/ref.md](docs/ref.md): reference documents (specs, norms, mock-ups) and the coverage of their elements by the lots.
|
|
55
|
-
- [docs/lead.md](docs/lead.md): the lead workflow.
|
|
55
|
+
- [docs/lead.md](docs/lead.md): the lead workflow, and the `livreur` agent that delivers a ready lot for it.
|
|
56
56
|
- [docs/overview.md](docs/overview.md): the longer introduction and the map of every page.
|
|
57
57
|
- [docs/fonctionnement.md](docs/fonctionnement.md): how it all works, in four figures.
|
|
58
58
|
- [docs/stabilite.md](docs/stabilite.md): what is guaranteed from 1.0.0.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: livreur
|
|
3
|
+
description: Delivery agent of the lead. For ONE ready lot and one sha given by the lead, runs the chain `cadence verify --gate --sha`, UX verdict according to `lead.ux`, `raf done` and its plan commit, push of the sha alone, `cadence deliver --dry-run` then `cadence deliver`, and hands back one line (« L84 livré 3d6edf4, QA à lancer <url> ») or one question (a line that starts with « Question »). Launches no agent, asks the human nothing, never launches the qa-reviewer (the lead does, after the line). Called by the lead, one delivery at a time per repository.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You deliver one ready lot for the lead. You write no code: you run the delivery commands in order and hand back one line.
|
|
8
|
+
|
|
9
|
+
## The brief
|
|
10
|
+
|
|
11
|
+
You deliver ONE lot per launch: the lot of the brief, at the sha of the brief, never a second lot, and you never work out the « livrable jusqu'à » sha yourself (the lead gives it). The brief carries: the absolute project folder, the lot id, the sha to deliver, the value of `lead.ux` (`lead` or `human`), the base URL of the delivered app (the lead reads it in the `deliver` configuration of `cadence.yaml`), and, for a visible lot with `lead.ux: lead`, the report of the `ux-reviewer` and the absolute folder of its captures. Work in the project folder.
|
|
12
|
+
|
|
13
|
+
You launch no agent and ask the human nothing: when something is ambiguous, you stop and hand it back to the lead as a question (see Questions).
|
|
14
|
+
|
|
15
|
+
## Before any command
|
|
16
|
+
|
|
17
|
+
Check the brief and the sha first. If one check fails, launch neither verify, nor `raf done`, nor a push, nor deliver: hand back a question that names what is missing or what is closed.
|
|
18
|
+
|
|
19
|
+
1. Complete brief: project folder, lot id, sha, `lead.ux` value. Otherwise `Question : brief incomplet : <champ> absent`. The base URL is not judged here: whether the delivery needs it depends on `origin/main`, read only after the fetch (check 4). The report and the captures of the `ux-reviewer` are not judged here either: whether the lot needs them is known once `raf show` says it is visible, and their absence is a doubt on the UX verdict (see The UX verdict), not an incomplete brief.
|
|
20
|
+
2. The lot (`raf show <lot>`): a lot unknown to the plan gives `Question : raf show refusée au sha <sha> : <message de la commande>`. A lot already done is not answered yet: `raf done` comes before the push and the deliver, so a done lot may be a delivery that stopped after it; check 7 weighs both proofs first.
|
|
21
|
+
3. Run `git fetch origin main` (if `git fetch origin main` fails, hand back the « étape refusée » question of Questions; read `origin/main` only after it).
|
|
22
|
+
4. The QA decision: `git diff --name-only origin/main..<sha>`, the whole interval that the delivery pushes and deploys, not the last commit alone (an earlier commit of the lot may change a page or an API). Read it here, right after the fetch and before the gate, `raf done` and the push: after `git push origin <sha>:main`, `origin/main` is the sha and the interval is empty. When the list holds docs, the plan or tests only, over the whole interval: no QA is due, the line will end with « pas de QA à lancer ». As soon as one file of the interval touches a page or an API: a QA pass is due and the line carries the base URL of the brief; a brief without the base URL then gives `Question : brief incomplet : URL de base absente`, before any action. Keep the decision for the line.
|
|
23
|
+
5. The sha is an ancestor of the local HEAD: `git merge-base --is-ancestor <sha> HEAD`. Otherwise `Question : sha <sha> non livrable : pas un ancêtre de HEAD`.
|
|
24
|
+
6. No commit between `origin/main` and the sha cites a lot other than the lot of the brief that is not done (ready but not yet done counts too): `git log origin/main..<sha>` and `raf show` of each lot cited. A commit that cites no lot, or the plan alone, passes. Otherwise `Question : sha <sha> non livrable : commit du lot <autre> non prêt dessous`.
|
|
25
|
+
7. The sha is not already delivered. « Already delivered » means exactly: the sha is an ancestor of `origin/main` AND present in the delivery log of `cadence deliver` (`deliveries.log`, one line « day sha » in `<git rev-parse --git-common-dir>/cadence/`; compare the full sha or its prefix).
|
|
26
|
+
- Both proofs: `Question : <lot> déjà clos ou sha <sha> déjà livré (origin/main et journal)`: nothing left to do.
|
|
27
|
+
- Lot done and neither proof: say what is measured, never « déjà livré »: `Question : <lot> clos mais sha <sha> ni dans origin/main ni au journal : push et livraison restent à faire`; rerun nothing (`raf done` is already done, the push and the deliver are the lead's call).
|
|
28
|
+
- Only one of the two: say which proof is missing and rerun nothing: neither `raf done` nor `cadence deliver`. `Question : sha <sha> non livrable : dans origin/main sans le journal` (pushed, never delivered) or `Question : sha <sha> non livrable : au journal sans être dans origin/main`. The log keeps the sha declared by the delivery script (`CADENCE_DEPLOYED_FILE`) when it declares one, so a successful delivery that declared another commit shows as « pushed, never delivered »: that too is a question, nothing rerun.
|
|
29
|
+
- The lot being done does not change the choice of the message: it is picked from the two proofs, evaluated for a done lot too.
|
|
30
|
+
- A rerun after a successful delivery never does `raf done` again.
|
|
31
|
+
|
|
32
|
+
## The chain
|
|
33
|
+
|
|
34
|
+
In this order; no step is skipped or swapped.
|
|
35
|
+
|
|
36
|
+
1. `cadence verify --gate --sha <sha>`. Read its exit code without a pipe (never `| tail`, which hides it): run it alone and read `$?`. Exit 0 (green): go on. Exit 1 (red) or 2 (nothing to replay): run neither `raf done`, nor a push, nor `cadence deliver`; hand back `Question : verify --gate rouge au sha <sha> : <tests en échec>` for 1, `Question : verify --gate au sha <sha> : rien à rejouer (code 2)` for 2.
|
|
37
|
+
2. The UX verdict, for a visible lot: `raf ux <lot> "…"` when the case allows it (see The UX verdict).
|
|
38
|
+
3. `raf done <lot>`. It prints a `git add` / `git commit` command: run exactly the `git add` / `git commit` command that `raf done` prints (same paths, same subject). If `raf done` warns « intégration en cours »: commit the plan at once, with no other write in the tree. Do not push the commit of the plan: it stays above `origin/main` for the lead, who pushes it after its own replay (the `pre-push` hook).
|
|
39
|
+
4. `git push origin <sha>:main`: the sha alone, never `HEAD`.
|
|
40
|
+
5. `cadence deliver --dry-run --sha <sha>`, then `cadence deliver --sha <sha>`.
|
|
41
|
+
6. Hand back the line (see The line).
|
|
42
|
+
|
|
43
|
+
Never `--force`, never `CADENCE_PUSH_UNGATED`, no second attempt after a refusal (`raf done`, `pre-push` hook exit 3, `cadence deliver`): stop and hand back `Question : <étape> refusée au sha <sha> : <message de la commande>`.
|
|
44
|
+
|
|
45
|
+
## The UX verdict
|
|
46
|
+
|
|
47
|
+
For a visible lot only, after the green gate and before `raf done`. Whether the lot is visible is read in `raf show <lot>`: a visible lot prints a line `visible : oui` (followed by `screen : …` when it has one) and a line `ux : <verdict>` or `ux : absent`; a lot that prints neither line is not visible, there is no UX verdict to form or to check: go on to `raf done`.
|
|
48
|
+
|
|
49
|
+
- `lead.ux: lead`: open the captures yourself (read the images of the captures folder, one by one) and read the report of the `ux-reviewer`; do not trust the words of the report. Record `raf ux <lot> "…"` with the last line of the report brought up to date, only when the reviewer has no reservation left and the captures show what the lot promised.
|
|
50
|
+
- At the slightest doubt (a reservation you cannot settle, a capture you cannot read, a layout or wording choice that is a matter of taste, a state the captures do not show): records no `raf ux`, closes nothing, hand back `Question : doute sur le verdict UX de <lot> : <réserve> (captures : <chemin>)`.
|
|
51
|
+
- Report or captures missing from the brief, for a visible lot with `lead.ux: lead` (known once `raf show` prints `visible : oui`): the same doubt, one message only: records no `raf ux`, closes nothing, hand back `Question : doute sur le verdict UX de <lot> : rapport ou captures absents du brief (captures : <chemin reçu, ou aucun>)`. Never « brief incomplet » for them.
|
|
52
|
+
- `lead.ux: human` (or no key): never form the verdict yourself. Read the `ux` line of `raf show <lot>`: `ux : <verdict>` says the human's verdict is recorded, go on; `ux : absent` says none is: close nothing and hand back `Question : verdict UX absent pour <lot> (lead.ux: human)`.
|
|
53
|
+
|
|
54
|
+
## The line
|
|
55
|
+
|
|
56
|
+
The last thing you hand back is one line, once the delivery is green. The base URL comes from the brief; never deduce or invent it.
|
|
57
|
+
|
|
58
|
+
- Delivery that changes a page or an API: `<lot> livré <sha court>, QA à lancer <url>` (for instance `L84 livré 3d6edf4, QA à lancer https://app.example`).
|
|
59
|
+
- Delivery that changes neither a page nor an API: `<lot> livré <sha court>, pas de QA à lancer`. The decision is the one of check 4 (`git diff --name-only origin/main..<sha>`, read after `git fetch origin main` and before the push: after `git push origin <sha>:main` the interval is empty): docs, the plan or tests only over the whole interval, no QA; as soon as one file of the interval touches a page or an API: the line carries the URL.
|
|
60
|
+
|
|
61
|
+
You never launch the `qa-reviewer`: the lead launches it after your line, with the URL it carries.
|
|
62
|
+
|
|
63
|
+
## Questions
|
|
64
|
+
|
|
65
|
+
A question starts with « Question : » and fits in a few lines: the step reached, the sha, the cause and the command to rerun. It says the state of the repository left by that step (for instance « raf done fait, commit du plan non fait »): you leave it as the step reached left it, and do not clean up. A step that fails or is refused (`git fetch origin main`, `raf done`, the commit of the plan, the push, `cadence deliver`) is handed back as `Question : <étape> refusée au sha <sha> : <message de la commande>`.
|
|
66
|
+
|
|
67
|
+
## Never
|
|
68
|
+
|
|
69
|
+
Never change code, never `raf review`, never `raf start`, never `raf note`; `raf ux` only in the case of the UX verdict. Never push the plan, never `HEAD`, never `--force`, never `CADENCE_PUSH_UNGATED`. Never `git checkout` or `git switch` to another commit or branch in this repository. Launch no agent. Do not stop to ask the human anything: a question goes in your reply to the lead.
|
package/agents/spec-writer.md
CHANGED
|
@@ -10,6 +10,7 @@ Follow the brief you are given. The method:
|
|
|
10
10
|
|
|
11
11
|
1. **Read the charter first.** The project's CLAUDE.md, its CSS and variables, the neighbouring components and pages, `docs/qa/expectations.md`, the routes and API contracts already present. The mockup must look like the product: reuse what exists, invent nothing when an equivalent exists.
|
|
12
12
|
2. **List the states before drawing**: empty, loading, error, and loaded when there is data. Each is an addressable section `#etat-<état>` of one static `maquette.html` (HTML + CSS, no business JavaScript). Each field of the sheet carries `data-testid="<field id>"`.
|
|
13
|
+
**Write the texts before drawing and render them in the mockup**, with the structure the reviewer will check: an error message says what, why, how to fix; an empty or loading state says what it is, why it is empty, how to start; an action label is a verb that names the result, never “OK” or “Validate” alone; the confirmation of a destructive action names the object and the consequence and its buttons are named by the action; one word per notion, in the sheet and in the mockup. Draw one primary element per state, one visible label per field, and take every colour, size and spacing from the product’s CSS variables (a product with no CSS variables has none to take: follow its existing values). A word or a message already in the product is kept as it is (reuse what exists).
|
|
13
14
|
3. **Render and look.** Capture each state at **1440 px** and **390 px** wide with the browser tool (Playwright), named `maquette-<état>-<largeur>.png`, with the absolute capture-directory path your brief gives in front of the name (a bare name lands in the repository), and look at every capture before keeping it: a mockup you did not look at is not checked. Copy the kept captures into the spec folder.
|
|
14
15
|
Before keeping a state, check its sample data: compute the weekday of every date shown (`date -d`), never write it from memory.
|
|
15
16
|
4. **Write the sheet** in the format of the template of the brief, and run `cadence spec check` on it until it passes. Every rule is `sourcee` (say where it comes from) or `hypothese` (say what would change if it were wrong, in `si_faux`). Never dress a guess as a source.
|
package/agents/ux-reviewer.md
CHANGED
|
@@ -24,7 +24,36 @@ On top of the usability and accessibility checks, check the **consistency mockup
|
|
|
24
24
|
- every state of `etats` has its section `#etat-<état>`; the `masque` of an empty-state rule really hides the fields it names;
|
|
25
25
|
- the units, formats and wording the sheet declares are those shown.
|
|
26
26
|
|
|
27
|
-
**
|
|
27
|
+
**Criteria added in mockup mode** — in this mode only (conformity mode and the review of an existing screen keep their text). Each one carries its named rule or the value to count; the checks of the method below (contrast, targets, keyboard, focus, alternatives, colour, reflow, data display) are already written there and are not repeated here.
|
|
28
|
+
|
|
29
|
+
**Family: visual hierarchy**
|
|
30
|
+
|
|
31
|
+
- one primary element per state: count the primary actions of each state, more than one is a finding (Nielsen 8);
|
|
32
|
+
|
|
33
|
+
**Family: consistency with the product tokens**
|
|
34
|
+
|
|
35
|
+
- colour, size and spacing values of `maquette.html` that do not come from the product’s CSS variables: report the count as one finding, not one per value (Nielsen 4); a product with no CSS variables gets no finding, write it in `nonVerifie`;
|
|
36
|
+
|
|
37
|
+
**Family: quality of the texts**
|
|
38
|
+
|
|
39
|
+
- an error message says what, why, how to fix (Nielsen 9);
|
|
40
|
+
- an empty or loading state says what it is, why it is empty, how to start (Nielsen 1);
|
|
41
|
+
- an action label is a verb that names the result, never “OK” or “Validate” alone (Nielsen 2 and 4);
|
|
42
|
+
- the confirmation of a destructive action names the object and the consequence, and its buttons are named by the action (Nielsen 5);
|
|
43
|
+
- one word per notion: count of distinct labels used for one notion across the states and the sheet (Nielsen 4);
|
|
44
|
+
|
|
45
|
+
**Family: complementary accessibility**
|
|
46
|
+
|
|
47
|
+
- focus order follows the reading order (WCAG 2.4.3);
|
|
48
|
+
- name, role, value and a visible label for every field (WCAG 4.1.2, 3.3.2, 1.3.1);
|
|
49
|
+
- an error is identified by text, not by colour or an icon alone (WCAG 3.3.1);
|
|
50
|
+
- text enlarged to 200 % loses no content or function (WCAG 1.4.4);
|
|
51
|
+
|
|
52
|
+
What a static mockup cannot show, such as what a real screen reader announces, goes in `nonVerifie`, not ticked, and is never replaced by a finding.
|
|
53
|
+
|
|
54
|
+
Severity keeps the scale below: an accessibility (WCAG) gap is blocking or major as before; a text, hierarchy or token defect is major only if it misleads or slows the task (an error message without the fix, two primary actions, an off-token value that changes the identity), otherwise minor.
|
|
55
|
+
|
|
56
|
+
**Sweep everything in the first review**, and render it as a checklist in the text of your report (one line per item, ticked or not): every state of the sheet at 1440 px and at 390 px; one line per family of the added criteria, per state at 1440 px and 390 px; every rule of the sheet; contrast (WCAG 2.2 AA, measured); the real envelope of the app (usable width, side bar, header — measured in the neighbouring pages, not assumed); gutters; empty or stretched panels; holes in the layout. What you cannot verify (a state you cannot capture, an ambiguous rule) goes in `nonVerifie`, not ticked. Report ALL the blocking and major findings in the first report: the mockup is reviewed up to `orchestrate.maquetteReviews` times (3 by default), but a later review that finds a major on a point nobody changed counts as a review defect in the wave journal. From the second review on, check the points of the previous one first, run the sweep again, and set `inchange: true` on each blocking or major finding about a point the spec writer did not change and the previous review had not raised (leave the field out otherwise).
|
|
28
57
|
|
|
29
58
|
Check the **sample data** too: recompute the weekday of every date shown (a command such as `date -d`), do not trust the one written. A wrong sample datum (weekday, date, figure that does not add up) is **minor**, never major: it changes no field, rule or state of the sheet, so it does not send the mockup back to the spec writer.
|
|
30
59
|
|
package/dist/cli.js
CHANGED
|
@@ -29,7 +29,7 @@ import { activeLock, REPO_LOCK } from './orchestrate/lock.js';
|
|
|
29
29
|
import { INTEGRATION_LOCK } from './orchestrate/integrate.js';
|
|
30
30
|
import { cadenceHome, liveWaves, openLotsIn, openWavesIn, waveLotsIn } from './orchestrate/registry.js';
|
|
31
31
|
import { blockingLots, LABEL, resumeCommand } from './orchestrate/table.js';
|
|
32
|
-
import { classifyAll, measure, ollamaClassifier, readCache, readJournals, readRuns, renderReport, sampleKeys, serializeCache } from './corrections.js';
|
|
32
|
+
import { bugCodeOf, bugCodePasses, classifyAll, classifySous, measure, ollamaClassifier, ollamaSousClassifier, readCache, readJournals, readRuns, readSousCache, renderReport, sampleKeys, serializeCache, serializeSousCache } from './corrections.js';
|
|
33
33
|
import { computePerf, perfHeader, perfWindows, readPlans, readProjectDeliveries, readProjectFailedEntries, renderPerf } from './perf.js';
|
|
34
34
|
import { findAcceptation, parseAcceptation, redAcceptations, resealAcceptation, resealForGreen, runAcceptation, setVert } from './orchestrate/accept.js';
|
|
35
35
|
import { refCommand } from './ref/command.js';
|
|
@@ -59,7 +59,7 @@ const HELP = `raf — plan « reste à faire » versionné dans le dépôt, reli
|
|
|
59
59
|
raf ux <id> "verdict" enregistre la revue d'ergonomie du lot (agent ux-reviewer)
|
|
60
60
|
raf review enable revue de code obligatoire avant « done » pour les lots qui ont des commits
|
|
61
61
|
raf review <id> "verdict" enregistre la revue de code du lot (agent code-reviewer)
|
|
62
|
-
raf show <id> [--notes] le lot : statut, dates, after, titre public, notes datées, commits comptés ; --notes : les notes seules
|
|
62
|
+
raf show <id> [--notes] le lot : statut, dates, after, titre public, visible et verdict ux (lot visible), notes datées, commits comptés ; --notes : les notes seules
|
|
63
63
|
raf commits <id> les commits du lot que compte la porte de revue de code, du plus ancien au plus récent
|
|
64
64
|
raf now ce qui est en cours, la suite, les derniers terminés
|
|
65
65
|
raf list [--status todo|doing|done|dropped]
|
|
@@ -75,7 +75,7 @@ const HELP = `raf — plan « reste à faire » versionné dans le dépôt, reli
|
|
|
75
75
|
mode worktree par lot (orchestrate.worktrees: true) (activé par défaut dans cadence, désactivé ailleurs tant que la clé est absente) ; ses options : --resume --wave <vague> --integrate projet:lot (rejoue un lot rendu à l'intégration) · --resume --wave <vague> --replay projet:lot (idem, et après une écriture de plan refusée) · --remove-worktree projet:lot [--abandon-commits] (retire le worktree et la branche conservés) ; la liste des worktrees conservés : orchestrate --status et lead tour
|
|
76
76
|
cadence lead tour [dossier] [--idle 3] [--depth n] [--json] le tableau du lead, sans modèle : une ligne par sous-dossier qui a un plan, ou la ligne du dossier seul s'il est lui-même un projet (en cours, dérive, notes de clôture, prochain lot prêt, dépôt), précédées de l'en-tête perf : une ligne par ⚠ de cadence perf, rien si tout est stable
|
|
77
77
|
cadence lead hold <étiquette> [--ttl minutes] | lead release <étiquette> déclare (60 min par défaut), puis rend, une session lancée à la main par le lead : elle est soustraite des créneaux libres de orchestrate --status et de lead tour ; au TTL échu, les deux la signalent en ⚠ jusqu'au release
|
|
78
|
-
cadence corrections [dossier] [--classify [--model m]] [--cache fichier] [--sample n] [--until date] [--since date] [--json] mesure des passes de correction des journaux dossier/.cadence/runs : lots à ≥1 et ≥2 corrections, par projet, visibles ou non, passes par cause (classées par Ollama avec --classify, hors quota)
|
|
78
|
+
cadence corrections [dossier] [--classify [--model m]] [--cache fichier] [--cache-sous-causes fichier] [--sample n] [--until date] [--since date] [--json] mesure des passes de correction des journaux dossier/.cadence/runs : lots à ≥1 et ≥2 corrections, par projet, visibles ou non, passes par cause (classées par Ollama avec --classify, hors quota)
|
|
79
79
|
cadence perf [dossier] [--since date] [--json] tableau de bord du workflow, sans modèle : deux fenêtres (7 derniers jours contre les 7 précédents) lues dans dossier/.cadence/runs et les plans, chaque vitesse à côté de sa qualité, ⚠ au-delà de 25 % de dégradation ; tokens par lot selon l'estimate, part de la revue, délais création → done → livraison, durée de mur par étape, coût en euros par lot livré (grille cadence.yaml perf.prices) ; --since est un jour qui tronque les fenêtres
|
|
80
80
|
cadence ref add <document.pdf> [--sections 3-5,9] [--slug nom] enregistre un document de référence hors du dépôt (references.dir de cadence.yaml, sinon $XDG_DATA_HOME/cadence/references) : pages rendues à 200 dpi, texte positionné, images incluses, index des figures et des passages qui les citent ; exige poppler, ImageMagick et tesseract (outil absent nommé avec sa commande, code 2)
|
|
81
81
|
cadence ref analyse <document.pdf> [--sections 3-5,9] [--slug nom] [--model sonnet|opus|haiku] [--model-b sonnet|opus|haiku] [--seuil 30] [--double] la chaîne : add, puis UNE lecture (une session de l'agent reference-analyst, lectures/a.json, le programme relit le fichier : ref check, document et genre) ; --double : deux lectures indépendantes (A sur --model, B sur --model-b, lectures/a.json et b.json), ref diff, le troisième regard (agent reference-verifier : confirme ou écarte ce qu'une seule lecture porte, d'après une découpe) et l'inventaire réconcilié (inventaire.json, usage.tokens = les trois sessions) — option dont la mesure n'a pas montré de gain (docs/mesures/ref-double-lecture-2026-10-25.md) ; --model-b et --seuil n'ont de sens qu'avec --double ; plafond déclaré : 20 pages par lecture ; code 1 lecture refusée ou (--double) trop d'éléments non appariés (--seuil, 30 %), 2 refus (plafond, modèle, session en échec), 3 quota
|
|
@@ -200,6 +200,7 @@ function dispatch(argv, io) {
|
|
|
200
200
|
classify: { type: 'boolean' },
|
|
201
201
|
model: { type: 'string' },
|
|
202
202
|
cache: { type: 'string' },
|
|
203
|
+
'cache-sous-causes': { type: 'string' },
|
|
203
204
|
sample: { type: 'string' },
|
|
204
205
|
sections: { type: 'string' },
|
|
205
206
|
slug: { type: 'string' },
|
|
@@ -632,8 +633,13 @@ function dispatch(argv, io) {
|
|
|
632
633
|
io.out(`after : ${lot.after.join(', ')}`);
|
|
633
634
|
if (lot.public)
|
|
634
635
|
io.out(`public : ${lot.public}`);
|
|
636
|
+
// L439 : le livreur lit ici si le lot est visible et s'il porte un verdict UX ; deux lignes stables, absentes d'un lot non visible.
|
|
637
|
+
if (lot.visible)
|
|
638
|
+
io.out('visible : oui');
|
|
635
639
|
if (lot.screen)
|
|
636
640
|
io.out(`screen : ${lot.screen}`);
|
|
641
|
+
if (lot.visible)
|
|
642
|
+
io.out(lot.ux ? `ux : ${lot.ux.verdict}` : 'ux : absent');
|
|
637
643
|
if (lot.spec)
|
|
638
644
|
io.out('spec : true');
|
|
639
645
|
if (lot.uses)
|
|
@@ -1497,7 +1503,7 @@ function spec(rest, io) {
|
|
|
1497
1503
|
/** `cadence corrections` (L165) : la mesure de référence des passes de correction d'une vague, lue dans <dossier>/.cadence/runs. */
|
|
1498
1504
|
async function corrections(rest, values, io) {
|
|
1499
1505
|
if (rest.length > 1)
|
|
1500
|
-
throw new RafError('usage : cadence corrections [dossier] [--classify [--model m]] [--cache fichier] [--sample n] [--until date] [--since date] [--json]');
|
|
1506
|
+
throw new RafError('usage : cadence corrections [dossier] [--classify [--model m]] [--cache fichier] [--cache-sous-causes fichier] [--sample n] [--until date] [--since date] [--json]');
|
|
1501
1507
|
const parent = resolve(io.cwd, rest[0] ?? '.');
|
|
1502
1508
|
const runsDir = join(parent, '.cadence', 'runs');
|
|
1503
1509
|
if (!existsSync(runsDir))
|
|
@@ -1512,14 +1518,33 @@ async function corrections(rest, values, io) {
|
|
|
1512
1518
|
const model = values.model ?? 'mistral-small3.2:24b';
|
|
1513
1519
|
const cacheFile = resolve(io.cwd, values.cache ?? join(parent, '.cadence', 'corrections.json'));
|
|
1514
1520
|
const data = readRuns(runsDir, values.until, values.since);
|
|
1521
|
+
const sousFile = resolve(io.cwd, values['cache-sous-causes'] ?? join(parent, '.cadence', 'corrections-sous-causes.json'));
|
|
1515
1522
|
const cache = readCache(cacheFile);
|
|
1523
|
+
// R5 (L447) : le cache des sous-causes illisible sort avec le code 1 de la fiche (corrections.json illisible sort avec 2, par `failure`).
|
|
1524
|
+
let sousCache;
|
|
1525
|
+
try {
|
|
1526
|
+
sousCache = readSousCache(sousFile);
|
|
1527
|
+
}
|
|
1528
|
+
catch (e) {
|
|
1529
|
+
if (!(e instanceof RafError))
|
|
1530
|
+
throw e;
|
|
1531
|
+
io.err(`raf: ${e.message}`);
|
|
1532
|
+
return 1;
|
|
1533
|
+
}
|
|
1516
1534
|
if (values.classify) {
|
|
1517
|
-
const
|
|
1518
|
-
await classifyAll(data.passes, cache,
|
|
1535
|
+
const url = io.env.OLLAMA_HOST ?? 'http://localhost:11434';
|
|
1536
|
+
await classifyAll(data.passes, cache, ollamaClassifier({ url, model }), model, (done, total) => {
|
|
1519
1537
|
if (done % 25 === 0 || done === total)
|
|
1520
1538
|
io.err(`classement : ${done}/${total}`);
|
|
1521
1539
|
}, { every: 10, save: () => writeFileSync(cacheFile, serializeCache(cache)) });
|
|
1522
1540
|
writeFileSync(cacheFile, serializeCache(cache));
|
|
1541
|
+
const saveSous = () => writeFileSync(sousFile, serializeSousCache(sousCache));
|
|
1542
|
+
const added = await classifySous(data.passes, cache, sousCache, ollamaSousClassifier({ url, model }), model, (done, total) => {
|
|
1543
|
+
if (done % 25 === 0 || done === total)
|
|
1544
|
+
io.err(`sous-causes : ${done}/${total}`);
|
|
1545
|
+
}, { every: 10, save: saveSous });
|
|
1546
|
+
if (added > 0)
|
|
1547
|
+
saveSous();
|
|
1523
1548
|
}
|
|
1524
1549
|
if (sample !== undefined) {
|
|
1525
1550
|
const byKey = new Map(data.passes.map((p) => [p.key, p]));
|
|
@@ -1536,7 +1561,8 @@ async function corrections(rest, values, io) {
|
|
|
1536
1561
|
return 0;
|
|
1537
1562
|
}
|
|
1538
1563
|
const m = measure(data, cache);
|
|
1539
|
-
|
|
1564
|
+
const bugCode = bugCodeOf(bugCodePasses(data.passes, cache), sousCache);
|
|
1565
|
+
io.out(values.json ? JSON.stringify(bugCode ? { ...m, bugCode } : m, null, 2) : renderReport(m, bugCode));
|
|
1540
1566
|
return 0;
|
|
1541
1567
|
}
|
|
1542
1568
|
/**
|
|
@@ -1553,17 +1579,31 @@ function readPerf(parent, today, since, warn) {
|
|
|
1553
1579
|
const cache = readCache(join(parent, '.cadence', 'corrections.json'));
|
|
1554
1580
|
const journals = readJournals(runsDir);
|
|
1555
1581
|
const warnings = [];
|
|
1582
|
+
// Les sous-causes (L447) n'arrêtent pas perf : un cache illisible avertit et la ligne est omise.
|
|
1583
|
+
const sousFile = join(parent, '.cadence', 'corrections-sous-causes.json');
|
|
1584
|
+
let sousCache;
|
|
1585
|
+
let sousWarning;
|
|
1586
|
+
try {
|
|
1587
|
+
sousCache = readSousCache(sousFile);
|
|
1588
|
+
}
|
|
1589
|
+
catch (e) {
|
|
1590
|
+
if (!(e instanceof RafError))
|
|
1591
|
+
throw e;
|
|
1592
|
+
sousWarning = `perf : ${sousFile} illisible, sous-causes n/d`;
|
|
1593
|
+
}
|
|
1556
1594
|
const plans = readPlans(parent, (l) => warnings.push(l));
|
|
1557
1595
|
if (plans.size === 0 && warnings.length === 0)
|
|
1558
1596
|
warnings.push(`perf : aucun plan sous ${parent}, indicateurs du plan n/d`);
|
|
1559
1597
|
const deliveries = readProjectDeliveries(parent, [...plans.keys()]);
|
|
1560
1598
|
const failures = readProjectFailedEntries(parent, [...plans.keys()]);
|
|
1561
1599
|
const failedDeliveries = new Map([...failures].map(([name, entries]) => [name, entries.map((e) => e.day)]));
|
|
1562
|
-
const result = computePerf({ journals, plans, cache, deliveries, failedDeliveries, failures, prices, windows: perfWindows(today, since), today, since });
|
|
1600
|
+
const result = computePerf({ journals, plans, cache, ...(sousCache ? { sousCache } : {}), deliveries, failedDeliveries, failures, prices, windows: perfWindows(today, since), today, since });
|
|
1563
1601
|
if (!prices)
|
|
1564
1602
|
warnings.push('perf : aucune grille de prix (cadence.yaml perf.prices), coût en euros n/d');
|
|
1565
1603
|
for (const [model, lots] of Object.entries(result.cost.withoutPrice))
|
|
1566
1604
|
warnings.push(`perf : modèle sans prix dans perf.prices : ${model}, coût n/d pour ${lots} lot(s)`);
|
|
1605
|
+
if (sousWarning)
|
|
1606
|
+
warnings.push(sousWarning);
|
|
1567
1607
|
warnings.forEach(warn);
|
|
1568
1608
|
return result;
|
|
1569
1609
|
}
|
package/dist/corrections.js
CHANGED
|
@@ -274,11 +274,172 @@ export function readCache(file) {
|
|
|
274
274
|
export function serializeCache(cache) {
|
|
275
275
|
return JSON.stringify(Object.fromEntries([...cache].sort(([a], [b]) => a.localeCompare(b))), null, 2) + '\n';
|
|
276
276
|
}
|
|
277
|
+
/** Les sous-causes d'une passe « bug de code » (L447, R1), dans l'ordre où le rapport les liste. */
|
|
278
|
+
export const SOUS_CAUSES = ['cas-limite', 'test-casse', 'contrat-mal-lu', 'appelant-oublie', 'affirmation-fausse', 'test-vide', 'autre'];
|
|
279
|
+
const SOUS_LABELS = {
|
|
280
|
+
'cas-limite': 'cas limite',
|
|
281
|
+
'test-casse': 'test cassé',
|
|
282
|
+
'contrat-mal-lu': 'contrat mal lu',
|
|
283
|
+
'appelant-oublie': 'appelant oublié',
|
|
284
|
+
'affirmation-fausse': 'affirmation fausse',
|
|
285
|
+
'test-vide': 'test vide',
|
|
286
|
+
autre: 'autre',
|
|
287
|
+
};
|
|
288
|
+
/** Ce que chaque valeur veut dire, une ligne chacune : le prompt du modèle de sous-cause (R10). */
|
|
289
|
+
const SOUS_DEFINITIONS = {
|
|
290
|
+
'cas-limite': 'un cas limite (entrée vide, absente, en double, d\'un autre type, refusée, forme réelle des données) que le code ne tient pas ou qu\'aucun test ne couvre',
|
|
291
|
+
'test-casse': 'un test existant que le changement casse',
|
|
292
|
+
'contrat-mal-lu': 'un contrat, une fiche ou un énoncé de lot mal lu : le code fait autre chose que ce qui est écrit',
|
|
293
|
+
'appelant-oublie': 'un appelant, un consommateur ou un autre site à changer a été oublié',
|
|
294
|
+
'affirmation-fausse': 'une documentation, un commentaire, un message ou un rapport affirme une chose fausse (chiffre, « jamais », « toujours », ancien comportement)',
|
|
295
|
+
'test-vide': 'un test qui ne peut pas échouer, ou un comportement ajouté sans test',
|
|
296
|
+
autre: 'tout bug de code qui n\'entre dans aucune des valeurs précédentes',
|
|
297
|
+
};
|
|
298
|
+
/** Les maillons de la chaîne où un mécanisme peut être posé (R2). */
|
|
299
|
+
export const MAILLONS = ['brief implement', 'auto-vérification avant rendu', 'porte locale', 'fiche'];
|
|
300
|
+
/** Le maillon dont relève chaque sous-cause (R2, hypothèse) ; `autre` n'en désigne aucun. */
|
|
301
|
+
export const MAILLON_DE = {
|
|
302
|
+
'cas-limite': 'brief implement',
|
|
303
|
+
'test-vide': 'brief implement',
|
|
304
|
+
'affirmation-fausse': 'auto-vérification avant rendu',
|
|
305
|
+
'appelant-oublie': 'auto-vérification avant rendu',
|
|
306
|
+
'test-casse': 'porte locale',
|
|
307
|
+
'contrat-mal-lu': 'fiche',
|
|
308
|
+
autre: null,
|
|
309
|
+
};
|
|
310
|
+
/** Seuils de désignation du maillon dominant (R2, hypothèse) : passes sous-classées au moins, et part minimale du maillon en tête, en %. */
|
|
311
|
+
export const MAILLON_MIN_PASSES = 10;
|
|
312
|
+
export const MAILLON_MIN_PART = 40;
|
|
313
|
+
/** Extrait une sous-cause connue d'une réponse (JSON `{sous}`) ; null sinon. */
|
|
314
|
+
export function parseSousCause(text) {
|
|
315
|
+
try {
|
|
316
|
+
const s = JSON.parse(text)?.sous;
|
|
317
|
+
return typeof s === 'string' && SOUS_CAUSES.includes(s) ? s : null;
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const SOUS_SYSTEM = `Tu sous-classes une passe de correction déjà classée « bug de code » : une session a écrit du code, une revue a formulé des constats, une session de correction les a traités. Réponds par UNE sous-cause, celle du constat le plus grave. Les valeurs :
|
|
324
|
+
${SOUS_CAUSES.map((s) => `- ${s} : ${SOUS_DEFINITIONS[s]} ;`).join('\n')}`;
|
|
325
|
+
/** Sous-classeur sur un modèle local servi par Ollama (comme `ollamaClassifier` : /api/chat, température 0, sortie contrainte par un schéma à sept valeurs). */
|
|
326
|
+
export function ollamaSousClassifier(opts) {
|
|
327
|
+
const doFetch = opts.fetch ?? fetch;
|
|
328
|
+
return async (pass) => {
|
|
329
|
+
const findings = pass.findings.map((f, i) => `${i + 1}. ${f.slice(0, 1500)}`).join('\n');
|
|
330
|
+
const user = `Lot ${pass.project} ${pass.lot}.\nConstats de la revue :\n${findings}\n\nCommits de la correction :\n${pass.commits.join('\n') || '(aucun)'}`;
|
|
331
|
+
try {
|
|
332
|
+
const res = await doFetch(`${opts.url.replace(/\/$/, '')}/api/chat`, {
|
|
333
|
+
method: 'POST',
|
|
334
|
+
headers: { 'content-type': 'application/json' },
|
|
335
|
+
body: JSON.stringify({
|
|
336
|
+
model: opts.model,
|
|
337
|
+
stream: false,
|
|
338
|
+
options: { temperature: 0 },
|
|
339
|
+
format: { type: 'object', properties: { sous: { type: 'string', enum: [...SOUS_CAUSES] }, raison: { type: 'string' } }, required: ['sous', 'raison'] },
|
|
340
|
+
messages: [{ role: 'system', content: SOUS_SYSTEM }, { role: 'user', content: user }],
|
|
341
|
+
}),
|
|
342
|
+
});
|
|
343
|
+
if (!res.ok)
|
|
344
|
+
return null;
|
|
345
|
+
const body = (await res.json());
|
|
346
|
+
return parseSousCause(body.message?.content ?? '');
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
/** La cause retenue d'une passe à constats : la relecture à la main, sinon le modèle ; absente sans classement. */
|
|
354
|
+
const retainedCause = (cache, p) => {
|
|
355
|
+
const c = cache.get(p.key);
|
|
356
|
+
return c ? (c.manual ?? c.cause) : undefined;
|
|
357
|
+
};
|
|
358
|
+
/** Les passes à sous-classer : à constats, de cause retenue bug-code (R1). */
|
|
359
|
+
export const bugCodePasses = (passes, cache) => passes.filter((p) => p.findings.length > 0 && retainedCause(cache, p) === 'bug-code');
|
|
360
|
+
/** Sous-classe les passes bug-code absentes du cache des sous-causes ; un échec n'y entre pas (R3). Rend le nombre d'entrées ajoutées. */
|
|
361
|
+
export async function classifySous(passes, cache, sousCache, classify, model, progress, checkpoint) {
|
|
362
|
+
const todo = bugCodePasses(passes, cache).filter((p) => !sousCache.has(p.key));
|
|
363
|
+
let done = 0;
|
|
364
|
+
let added = 0;
|
|
365
|
+
for (const p of todo) {
|
|
366
|
+
const sous = await classify(p);
|
|
367
|
+
if (sous) {
|
|
368
|
+
sousCache.set(p.key, { sous, by: `ollama:${model}` });
|
|
369
|
+
added++;
|
|
370
|
+
}
|
|
371
|
+
done++;
|
|
372
|
+
progress?.(done, todo.length);
|
|
373
|
+
if (checkpoint && added > 0 && done % checkpoint.every === 0)
|
|
374
|
+
checkpoint.save();
|
|
375
|
+
}
|
|
376
|
+
return added;
|
|
377
|
+
}
|
|
378
|
+
export function readSousCache(file) {
|
|
379
|
+
let raw = null;
|
|
380
|
+
if (existsSync(file)) {
|
|
381
|
+
raw = readJson(file);
|
|
382
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
|
|
383
|
+
throw new RafError(`${file} : JSON illisible (un objet clé → classement est attendu), corrigez ou supprimez le fichier`);
|
|
384
|
+
}
|
|
385
|
+
const known = (s) => typeof s === 'string' && SOUS_CAUSES.includes(s);
|
|
386
|
+
const entries = Object.entries((raw ?? {}));
|
|
387
|
+
for (const [key, c] of entries) {
|
|
388
|
+
if (!known(c?.sous))
|
|
389
|
+
throw new RafError(`${file} : sous-cause inconnue pour ${key} : ${String(c?.sous)}`);
|
|
390
|
+
if (c.manual !== undefined && !known(c.manual))
|
|
391
|
+
throw new RafError(`${file} : sous-cause inconnue pour ${key} : ${String(c.manual)}`);
|
|
392
|
+
}
|
|
393
|
+
return new Map(entries);
|
|
394
|
+
}
|
|
395
|
+
export function serializeSousCache(cache) {
|
|
396
|
+
return JSON.stringify(Object.fromEntries([...cache].sort(([a], [b]) => a.localeCompare(b))), null, 2) + '\n';
|
|
397
|
+
}
|
|
398
|
+
/** Désigne le maillon dominant : le plus lourd en passes, si au moins MAILLON_MIN_PASSES sont sous-classées et qu'il pèse MAILLON_MIN_PART % d'elles ; ni égalité en tête ni `autre` ne désignent (R2). */
|
|
399
|
+
export function dominantMaillon(parSousCause) {
|
|
400
|
+
const total = SOUS_CAUSES.reduce((n, s) => n + parSousCause[s], 0);
|
|
401
|
+
if (total < MAILLON_MIN_PASSES)
|
|
402
|
+
return { nom: null, raison: `${total} passes sous-classées, il en faut au moins ${MAILLON_MIN_PASSES}` };
|
|
403
|
+
const poids = MAILLONS.map((m) => ({ m, passes: SOUS_CAUSES.filter((s) => MAILLON_DE[s] === m).reduce((n, s) => n + parSousCause[s], 0) })).sort((a, b) => b.passes - a.passes);
|
|
404
|
+
const [first, second] = poids;
|
|
405
|
+
if (first.passes > 0 && first.passes === second.passes)
|
|
406
|
+
return { nom: null, raison: `égalité en tête entre ${first.m} et ${second.m} (${first.passes} passes chacun)` };
|
|
407
|
+
if (first.passes * 100 < MAILLON_MIN_PART * total)
|
|
408
|
+
return { nom: null, raison: `aucun maillon à ${MAILLON_MIN_PART} % des passes sous-classées (le premier : ${first.passes} sur ${total})` };
|
|
409
|
+
return { nom: first.m, raison: `${first.passes} passes sur ${total} sous-classées (${Math.round((100 * first.passes) / total)} %)` };
|
|
410
|
+
}
|
|
411
|
+
/** Compte les sous-causes des passes bug-code données (R4 : manual prime ; R12 : une entrée du cache hors de ces passes n'est pas lue). Rend null sans passe bug-code (R9). */
|
|
412
|
+
export function bugCodeOf(passes, sousCache) {
|
|
413
|
+
if (passes.length === 0)
|
|
414
|
+
return null;
|
|
415
|
+
const parSousCause = Object.fromEntries(SOUS_CAUSES.map((s) => [s, 0]));
|
|
416
|
+
let classees = 0;
|
|
417
|
+
for (const p of passes) {
|
|
418
|
+
const c = sousCache.get(p.key);
|
|
419
|
+
if (!c)
|
|
420
|
+
continue;
|
|
421
|
+
parSousCause[c.manual ?? c.sous]++;
|
|
422
|
+
classees++;
|
|
423
|
+
}
|
|
424
|
+
return { classees, sansSousCause: passes.length - classees, parSousCause, maillon: dominantMaillon(parSousCause) };
|
|
425
|
+
}
|
|
426
|
+
/** Les sous-causes dans l'ordre décroissant des passes (à égalité, l'ordre de SOUS_CAUSES), celles sans passe écartées. */
|
|
427
|
+
export function sousCausesRanked(b) {
|
|
428
|
+
return SOUS_CAUSES.map((sous, i) => ({ sous, label: SOUS_LABELS[sous], passes: b.parSousCause[sous], i })).filter((x) => x.passes > 0).sort((a, b2) => b2.passes - a.passes || a.i - b2.i);
|
|
429
|
+
}
|
|
430
|
+
/** La section « Bug de code par sous-cause » de `cadence corrections` (A1) : sous-cause, passes, part des passes sous-classées, maillon. */
|
|
431
|
+
export function renderSousCauses(b) {
|
|
432
|
+
const out = ['### Bug de code par sous-cause', '', `${b.classees} passes sous-classées sur ${b.classees + b.sansSousCause}${b.sansSousCause > 0 ? ` · sans sous-cause : ${b.sansSousCause} (à classer : cadence corrections --classify)` : ''}.`, ''];
|
|
433
|
+
if (b.classees > 0)
|
|
434
|
+
out.push('| Sous-cause | Passes | Part | Maillon |', '|---|---|---|---|', ...sousCausesRanked(b).map((r) => `| ${r.label} | ${r.passes} | ${pct(r.passes, b.classees)} | ${MAILLON_DE[r.sous] ?? 'aucun'} |`), '');
|
|
435
|
+
out.push(`Maillon dominant : ${b.maillon.nom ?? `aucun (${b.maillon.raison})`}${b.maillon.nom ? ` — ${b.maillon.raison}` : ''}.`, '');
|
|
436
|
+
return out;
|
|
437
|
+
}
|
|
277
438
|
const pct = (a, b) => (b === 0 ? '—' : `${Math.round((100 * a) / b)} %`);
|
|
278
439
|
/** Les causes qu'une meilleure spécification évite (critère de réussite de l'étape spec). */
|
|
279
440
|
const SPEC_CAUSES = ['spec-floue', 'regle-inventee', 'test-manquant'];
|
|
280
441
|
const row = (name, t) => `| ${name} | ${t.lots} | ${t.atLeast1} (${pct(t.atLeast1, t.lots)}) | ${t.atLeast2} (${pct(t.atLeast2, t.lots)}) | ${t.passes} |`;
|
|
281
|
-
export function renderReport(m) {
|
|
442
|
+
export function renderReport(m, bugCode) {
|
|
282
443
|
const all = [...CAUSES, 'sans-constat', 'non-classe'];
|
|
283
444
|
const used = all.filter((c) => (c !== 'non-classe' && c !== 'sans-constat') || m.byCause[c] > 0);
|
|
284
445
|
const projects = Object.keys(m.byProject).sort();
|
|
@@ -299,5 +460,7 @@ export function renderReport(m) {
|
|
|
299
460
|
out.push('');
|
|
300
461
|
if (m.agreement.reviewed > 0)
|
|
301
462
|
out.push(`Échantillon relu à la main : ${m.agreement.reviewed} passes, le modèle local est d'accord avec la relecture sur ${m.agreement.agree} (${pct(m.agreement.agree, m.agreement.reviewed)}) ; la cause relue prime dans les tableaux.`, '');
|
|
463
|
+
if (bugCode)
|
|
464
|
+
out.push(...renderSousCauses(bugCode));
|
|
302
465
|
return out.join('\n');
|
|
303
466
|
}
|
package/dist/perf.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join, relative, sep } from 'node:path';
|
|
2
2
|
import { planLoader } from './config.js';
|
|
3
|
-
import { CAUSES, isReview as isReviewKind } from './corrections.js';
|
|
3
|
+
import { bugCodeOf, CAUSES, isReview as isReviewKind, sousCausesRanked } from './corrections.js';
|
|
4
4
|
import { addDays, diffDays, isDay, toDay } from './dates.js';
|
|
5
5
|
import { gitCommonDir } from './git.js';
|
|
6
6
|
import { findProjects } from './lead.js';
|
|
@@ -129,8 +129,9 @@ function indicator(current, previous, lots, windows, dir, twin, nullNote = null,
|
|
|
129
129
|
const byEstimateKey = (a, b) => parseFloat(a) - parseFloat(b) || a.length - b.length;
|
|
130
130
|
/** Les deux valeurs d'un indicateur, calculées fenêtre par fenêtre. */
|
|
131
131
|
const both = (windows, f) => ({ current: f(windows.current), previous: f(windows.previous) });
|
|
132
|
-
function causeWindow(passes, w, cache) {
|
|
132
|
+
function causeWindow(passes, w, cache, sousCache) {
|
|
133
133
|
const counts = new Map();
|
|
134
|
+
const bugCodePasses = [];
|
|
134
135
|
let unclassified = 0;
|
|
135
136
|
let withoutFindings = 0;
|
|
136
137
|
const byReview = {};
|
|
@@ -150,14 +151,17 @@ function causeWindow(passes, w, cache) {
|
|
|
150
151
|
}
|
|
151
152
|
const cause = c.manual ?? c.cause;
|
|
152
153
|
counts.set(cause, (counts.get(cause) ?? 0) + 1);
|
|
154
|
+
if (cause === 'bug-code')
|
|
155
|
+
bugCodePasses.push(pass);
|
|
153
156
|
}
|
|
157
|
+
const bugCode = sousCache ? bugCodeOf(bugCodePasses, sousCache) : null;
|
|
154
158
|
const classified = [...counts.values()].reduce((a, b) => a + b, 0);
|
|
155
159
|
const top = CAUSES.filter((c) => counts.has(c))
|
|
156
160
|
.map((cause) => ({ cause, passes: counts.get(cause), share: counts.get(cause) / classified }))
|
|
157
161
|
.sort((a, b) => b.passes - a.passes)
|
|
158
162
|
.slice(0, 3);
|
|
159
163
|
const withFindings = classified + unclassified;
|
|
160
|
-
return { top, classified, unclassified, withoutFindings, unclassifiedShare: withFindings === 0 ? null : unclassified / withFindings, byReview };
|
|
164
|
+
return { top, classified, unclassified, withoutFindings, unclassifiedShare: withFindings === 0 ? null : unclassified / withFindings, byReview, ...(bugCode ? { bugCode } : {}) };
|
|
161
165
|
}
|
|
162
166
|
/** La sorte d'étape sous laquelle la hausse se compte : `review` et `review-small` ne font qu'une (la taille du lot choisit l'une ou l'autre, `orchestrate.review.threshold` : le passage de l'une à l'autre n'est pas une hausse). */
|
|
163
167
|
const carrierKind = (kind) => (kind === 'review-small' ? 'review' : kind);
|
|
@@ -222,7 +226,7 @@ export function computePerf(input) {
|
|
|
222
226
|
};
|
|
223
227
|
const lotCounts = both(windows, (w) => lotsIn(w).length);
|
|
224
228
|
const passDays = withPasses.flatMap((d) => d.j.passes.map((pass) => ({ pass, day: pass.fixEnded && !Number.isNaN(Date.parse(pass.fixEnded)) ? toDay(new Date(pass.fixEnded)) : d.day })));
|
|
225
|
-
const causes = { current: causeWindow(passDays, windows.current, cache), previous: causeWindow(passDays, windows.previous, cache) };
|
|
229
|
+
const causes = { current: causeWindow(passDays, windows.current, cache, input.sousCache), previous: causeWindow(passDays, windows.previous, cache) };
|
|
226
230
|
// Interventions du lead (R10, R15)
|
|
227
231
|
const lastOf = new Map();
|
|
228
232
|
for (const d of all) {
|
|
@@ -630,6 +634,14 @@ function redBreakdownLines(b) {
|
|
|
630
634
|
` livraisons rouges par projet : ${byProject.map(([name, n, cl]) => `${name} ${n} (${cl.map(([k, c]) => `${k} ${c}`).join(', ')})`).join(' · ')}`,
|
|
631
635
|
];
|
|
632
636
|
}
|
|
637
|
+
/** La ligne des sous-causes bug-code sous « causes dominantes » (L447, R6/R7) : sans sous-cause classée, elle dit comment les classer. */
|
|
638
|
+
function bugCodeLine(b) {
|
|
639
|
+
if (b.classees === 0)
|
|
640
|
+
return ` bug de code (${b.sansSousCause} passes) : sous-causes n/d · à classer : cadence corrections --classify`;
|
|
641
|
+
const ranked = sousCausesRanked(b).map((r, i) => `${i + 1}. ${r.label} ${r.passes}`).join(' · ');
|
|
642
|
+
const maillon = b.maillon.nom ?? `aucun (${b.maillon.raison})`;
|
|
643
|
+
return ` bug de code (${b.classees} passes sous-classées sur ${b.classees + b.sansSousCause}) : ${ranked}${b.sansSousCause > 0 ? ` · sans sous-cause : ${b.sansSousCause}` : ''} · maillon dominant : ${maillon}`;
|
|
644
|
+
}
|
|
633
645
|
/** Le tableau en texte : chaque indicateur de vitesse sur la même ligne que son jumeau de qualité (R8). */
|
|
634
646
|
export function renderPerf(r) {
|
|
635
647
|
const lines = indicatorLines(r);
|
|
@@ -669,6 +681,8 @@ export function renderPerf(r) {
|
|
|
669
681
|
const c = r.corrections.causes.current;
|
|
670
682
|
const top = c.top.map((t, i) => `${i + 1}. ${LABELS[t.cause]} ${t.passes} (${pct(t.share)})`).join(' · ');
|
|
671
683
|
out.push(` causes dominantes (${c.classified} passes classées) : ${top || 'aucune'} · non classé : ${c.unclassifiedShare === null ? 'n/d' : pct(c.unclassifiedShare)} · sans revue (tests rouges) : ${c.withoutFindings}${c.unclassified > 0 ? ' · à classer : cadence corrections --classify' : ''}`);
|
|
684
|
+
if (c.bugCode)
|
|
685
|
+
out.push(bugCodeLine(c.bugCode));
|
|
672
686
|
const origins = Object.entries(c.byReview).sort(([a, x], [b, y]) => y - x || a.localeCompare(b));
|
|
673
687
|
if (origins.length > 0)
|
|
674
688
|
out.push(` origine des passes à constats : ${origins.map(([k, n]) => `${k} ${n}`).join(' · ')}`);
|
package/docs/corrections.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# corrections
|
|
4
4
|
|
|
5
|
-
`cadence corrections [folder] [--classify [--model m]] [--cache file] [--sample n] [--until date] [--since date] [--json]` measures how often a wave
|
|
5
|
+
`cadence corrections [folder] [--classify [--model m]] [--cache file] [--cache-sous-causes file] [--sample n] [--until date] [--since date] [--json]` measures how often a wave
|
|
6
6
|
has to correct itself. From the folder a wave was launched from (default: the current one) it reads the journals
|
|
7
7
|
`<folder>/.cadence/runs/<wave>/<project>--<lot>.json` of **every project** and prints:
|
|
8
8
|
|
|
@@ -23,3 +23,13 @@ beside a pass in the cache file (one of the seven causes, else the command refus
|
|
|
23
23
|
on the reviewed sample. `--until date` (ISO, e.g. `2026-10-10T18:31Z`) reads only the waves created up to then (`created` in the wave's `wave.json`) and, in them, only the steps ended by then (`ended`), so that neither later waves nor a wave still running at that time change a published baseline; a wave or a step with no readable date is left out. `--since date` (ISO, same format) keeps only the lots of the waves **created after** that date (`created` in the wave's `wave.json`), **with all their passes**: a wave renders every brief from the copy of the tool frozen when it is created (`<wave>/tool/`), so the creation date of the wave, not the start of a lot, tells which brief the lot was given. A `--continue` wave created before a change of brief therefore does not give it to its lots, even those that start after the change (a difference of two unbounded runs is wrong as soon as a wave straddles the date); the exception is a wave resumed (`--resume`) without a snapshot (older than L61) or, with `--budget project:lot=+N`, from a snapshot older than L223: it runs with the current `dist/` and templates, so it does receive the new brief. A wave with no readable `created` is left out, by `--since` as by `--until`. `--since` is strict and `--until` inclusive, so a wave created exactly at the date is on the `--until` side only and the two sets never overlap; `--since` and `--until` can be combined (a window of creation dates). Together `--until d` and `--since d` split the waves, and they add up to the unbounded total only when `d` is set while no wave is running (between two sessions, as `npm run service` requires; `cadence orchestrate --status` shows none, **and** no wave created before `d` is still resumable: `--status` lists only the live waves, an interrupted or suspended one is not shown and `--resume` would replay passes after `d`; list them with `jq -r 'select(.status != "done") | "\(.id) \(.status) \(.created)"' <folder>/.cadence/runs/*/wave.json`, and finish each before setting `d` or never resume it): a wave created before `d` that ends a pass after `d` leaves that pass in neither (`--until` does not read it, so that a published baseline does not move), and a gap between the sum and the unbounded total means `d` fell inside a wave. `--json` prints the measure as data. The command reads the journals and writes nothing but the cache.
|
|
24
24
|
The first baseline is in [docs/mesures/corrections-2026-10-10.md](mesures/corrections-2026-10-10.md); `test/corrections-mesure.test.ts` replays its command on the journals and checks the document's results word for word (skipped when the journals are not there). The measure of the screen-sheet step against the visible lots of this baseline (L176: 8 lots with a sheet, verdict per row of the success table, token cost beside the passes avoided) is in [docs/mesures/etape-spec-2026-10-12.md](mesures/etape-spec-2026-10-12.md); the target of its third row is ≤ 8 %, half the 16 % reference. The measure of the back-end spec step against the non-visible lots (L190: 10 lots with a back-end sheet, passes by cause, token cost beside the passes avoided) is in [docs/mesures/etape-spec-back-2026-10-16.md](mesures/etape-spec-back-2026-10-16.md).
|
|
25
25
|
The reading of the « bug de code » passes of the 23–29-10 window, the block added to the `implement` brief against them (L428) and the protocol to measure its effect on two `cadence perf` windows are in [docs/mesures/bug-code-2026-10-29.md](mesures/bug-code-2026-10-29.md).
|
|
26
|
+
|
|
27
|
+
## Sub-causes of the « bug de code » passes (L447)
|
|
28
|
+
|
|
29
|
+
The cause `bug-code` alone is 69 % of the classified passes (74 of 107, 24→30-10) and does not say *which* bug. A pass whose retained cause (`manual`, else the model's) is `bug-code` and that has review findings is therefore also given **one sub-cause** from a closed list of seven: `cas-limite` (edge case not tested or not held), `test-casse` (an existing test broken by the change), `contrat-mal-lu` (contract, sheet or lot statement misread), `appelant-oublie` (a caller or another site to change was forgotten), `affirmation-fausse` (a document, comment or report that is false), `test-vide` (a test that cannot fail, or a behaviour with no test) and `autre`. A pass of another cause, or with no finding, is never sent to the model for this.
|
|
30
|
+
|
|
31
|
+
- `--classify` first classifies the causes as before, then asks the same local model for the sub-cause of each `bug-code` pass that has none (temperature 0, output constrained to the seven values, findings cut to 1500 characters). The answers are cached in `<folder>/.cadence/corrections-sous-causes.json` (or `--cache-sous-causes file`): an object `pass key → { sous, by, manual? }`. A failed answer (Ollama down, non-2xx status, value outside the list) leaves the pass out of the cache, counted « sans sous-cause » and taken again by the next `--classify`; `corrections.json` is not touched. A `manual` field (one of the seven values) beats the model in every table. An entry whose key is not a `bug-code` pass of the window (re-classified, cause changed by hand, key absent from the window) is ignored: neither counted, erased nor refused.
|
|
32
|
+
- An unreadable file (not a JSON object) or an unknown sub-cause makes the command refuse with exit code 1, naming the file (and the key), without printing or rewriting anything; an absent file reads as empty. Without `--classify` the file is read, never written.
|
|
33
|
+
- The report gains the section « Bug de code par sous-cause » (sub-cause, passes, share of the sub-classified passes, link) and a line « Maillon dominant ». `--json` gains the key `bugCode` — `{ classees, sansSousCause, parSousCause, maillon: { nom, raison } }`. With no `bug-code` pass in the window nothing is added: the text report and the JSON keys are those of before.
|
|
34
|
+
|
|
35
|
+
**The link.** Each sub-cause belongs to one link of the chain: `cas-limite` and `test-vide` to the `implement` brief, `affirmation-fausse` and `appelant-oublie` to the self-check before handing back, `test-casse` to the local gate, `contrat-mal-lu` to the sheet, `autre` to none. The **dominant link** is the one whose sub-causes weigh the most passes; it is named only when at least 10 passes are sub-classified and its passes are at least 40 % of them. With fewer, with a link under 40 %, or with two links tied at the top, the line says « aucun » and why. The two thresholds and the table are constants of `src/corrections.ts` (hypotheses: the link is recomputed at each read, nothing stores it). `cadence perf` prints the same count under « causes dominantes » for the current 7-day window — `bug de code (N passes sous-classées sur M) : 1. cas limite … · maillon dominant : …` — reading the cache without writing it: an absent file gives `sous-causes n/d · à classer : cadence corrections --classify`, an unreadable one a warning on stderr (`perf : <file> illisible, sous-causes n/d`) and no line, exit code 0 in both cases (`corrections.causes.current.bugCode` in `--json`). Over 7 days the 10-pass threshold is often not reached: « aucun » is then the usual answer, not an error.
|
package/docs/lead.md
CHANGED
|
@@ -64,7 +64,7 @@ picture, not its source.
|
|
|
64
64
|
build run by the lead, `raf check` clean. A `visible` lot waits for the human to look at the
|
|
65
65
|
UX reviewer's captures: the lead records `raf ux` on the human's verdict, never on its own — unless the parent folder's `cadence.yaml` says `lead.ux: lead`: then the lead forms the verdict from the reviewer's report and its own look at the captures, and asks the human only at the slightest doubt. Then
|
|
66
66
|
`raf done`.
|
|
67
|
-
6. **Delivery.**
|
|
67
|
+
6. **Delivery.** The lead can hand a ready lot to the `livreur` agent (L439, [`agents/livreur.md`](../agents/livreur.md)): one subagent per lot, one at a time per repository, never during the integration of a lot of the wave (`cadence lead tour` shows it). The brief gives the project folder, the lot, the sha to deliver (« livrable jusqu'à »), the value of `lead.ux`, the base URL of the delivered app and, for a visible lot with `lead.ux: lead`, the `ux-reviewer`'s report and the folder of its captures. The `livreur` checks the brief and the sha first (the sha is an ancestor of HEAD, no commit of a lot that is not done below it, not already in `origin/main`; « already delivered » means in `origin/main` AND in the delivery log `deliveries.log`), then runs `cadence verify --gate --sha`, the UX verdict (`raf ux` only with `lead.ux: lead`, on the captures it opens itself, nothing at the slightest doubt; with `lead.ux: human` it only reads that a verdict is recorded: `raf show <lot>` prints `visible : oui` and `ux : <verdict>` or `ux : absent` for a visible lot), `raf done` and the plan commit that `raf done` prints, `git push origin <sha>:main`, `cadence deliver --dry-run --sha` then `cadence deliver --sha`. It pushes neither the plan commit (the lead pushes it after its own replay) nor `HEAD`, uses neither `--force` nor `CADENCE_PUSH_UNGATED`, and retries nothing after a refusal. It hands back one line, `L84 livré 3d6edf4, QA à lancer <url>` (or `…, pas de QA à lancer` when `git diff --name-only origin/main..<sha>`, read right after `git fetch origin main` and before the gate and the push — after the push the interval is empty — lists docs, the plan or tests only; when it touches a page or an API, a brief without the base URL is refused there, before any action), or one `Question : …` naming the step reached, the sha, the cause and the command to rerun. The lead launches the `qa-reviewer` itself with that URL; `raf review` stays with the lead. Otherwise the lead delivers itself: push (for a « livrable jusqu'à <sha> », that sha alone, never HEAD), then
|
|
68
68
|
`cadence deliver`. Where the project declares a gate, the `pre-push` hook refuses a sha with no green verdict of
|
|
69
69
|
`cadence verify --gate` (L426): the lot's sha has the verdict of the re-check, the plan commit above it (the head of `main`
|
|
70
70
|
after `raf done` and its commit) changes only the plan and takes that verdict (L432); a commit that also touches another file (a release commit, say) has none and gets a full replay with `cadence verify --gate --sha HEAD` before the push;
|
package/docs/orchestrate.md
CHANGED
|
@@ -335,6 +335,7 @@ After a valid spec, a lot with a sheet gets a UX review of its mockup, `maquette
|
|
|
335
335
|
that it checks the **consistency mockup ↔ sheet**: every field of the sheet in the mockup with its `data-testid`, every rule message visible, word for word, in the state its `etat` names, every state with its
|
|
336
336
|
`#etat-<état>` section, the `masque` of an empty-state rule really hiding the fields it names. `effort.maquette` (default `high`) sets its effort level. Its verdict is **not** `raf ux`: the conformity pass after the code keeps that role.
|
|
337
337
|
- **A full sweep from the first review** (L230). The brief and the agent ask for a checklist in the text of the report, one line per item: every state of the sheet at 1440 and 390 px, every rule of the sheet, contrast, the real envelope of the app (usable width, side bar, header), gutters, empty or stretched panels, holes in the layout. What cannot be verified goes in `nonVerifie`; the program does not check the list. The first report must hold ALL the blocking and major findings, none kept for a later review.
|
|
338
|
+
- **Design criteria in the mockup review** (L448). In mockup mode only, the agent also checks four families of criteria absent from its method, each tied to a named rule or a count: **visual hierarchy** (one primary element per state, Nielsen 8), **consistency with the product tokens** (count of colour, size and spacing values that do not come from the product's CSS variables, Nielsen 4), **quality of the texts** (error message = what, why, how to fix, Nielsen 9; empty or loading state = what it is, why it is empty, how to start, Nielsen 1; action label = a verb that names the result, Nielsen 2 and 4; destructive confirmation naming the object and the consequence, Nielsen 5; one word per notion, count of distinct labels) and **complementary accessibility** (focus order 2.4.3, name, role, value and field label 4.1.2 / 3.3.2 / 1.3.1, error identified by text 3.3.1, text at 200 % 1.4.4). The checklist of the report gets one line per family and per state; what a static mockup cannot show (a real screen reader) goes in `nonVerifie`. Severity keeps its scale: an accessibility gap stays blocking or major, a text, hierarchy or token defect is major only if it misleads or slows the task, otherwise minor. The spec writer applies the drawing-time ones (the texts, one primary element per state, one label per field, the product's variables) before the review; the measures (contrast, zoom, focus order) stay with the reviewer. No new review, no new session: the budget of `maquetteReviews` and the report schema are unchanged. Measure: `cadence perf`, « sessions ux par lot visible » (1.6 on the window of 30-10, target 1.2).
|
|
338
339
|
- **The loop, `orchestrate.maquetteReviews` reviews at most** (integer ≥ 1, default 3; any other value is a configuration error). A blocking or major finding sends the mockup back to the spec writer: a fresh `spec` session whose brief carries the findings, then the mockup is reviewed again (the brief of review n says « review n of N », and « the last review » for the last). If the last review is
|
|
339
340
|
still not conforming, the lot is handed back with what remains (`maquette non conforme après 3 revues : …`, `après 1 revue` when the key is 1) and nothing is implemented. A lot with a screen and `orchestrate.maquette` not `false` has a budget of `maquetteReviews` `spec` sessions, shared with the sheet review of a mixed lot (which keeps its two reviews); any other lot keeps two. If the program's check or the reviews already used the budget, a non-conforming review hands the lot back at once (`maquette non conforme et la spec a déjà joué 3 sessions`); an answer to a question of the lead opens a new budget.
|
|
340
341
|
- **Review defects in the journal.** From review 2, the agent marks `inchange: true` on each blocking or major finding about a point the spec writer did not change and the previous review had not raised (optional boolean of a finding; anything but `true` counts as false). For each one the program writes `<project>:<lot> · défaut de revue : maquette, revue <n>, majeur sur un point inchangé absent de la revue <n-1> : <text>` in the wave journal. Nothing else changes: verdict, loop and state of the lot are the same. A conforming review goes on to `accept` (or `implement`). On a resume, a **sealed** `acceptation.json` (its `empreintes` set by the program), still red or already green (`vert: true`), proves the review was done and is not replayed; an unsealed one proves nothing, a `vert: true` one included (only the cycle sets `vert`, after the seal). **A green and sealed acceptation never goes back to `accept` (L233)**, nor to the sheet and mockup reviews, even when its seal no longer holds (a test resealed by `cadence spec reseal`, a sheet amended since): the lot goes to `implement` or the review, the step would rewrite a green oracle and demand red tests. The `accept` brief and the `acceptance-writer` agent say the same: given a `vert: true` file whose `empreintes` hold one entry per listed test file, never rewrite it nor touch `resceaux`, `empreintes` or `vert`, write nothing and hand the lot back; given a `vert: true` file without `empreintes`, or whose `empreintes` lack one of the listed files (never sealed, it proves nothing), write the oracle as usual with `vert: false` (the program itself never starts an `accept` session on a green and sealed acceptation, even for a lot waiting on an answer with `next` = `accept`, so nothing relies on the prompt alone). A lot waiting on an answer is compared with the file as it was before its first `accept` session: if that session rewrote the green file and committed it before asking its question (`vert: false`, `resceaux` erased), the lot is handed back (`verrou des tests d'acceptation : … acceptation verte réécrite …`) instead of adopting the rewrite as its red commit; only a `cadence spec reseal` by the lead (journal `resceaux` longer, commit outside any session) is accepted. A `maquette` step with no sheet warns (`revue de la maquette ignorée : aucune fiche …`) and goes to `implement`.
|
|
@@ -399,6 +400,7 @@ Once the sheet exists, the steps after it all read it (L174, `src/orchestrate/av
|
|
|
399
400
|
- **A self-check list before handing back (L222).** Both `implement.md` and `fix.md` carry the same fixed list (plain text, no marker, no new variable), taken from the recurring findings of the reviews of 2026-10-15: the usage documentation (README and architecture documentation) updated in the same commits as the behaviour; one test named `Rn — …` per rule of the sheet (one per business rule of the lot when there is no sheet); no dead code and no test that cannot fail (an assertion always true, or a test that does not exercise its rule); for a modified screen, the capture due of each state at 1440 and 390 px with a file name (where it goes is the Playwright paragraph's rule) (a lot with no modified screen owes none and says so); no false comment, docstring or message. The session goes through it point by point on its own commits before it reports, and a point it cannot hold is written under `choix` or `nonVerifie` of its report: the report schema has no new field. In `fix` the list covers only what the session changed, and "fix them, and nothing else" stays. The other briefs (`fix-minors`, `spec`, `spec-back`, `accept`, `review-small`…) are unchanged. The before/after measure — correction passes per lot, replayed with `cadence corrections` (`--until <bound>` for the "before", `--since <bound>` for the "after", the bound being the time of the `npm run service` that puts the list in service) — is in [docs/mesures/autocontrole-avant-revue-2026-10-15.md](mesures/autocontrole-avant-revue-2026-10-15.md).
|
|
400
401
|
- **Auto-verification once the tests are green (L236).** After the self-check list, `implement.md` and `fix.md` carry a second fixed block: when the full suite and the build pass, the session re-reads the complete diff of its lot (of its fix commits, in `fix`) against the grid of the `code-reviewer` agent — code bug, architecture drift, `docs/architecture.md` (or the project's architecture documentation) up to date, edge cases, one test per changed behaviour — fixes what it finds at once (test first, a `fix(<lot>): …` commit, suite and build replayed), then starts `resume` with an « auto-vérification » paragraph: what it re-read, found and fixed, could not hold. No new report field. The cost bound is written in the brief: one reading of the diff, not a second review (no subagent, no files the diff does not touch). Measure before the block (`cadence corrections --classify`, 10 to 17-10): 356 passes, 53 % code bug, 19 % architecture drift, 14 % unclear spec; `cadence perf` of 17-10: 37 % right the first time. Target: above 50 % on the next window with tokens per lot stable (`cadence perf`).
|
|
401
402
|
- **Limits and rules before the walkthrough (L428).** `implement.md` (not `fix.md`) carries a third fixed block between the self-check list and the real walkthrough, aimed at the first cause of correction passes: a code bug the review finds in the diff (100 of the 143 classified passes of the 23–29-10 window, `cadence perf`). The session re-reads its diff against the fiche and the lot and, **per rule** (`Rn`), names the test that fails if the code breaks it and the line that holds it; **per function, parser, pattern, threshold or command it adds or changes**, lists the inputs at the edge (empty, absent, twice, wrong order, wrong type, refused, the real shape of the data, the other callers) and gives each a test that fails without its code, except a case the fiche does not cover: with a fiche, the session does not code a rule the fiche does not state, so that case goes under `"nonVerifie"` or `"choix"`, neither coded nor tested (a lot without a fiche has no such exception: every listed case is due a test, and one left untested goes under `"nonVerifie"`); **per figure, « all », « never » or « always »** written in README, docs, comments or tests, checks it against the code or by running it. A listed case left untested goes under `"nonVerifie"`. The reading of the 23–29-10 passes that led to it, and the way to measure its effect on two windows, are in [mesures/bug-code-2026-10-29.md](mesures/bug-code-2026-10-29.md). Test: `test/limites-et-regles-l428.test.ts`.
|
|
403
|
+
- **The edge-cases point, sharpened (L447).** `cadence corrections` now sub-classifies the `bug-code` passes ([corrections](corrections.md)); on the 24→30-10 window the dominant link is the `implement` brief (99 of 136 passes; 96 of them, 71 %, are edge cases). The « edge cases » bullet of the L428 block therefore carries, marked `[L447]`, two more demands: the list of edge inputs is written before the first line of code, and before reporting each listed input is played once on the real path (a real file or call, not only the fixture). It is the same block, not a second one; no other brief changes. Table, protocol and the two windows to measure (L428 and L447 together, from 2026-10-30): [docs/mesures/bug-code-2026-10-30.md](mesures/bug-code-2026-10-30.md).
|
|
402
404
|
- **Real walkthrough before the auto-verification (L344).** Reading a diff did not show three major review findings of 26-10 (a command impossible in its own documented path, a « champs exclus » read against a decision of 22-10, a failure banner out of view). `implement.md` therefore asks, once the tests are green, to play what was delivered as a user would: a command in a throwaway repository (`git init` in a fresh temporary folder), following the documentation the lot wrote, command by command; a screen in each of its states, failure included and shown where the action takes place, with a capture of each. What breaks is fixed in the same session (test first) and played again; the report says it in a paragraph of `"resume"` that starts with « parcours réel » (no new field; a lot with nothing to play says so). `fix.md` does not carry it: a correction pass fixes the findings it is given.
|
|
403
405
|
- **Decisions to respect (L344).** The program (not the pre-check session, which many lots skip) reads the notes of the lot and of its parent lot (`parent:`) and keeps those that name who decided (Sylvain, the human, the lead, the user) and say that they decided, validated, asked or refused (`src/orchestrate/decisions.ts`). The notes the program writes itself are never taken for one, whatever they quote: a hypothesis of a fiche (« Hypothèses (fiche …) — », a choice nobody validated), a minor of a review (« Revue de … — mineur : »), a replaced title (« titre remplacé : », the stale one), « remis en attente » and « abandonné : ». Each is given with its date and its text as written (cut at 1500 characters) under « Decisions to respect » in the `implement` and `fix` briefs (and in the `--dry-run` briefs), with the rule: an interpretation that departs from one of them is a question under `"questions"`, never a choice under `"choix"`. Nothing is written when no note qualifies. The effect is measured by the share right the first time of `cadence perf` over two windows.
|
|
404
406
|
- **The code review (`review`, `review-small`) checks rule → test → green** (`règle → test → vert`). For each `Rn` of the sheet the reviewer checks that its test exists (named after the rule), that it is green, and that the behaviour is in the diff; when an acceptance file is named, that each listed test is unchanged since the red commit (`git diff <red commit> -- <file>` empty; the brief gives the red commit). A rule without a test, an acceptance test modified since the red commit, a rule coded that is not in the sheet, and a test that is green without exercising its rule are **major** findings. The reviewer no longer rebuilds the cases from the code.
|
package/docs/perf.md
CHANGED
|
@@ -14,7 +14,7 @@ one (the 7 days before). Every speed indicator is printed on the same line as it
|
|
|
14
14
|
|---|---|---|---|
|
|
15
15
|
| Throughput | lots ready, lots handed back per day, lots delivered (plan `done` with `finished` in the window), share of handed-back lots within their token budget | escaped defects | is the flow dropping? look for the bottleneck before adding lots; are the budgets right? |
|
|
16
16
|
| Corrections | median of `fix` passes per lot, share of lots with 2 passes or more | share of lots right the first time (no `fix` pass) | is the first write good enough? harden the sheet or the brief |
|
|
17
|
-
| Corrections | dominant causes (top 3 of the classified passes, the unclassified share apart) | — | which link to fix |
|
|
17
|
+
| Corrections | dominant causes (top 3 of the classified passes, the unclassified share apart); under it, the sub-causes of the `bug-code` passes and the dominant link (L447, [corrections](corrections.md)) | — | which link to fix |
|
|
18
18
|
| Lead interventions | lots handed back by a wave then finished outside it, sessions that see the application per visible lot (`ux` steps, plus the `review-small` of a small visible lot), share of visible lots reviewed by a dedicated `ux` step | escaped defects (sessions per lot only) | does the wave stop too early? does the ergonomics review cost more than one round? |
|
|
19
19
|
|
|
20
20
|
A line starts with `⚠` and ends with the decision it informs when its value is **worse than the previous window by more than 25 %**
|
package/docs/raf.md
CHANGED
|
@@ -68,7 +68,7 @@ raf gantt # .cadence/gantt.html (next to the plan)
|
|
|
68
68
|
| `raf note <id> "text"` | dated note — keep decisions next to the work |
|
|
69
69
|
| `raf title <id> "new title"` | replace the title of a lot of any status (`done` included) and add a dated note « titre remplacé : « old title » »; `raf now`, `raf show`, `raf list`, `session start` and the proposals read the new one. Refused on an empty or identical title, an unknown lot or a sub-task |
|
|
70
70
|
| `raf archive [--before date] [--dry-run]` | move the `done` and `dropped` lots (those finished strictly before `--before`, when given; a lot with no `finished:` date stays then) out of the FILE `.cadence/raf.yaml` into `.cadence/archive/<year>.yaml`, the year of their end (L396). **The archive is a storage detail, invisible to every reader**: the loaded plan presents all the lots, active and archived, **in the order they had before the archiving** — each archived lot takes its place by number among the lots of the plan file (just before the first active lot with a greater number), the order of the plan file itself never moves — so `raf show`, `raf list`, `raf commits`, `raf check`, `raf now`, `raf gantt`, `cadence news`, `cadence perf`, `session start`, `lead tour`, `cadence ref coverage`, `cadence deliver`, the post-commit hook and `cadence orchestrate` print exactly what they printed before the archiving — nothing has to « also look in the archive », a lot id is never reused, and a commit or a News entry citing an archived lot is not « inconnu ». What changes is the file: the plan the sessions open stays short (the cadence plan weighed 559 KB, ≈ 150 k tokens) and the briefs of the implementation, the sheets, the code review and the pre-check tell the sessions never to open `.cadence/archive/`. **Writing**: every lot goes back to the file it came from — `raf note`, `raf review`, `raf ux`, `raf title`… on an archived lot rewrite its year file, the plan file is untouched; a lot reopened (`raf todo <id>`, or `status: doing` set by hand in a year file) comes back to the end of the plan file at the next write, so the archive only ever holds `done` and `dropped` lots. **Lossless, whatever fails**: the year files are written before the plan file (should the plan then fail to write, a moved lot is at worst duplicated — which the next load refuses by name — never lost), and any exception during the write (`EACCES` on a read-only file, a full disk, a `plan.onWrite` command that fails) puts the plan, the year files and the declared files back as they were — a year file born of that write disappears — and the error says so (`écriture du plan refusée (EACCES, …) — plan et archive inchangés`). **Guards**: a closed lot with an open gap (what `raf check` signals — code review, UX review, docs.sync, News entry…) stays in the plan with the gap as its printed reason (`L1 reste dans le plan : écart ouvert — L1 est visible et terminé sans entrée Nouveautés …`); a lot named in `after:` by another, or counted by a `gate:`, is archived like any other — the readers see the archive, so the dependency and the count read the same. A same id found in two files (plan and archive, or two years) is a named error that stops every command (`identifiant en double : L1 est dans .cadence/archive/2025.yaml et dans .cadence/raf.yaml`) until one copy is removed by hand — never a silent choice of one copy. `--dry-run` lists without writing; `--before` takes `YYYY-MM-DD`; refused on a read-only plan (its tool keeps its own history) and while the plan is still at the legacy `docs/plan/raf.yaml` (the archive would land under `docs/`, published, and `raf migrate` would not move it: `raf migrate` first, the refusal says so). For `cadence orchestrate`, the year files are plan files like `.cadence/raf.yaml`: a session that commits one has « touché le plan ». Like every plan write, it is not committed: the command to commit the plan and the archive files together, at explicit paths, is printed (`plan.commit` template, action `N lot(s) archivé(s)`); replaying moves nothing twice. The archive files are kept with the plan: touching them is not working on a lot. **The published plan**: a generator run by `plan.onWrite` (below) that reads only the file `.cadence/raf.yaml` publishes a plan without the archived lots; `raf archive` says so on stderr when `plan.onWrite` is declared, the generator must also read `.cadence/archive/*.yaml` (key `lots`, same lot format as the plan) |
|
|
71
|
-
| `raf show <id> [--notes]` | one lot: status, dates, `goal : …` right after the title when the lot has a `goal:` (native key or `plan.fields.goal`, L272: the objective that replaces the title in the `orchestrate` briefs), `after`, public title, `
|
|
71
|
+
| `raf show <id> [--notes]` | one lot: status, dates, `goal : …` right after the title when the lot has a `goal:` (native key or `plan.fields.goal`, L272: the objective that replaces the title in the `orchestrate` briefs), `after`, public title, then for a visible lot two stable lines — `visible : oui` (followed by `screen : …` when the lot has one) and `ux : <verdict>` or `ux : absent` (the `raf ux` verdict, L439: what the `livreur` agent reads before closing a visible lot; a lot that is not visible prints neither line), `spec`, `uses : banc, passerelle` when the lot has `uses:`, dated notes in order, then the counted commits (as `raf commits`, neighbouring repositories included); `--notes` prints the notes alone |
|
|
72
72
|
| `raf commits <id>` | the commits counted for a lot (the set the code review gate uses), one `<sha> <subject>` per line, oldest first; a lot that declares `repos:` ([neighbouring repositories](orchestrate.md#a-lot-whose-work-is-in-a-neighbouring-repository)) gets, after them, one `dépôt <path> :` section per neighbour with the commits there that cite the lot and fall within the lot's period (`started` to `finished`) and, when the lot declares a `cite:` for that neighbour, contain that string (`aucun commit du lot`, or `introuvable` for a path that is not there) |
|
|
73
73
|
| `raf now` | what to do next |
|
|
74
74
|
| `raf list [--status s]` | flat list |
|
package/docs/skills.md
CHANGED
|
@@ -10,7 +10,7 @@ As a plugin:
|
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
gives `/cadence:session-start`, `/cadence:session-close`, `/cadence:deliver`,
|
|
13
|
-
`/cadence:lead`, `/cadence:orchestrate
|
|
13
|
+
`/cadence:lead`, `/cadence:orchestrate`, the `ux-reviewer`, `code-reviewer` and `qa-reviewer` agents and the `livreur` delivery agent. Or copy them into the
|
|
14
14
|
repository with `cadence skills install` (to `.claude/skills/cadence-*` and
|
|
15
15
|
`.claude/agents/cadence-*.md`; `--dir` for another `.claude` folder,
|
|
16
16
|
`--force` to overwrite local edits).
|
|
@@ -82,6 +82,7 @@ repository with `cadence skills install` (to `.claude/skills/cadence-*` and
|
|
|
82
82
|
other did not, each with the rendered page and a crop of its zone, and answers `confirme` or `ecarte` with a reason, measuring with
|
|
83
83
|
`cadence ref measure` rather than by eye and never guessing (what it cannot tell is `ecarte`). Its tools are `Read` and `Bash`: it writes nothing and never sees the readings
|
|
84
84
|
([ref](ref.md#cadence-ref-analyse-documentpdf---sections-3-59---slug-name---model-sonnetopushaiku---model-b-sonnetopushaiku---seuil-30---double)).
|
|
85
|
+
- **livreur** (agent, L439): the lead's delivery agent. Given one ready lot, its sha and the base URL of the app, it checks the sha (ancestor of HEAD, no lot that is not done below it, not already in `origin/main`), then runs `cadence verify --gate --sha`, the UX verdict according to `lead.ux`, `raf done` with the plan commit it prints, `git push origin <sha>:main` and `cadence deliver`, and hands back one line (`L84 livré 3d6edf4, QA à lancer <url>`) or one `Question : …`. Its tools are `Read`, `Grep`, `Glob` and `Bash`: no `Agent`, no `Edit`, no `Write`; it never launches the `qa-reviewer`, `raf review` or a force push ([lead](lead.md)).
|
|
85
86
|
- **qa-reviewer** (agent): any web app (read-only like `ux-reviewer`: same tool list, no `Edit`, no `Write`); given a repository and a base URL (and
|
|
86
87
|
optionally a lot id, to walk only the pages it touched — those that call the
|
|
87
88
|
changed endpoints, those of the screens it changed, the pages that consume the services the lot changed,
|
package/docs/whats-new.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
## In two lines
|
|
6
6
|
|
|
7
|
+
- **1.27.0**: `cadence corrections --classify` names the sub-cause of each code-bug pass and the dominant link of the chain, `cadence perf` shows it; the mockup review checks hierarchy, tokens, texts and extra accessibility.
|
|
8
|
+
- **1.26.0**: a new `livreur` agent delivers a ready lot for the lead (gate, UX verdict, `raf done`, push, deliver) and hands back one line; `raf show` prints `visible` and `ux` for a visible lot.
|
|
7
9
|
- **1.25.1**: publishes 1.25.0 (tagged, never published); tests now pass in a fresh clone and `cadence release` replays them in one; the panel of lots shows each project's last delivery.
|
|
8
10
|
- **1.25.0**: the panel of lots shows pending decisions, each lot's cycle and its budget and time overrun; `orchestrate --status` is compact; reviewers answer in 5 lines; `--resume --integrate` waits for a live wave.
|
|
9
11
|
- **1.24.0**: a push whose test gate is not green is refused; a wave counts the sessions the lead declared; a ready lot waits for its wave's integration; the `implement` brief gains « Limits and rules »; `plan.onWrite` errors read clearly.
|
|
@@ -52,6 +54,10 @@ Each is a paragraph in [docs/whats-new.md](whats-new.md) and a full section in [
|
|
|
52
54
|
|
|
53
55
|
The long form of « In two lines » above (the README no longer carries a « What's new » section since L326: it links here). The full history, with the date of each version, is in [CHANGELOG.md](../CHANGELOG.md).
|
|
54
56
|
|
|
57
|
+
**1.27.0**: the code-bug bottleneck is named, and the mockup review looks at more. `cadence corrections` sub-classifies the « bug de code » passes (L447, `docs/corrections.md`): one of seven sub-causes (edge case, broken test, misread contract, forgotten caller, false statement, empty test, other) per pass, asked of the local model with `--classify` and cached in `.cadence/corrections-sous-causes.json` (`--cache-sous-causes`); the report gains the section « Bug de code par sous-cause » and the dominant link of the chain (at least 10 passes sub-classified and 40 % of them), `--json` the key `bugCode`, and `cadence perf` prints the same count under « causes dominantes », read-only; nothing changes for a window with no `bug-code` pass. On the 24→30-10 window the dominant link is the `implement` brief (`docs/mesures/bug-code-2026-10-30.md`): 96 of the 136 `bug-code` passes are edge cases, so the « edge cases » bullet of the L428 block of `implement.md` now asks for the list before the first line of code and for each listed input to be played once on the real path before reporting (marked `[L447]`); the next measure counts L428 and L447 together, from 2026-10-30. The mockup review (L448, `docs/orchestrate.md`) checks four more families of design criteria, each tied to a named rule or a count: visual hierarchy (one primary element per state), consistency with the product tokens (values that do not come from the product's CSS variables), quality of the texts (error, empty and loading states, action labels, destructive confirmations, one word per notion) and complementary accessibility (focus order, name, role and label, error as text, text at 200 %). It applies in mockup mode only, with no new review or session and the same severity scale and report; the spec writer and its brief apply the drawing-time criteria before the review. Measure: « sessions ux par lot visible » of `cadence perf` (1.6 on the window of 30-10, target 1.2).
|
|
58
|
+
|
|
59
|
+
**1.26.0**: the lead hands the delivery of a ready lot to a subagent. The `livreur` agent (L439, installed by `cadence skills install` like the others) takes one ready lot and one sha given by the lead, and runs `cadence verify --gate --sha`, the UX verdict according to `lead.ux`, `raf done` and the plan commit it prints, `git push origin <sha>:main` and `cadence deliver`, then hands back one line (`L84 livré 3d6edf4, QA à lancer <url>`) or one `Question : …`. It first checks that the sha is an ancestor of HEAD, has no lot that is not done below it and is not already in `origin/main` and in the delivery log; it launches no agent, never the `qa-reviewer` (the lead does, after the line), and the lead launches one `livreur` at a time (`docs/lead.md`). A relaunch on a lot already done weighs both proofs (`origin/main` and `deliveries.log`), so « pushed, never delivered » is no longer masked by « déjà clos ». It reads `git diff --name-only origin/main..<sha>` right after `git fetch origin main`, before the gate and the push, to decide whether a QA pass is due and refuses a brief without the base URL before any action. `raf show <lot>` prints two new stable lines for a visible lot, `visible : oui` (then `screen : …` when the lot has one) and `ux : <verdict>` or `ux : absent` (`docs/raf.md`): that is how the `livreur` knows whether the lot is visible and whether the human's UX verdict is recorded (`lead.ux: human`).
|
|
60
|
+
|
|
55
61
|
**1.25.1**: publishes 1.25.0 and closes the hole that kept it from reaching npm. The `v1.25.0` tag was pushed but its `publish` workflow failed in `test/hud-cycle`, `test/hud-deliveries` and `test/hud-depense-l414`: vitest read `plugins/cadence-hud/tsconfig.json`, which extends `.claude-plugin/types/tsconfig.json`, a file Claude Code writes when it loads the mod and git does not track, so no local tree could see it fail (L445); the tag is not rewritten, everything 1.25.0 brings (see below) ships in this version. The test suite now passes in a fresh `git clone` (the mod's own typecheck still extends that file). `cadence release` prints, right after the gate replay, a step that replays the version commit in a fresh `git clone --no-hardlinks` (`npm ci`, `npm run typecheck`, `npm test`, the clone removed afterwards), and `.github/workflows/tests.yml` also runs on every push to `main` (L446, `docs/releasing.md`). The panel of lots (`/hud lots`, `docs/cadence-hud.md`) shows, under the lots in progress, the last delivery of each project: its short sha, its age in days, green or red (read from the journal `cadence deliver` writes), and the lots closed since the last green delivery that were not delivered; `cadence lead tour --json` gains `closed`, the closed lots of a project with their closing day (L415).
|
|
56
62
|
|
|
57
63
|
**1.25.0**: the lead reads less and waits less. The panel of lots (`/hud lots`, `docs/cadence-hud.md`) opens with « ce qui attend une décision » — the questions of the sessions with their `--answer` command, the lots handed back with their cause, the UX verdicts to record and the failed deliveries, each with a command that runs as pasted (L412) — and draws under each lot a wave carries the cycle of the lot as a strip, from `fiche` to `prêt` then `revue posée → clos → livré`, the current stage in bold with its duration and passes (L413). For each lot in progress it shows the tokens consumed over the lot's budget and the elapsed time next to the usual time of a lot of that size, with a `⚠` on an overrun (budget exceeded, or more than 1.5 times the usual time); `cadence perf --json` gains `lotMinutes`, the median wall time per size class (L414). `cadence orchestrate --status` prints a compact table, one line per lot, and `--status [<wave>] --detail <project:lot>` prints the detail of one lot, which cost the lead 5 to 10 k tokens per read (L441); `code-reviewer` and `qa-reviewer` answer in 5 lines at most and write the full report to the file named by a `Report file: <path>` line of the brief (L440). A wave no longer leaves slots idle while it has nothing left to play (L433), `cadence orchestrate --resume --integrate|--replay` waits, for 3 hours at most, for a live wave that holds the repository instead of exiting 2 (L443), and `cadence verify --gate --sha <sha>` reads the gate the wave played when only plan commits sit above the checked commit, instead of replaying it for about 5 minutes (L444); the `pre-push` hook takes the same shortcut for a commit that changes only the plan (L432), and `cadence verify --gate` plays in a throwaway copy while a wave is live on the repository (L431). `raf done` now cites the files `plan.onWrite` really wrote, so the proposed `git add` no longer leaves one out (L436).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylad/cadence",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.0",
|
|
4
4
|
"description": "A small, repo-native working method: a versioned plan linked to your commits, a changelog with screenshots, session rituals and deliveries proven by their effect.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Sylvain Ladoire",
|
package/skills/lead/SKILL.md
CHANGED
|
@@ -35,8 +35,16 @@ commands (its CLAUDE.md names them), never with `raf start|done|note|ux|review`.
|
|
|
35
35
|
the human (a trial at 3 was decided on 2026-10-12); if the slots are the bottleneck, say so and let them decide.
|
|
36
36
|
- **Never two subagents in the same repository at the same time**, and the lead does not commit in a
|
|
37
37
|
repository where a subagent is working.
|
|
38
|
-
- **Deliveries and `raf ux` / `raf review` verdicts are done by the lead, one project at a time
|
|
39
|
-
|
|
38
|
+
- **Deliveries and `raf ux` / `raf review` verdicts are done by the lead, one project at a time, or by the `livreur` agent it
|
|
39
|
+
launches** (L439): the delivery and, with `lead.ux: lead`, `raf ux` may be done by the `livreur`; `raf review` stays with the
|
|
40
|
+
lead. Launch one `livreur` at a time: never two deliveries in parallel, and each `livreur` counts in the cap of subagents. Only another
|
|
41
|
+
`livreur` or another subagent blocks a new one; the sessions of a running wave do not (a ready lot is delivered while the wave runs,
|
|
42
|
+
section 2b). Never launch a `livreur` while the wave integrates a lot (`cadence lead tour` shows it): `raf done` would write the plan
|
|
43
|
+
of the main tree, which that integration refuses as modified. Brief: the absolute project folder, the lot, the sha to deliver
|
|
44
|
+
(« livrable jusqu'à »), the value of `lead.ux`, the base URL of the delivered app (the `deliver` configuration of `cadence.yaml`),
|
|
45
|
+
and for a visible lot with `lead.ux: lead` the `ux-reviewer`'s report and the folder of its captures. It hands back one line
|
|
46
|
+
(`L84 livré 3d6edf4, QA à lancer <url>`) or one `Question : …`; the lead itself launches the `qa-reviewer` with the URL of the line
|
|
47
|
+
(section 4), then the `livreur` of the next ready lot.
|
|
40
48
|
- A subagent cannot ask the human anything. When it hits an ambiguity it stops and reports; the lead
|
|
41
49
|
brings the question to the human.
|
|
42
50
|
|
|
@@ -179,7 +187,7 @@ reviewer's last line brought up to date. It does so only when the reviewer has n
|
|
|
179
187
|
what the lot promised. At the **slightest doubt** — a reservation of the reviewer it cannot settle, a capture it cannot read,
|
|
180
188
|
a layout or wording choice that is a matter of taste, a state the captures do not show — it records nothing, brings the
|
|
181
189
|
captures and the question to the human, and the chain goes on with the other lots as for any UX reservation. The verdict
|
|
182
|
-
is
|
|
190
|
+
is recorded by the lead's own `raf ux` or, for a lot it hands over, by the `livreur` on the same criterion (it opens the captures itself and records nothing at the slightest doubt).
|
|
183
191
|
|
|
184
192
|
## 3. Check
|
|
185
193
|
|
|
@@ -217,7 +225,7 @@ is always recorded by the lead's own `raf ux`, never by a subagent.
|
|
|
217
225
|
|
|
218
226
|
## 4. Delivery
|
|
219
227
|
|
|
220
|
-
One project at a time, by the lead: push, then the `deliver` skill (`cadence deliver --dry-run`, then
|
|
228
|
+
One project at a time, by the lead or, for a ready lot it hands over, by the `livreur` agent (« Limits that always apply »): push, then the `deliver` skill (`cadence deliver --dry-run`, then
|
|
221
229
|
`cadence deliver`). Follow the human's standing instructions about confirmation before production.
|
|
222
230
|
|
|
223
231
|
**The sha pushed needs a green verdict of the gate (L426).** Where the project declares a gate (`orchestrate.test` or
|
|
@@ -21,7 +21,7 @@ A point you cannot hold is written under "choix" or "nonVerifie" of your report,
|
|
|
21
21
|
|
|
22
22
|
Limits and rules, before the walkthrough (a code bug found by the review is the first cause of correction passes: 100 of the 143 classified passes of the 23–29-10 window). Re-read your diff against the fiche (if there is one) and the lot, then:
|
|
23
23
|
- rules: for each rule of the fiche (`Rn`), or of the lot when it has no fiche, name the test that fails if the code breaks it and the line of code that holds it; a rule with no such pair is not done;
|
|
24
|
-
- edge cases:
|
|
24
|
+
- edge cases [L447] (the first sub-cause of the code bugs: 96 of the 136 `bug-code` passes of the 24–30-10 window, 71 %): write the list BEFORE the first line of code, and before reporting play each listed input once on the real path (a real file or a real call, never only your fixture) and compare what comes out with what the rule says. For every function, parser, pattern, threshold or command you add or change, list the inputs at its edge — empty, absent, twice, wrong order, wrong type, refused, the real shape of the data (not the one of your fixture), the other callers of what you changed — and give each one a test that fails without your code, before handing back, except a case the fiche does not cover: with a fiche, you do not code a rule it does not state, so that case is written under "nonVerifie" or "choix", neither coded nor tested; a case you listed and did not test goes under "nonVerifie";
|
|
25
25
|
- claims: each figure, « all », « never » or « always » that your README, docs, comments or tests state is checked against the code or by running it, not from memory.
|
|
26
26
|
|
|
27
27
|
Real walkthrough once the tests are green, before the auto-verification below: play what you delivered as a user would, not by re-reading the diff. A command: run it in a throwaway repository (`git init` in a fresh folder of the session's temporary folder, never inside the repository), following step by step the documentation the lot wrote (README, `docs/`), every command exactly as written there; a command the documentation cannot run, or whose documented path is impossible from the state the previous steps leave, is broken. A screen: open it in each of its states, failure included, the failure shown where the action takes place (in view from the control the user just used, not above or below the fold), and take a capture of each. What breaks, fix in this same session (test first, a `fix({{lot}}): …` commit) and play it again. Then write in "resume" a paragraph that starts with « parcours réel »: what you played, what broke and was fixed, what you could not play (also under "nonVerifie"); a lot with nothing to play (no command, no screen) says so in that paragraph.
|
|
@@ -5,7 +5,7 @@ Goal of the lot: {{objectif}}
|
|
|
5
5
|
|
|
6
6
|
You review the mockup BEFORE any code is written: the later steps (acceptance tests, implementation) will take the sheet and the mockup as their reference. Read the project's CLAUDE.md and its charter (existing CSS and variables, neighbouring components) first: the product's identity is a constraint.
|
|
7
7
|
Besides the usability and accessibility checks of your instructions, check the consistency between the mockup and the sheet: every field of `champs` is in the mockup with its `data-testid`, every rule message (`regles[].message`) is visible, word for word, in the state named by the `etat` of the rule, every state of `etats` has its section `#etat-<état>`, the `masque` of an empty-state rule really hides the fields it names.
|
|
8
|
-
Sweep the whole mockup in this review, whatever its rank, and render it as a checklist in the text of your report (one line per item, checked or not): every state of the sheet at 1440 px and at 390 px; every rule of the sheet; contrast (WCAG 2.2 AA, measured); the real envelope of the app (usable width, side bar, header: measure it in the neighbouring pages instead of assuming it); gutters; empty or stretched panels; holes in the layout; what you cannot verify goes in `nonVerifie`, not ticked.
|
|
8
|
+
Sweep the whole mockup in this review, whatever its rank, and render it as a checklist in the text of your report (one line per item, checked or not): every state of the sheet at 1440 px and at 390 px; every rule of the sheet; one line per family of the added criteria of your instructions (visual hierarchy, consistency with the product tokens, quality of the texts, complementary accessibility), per state at 1440 px and 390 px; contrast (WCAG 2.2 AA, measured); the real envelope of the app (usable width, side bar, header: measure it in the neighbouring pages instead of assuming it); gutters; empty or stretched panels; holes in the layout; what you cannot verify goes in `nonVerifie`, not ticked.
|
|
9
9
|
A later review that finds a major on a point nobody changed is counted as a review defect in the wave journal.
|
|
10
10
|
Check the sample data of the mockup: recompute the weekday of every date shown (do not trust the one written). A wrong sample datum (weekday, date, figure that does not add up) is a minor finding, not a major one: it changes no rule, no field and no state of the sheet, and is fixed in a line.
|
|
11
11
|
Ground every finding in a named rule (Nielsen heuristic, WCAG 2.2 AA criterion) or a measurement, or in a mismatch between the mockup and the sheet. Rank them blocking / major / minor: a blocking or major finding sends the mockup back to the spec writer, a minor one becomes a note of the lot.
|
|
@@ -9,6 +9,8 @@ What you produce, in `<dir>/<écran>/`:
|
|
|
9
9
|
2. The captures described above, one per state and per width, copied from the Playwright output directory of this wave into the folder (give a capture the absolute path of that directory, as the Playwright paragraph says, then copy it).
|
|
10
10
|
3. `fiche.yaml`, in the format of the template below. Check it with `cadence spec check <fiche>` until it is valid, and derive the QA expectations with `cadence spec qa <fiche>` only if the lot asks for it.
|
|
11
11
|
|
|
12
|
+
Write the texts before the review, with the structure the UX reviewer will check: an error message says what, why, how to fix; an empty or loading state says what it is, why it is empty, how to start; an action label is a verb that names the result, never "OK" or "Validate" alone; the confirmation of a destructive action names the object and the consequence; one word per notion, in the sheet and in the mockup. A word or a message already in the product is kept as it is.
|
|
13
|
+
|
|
12
14
|
Sheet rules:
|
|
13
15
|
- Every rule is `sourcee` (it says where it comes from: a line of the lot, a note, `expectations.md`, a rule already coded, an API contract) or `hypothese` (you chose it for lack of a source; `si_faux` says what would change if it were wrong). Never present a guess as sourced.
|
|
14
16
|
- Check every date in the sample data of the mockup before keeping it: compute its weekday (a command such as `date -d 2026-09-03 +%A`, or a script), never write it from memory — a « Wed 03/09 » that is a Thursday sends the mockup back for nothing. The same goes for any figure that must add up (a score and its result, a total and its parts).
|