@sylad/cadence 1.36.0 → 1.37.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 +2 -2
- package/dist/orchestrate/briefs.js +23 -3
- package/dist/orchestrate/cycle.js +21 -16
- package/dist/orchestrate/gate-notes.js +7 -1
- package/dist/orchestrate/no-finding.js +19 -0
- package/dist/orchestrate/precheck.js +34 -0
- package/dist/orchestrate/schemas.js +2 -2
- package/dist/orchestrate/table.js +2 -2
- package/docs/corrections.md +2 -0
- package/docs/mesures/brief-implementation-enrichi-avant-apres.md +36 -0
- package/docs/orchestrate.md +8 -3
- package/docs/whats-new.md +3 -0
- package/package.json +2 -1
- package/templates/orchestrate/fix-docs.md +1 -1
- package/templates/orchestrate/fix-minors.md +1 -1
- package/templates/orchestrate/fix.md +5 -4
- package/templates/orchestrate/implement.md +5 -4
- package/templates/orchestrate/precheck.md +1 -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.37.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.37.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
|
@@ -49,12 +49,12 @@ Details: [docs/skills.md](docs/skills.md) and [docs/cadence-hud.md](docs/cadence
|
|
|
49
49
|
|
|
50
50
|
- [docs/raf.md](docs/raf.md): the plan, its commands and its file.
|
|
51
51
|
- [docs/orchestrate.md](docs/orchestrate.md): waves, budgets, and [the edge-case probe](docs/orchestrate.md#the-edge-case-probe-orchestrateprobe-l497).
|
|
52
|
+
- [Brief grid and pre-check reading](docs/mesures/brief-implementation-enrichi-avant-apres.md): grid of the code-reviewer in the `implement` brief (L523).
|
|
52
53
|
- [docs/deliver.md](docs/deliver.md): wait for the CI, deploy, verify the effect; [docs/remote-gate.md](docs/remote-gate.md): the gate on another machine.
|
|
53
54
|
- [docs/spec.md](docs/spec.md): screen and back-end sheets.
|
|
54
55
|
- [docs/ref.md](docs/ref.md): reference documents (specs, norms, mock-ups) and the coverage of their elements by the lots.
|
|
55
56
|
- [docs/lead.md](docs/lead.md): the lead workflow, and the `livreur` agent that delivers a ready lot for it.
|
|
56
|
-
- [docs/overview.md](docs/overview.md): the longer introduction and the map of every page.
|
|
57
|
-
- [docs/fonctionnement.md](docs/fonctionnement.md): how it all works, in four figures.
|
|
57
|
+
- [docs/overview.md](docs/overview.md): the longer introduction and the map of every page; [docs/fonctionnement.md](docs/fonctionnement.md): how it works.
|
|
58
58
|
- [docs/stabilite.md](docs/stabilite.md): what is guaranteed from 1.0.0; [docs/qualite.md](docs/qualite.md): the code-quality ratchet.
|
|
59
59
|
- [docs/whats-new.md](docs/whats-new.md) and [CHANGELOG.md](CHANGELOG.md): what changed.
|
|
60
60
|
|
|
@@ -82,6 +82,23 @@ export function localReading(lot) {
|
|
|
82
82
|
'Keep every read small (`grep -n`, `git log --oneline -S`, a few dozen lines at a time, never a whole large file),',
|
|
83
83
|
].join('\n');
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Ce que le précheck apporte au brief de l'implémentation : son constat (« partiel ») puis, séparée par une ligne vide, ce qu'il a lu
|
|
87
|
+
* (L523), pour que la session parte de là au lieu de tout relire à froid. `lu` n'est passé qu'à la première passe : la lecture date du
|
|
88
|
+
* début du lot, une reprise après interruption a des commits à relire (l'appelant ne le passe pas). Sans constat ni lecture, `commits` est rendu tel quel.
|
|
89
|
+
*/
|
|
90
|
+
export function precheckBrief(commits, finding, lu) {
|
|
91
|
+
const withFinding = finding ? [commits, finding].filter(Boolean).join('\n') : commits;
|
|
92
|
+
if (!lu)
|
|
93
|
+
return withFinding;
|
|
94
|
+
const reading = [
|
|
95
|
+
'Pre-check reading (L523): the read-only pre-check of this lot, at the start of the lot, looked in the repository and read this.',
|
|
96
|
+
'Start from these files, the neighbouring code to imitate and the existing tests it names, without searching for them again;',
|
|
97
|
+
'reopen a file only to modify it or when this reading is not enough, and re-read the lines you edit (the reading dates from the start of the lot).',
|
|
98
|
+
lu,
|
|
99
|
+
].join('\n');
|
|
100
|
+
return [withFinding, reading].filter(Boolean).join('\n\n');
|
|
101
|
+
}
|
|
85
102
|
const FILES = {
|
|
86
103
|
precheck: 'precheck.md',
|
|
87
104
|
spec: 'spec.md',
|
|
@@ -129,9 +146,9 @@ export function renderBrief(kind, vars, source = TEMPLATES_DIR) {
|
|
|
129
146
|
return `${out.replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n').trim()}\n`;
|
|
130
147
|
}
|
|
131
148
|
/**
|
|
132
|
-
* Consigne `{{gate}}` (L399) : le programme
|
|
149
|
+
* Consigne `{{gate}}` (L399, L518) : le programme joue une fois après l'écriture `orchestrate.test` et `orchestrate.build` ; la session ne joue que les tests ciblés du code touché (la suite entière rejouée en session coûtait 2,7 suites par lot, étude L517) et le build tel quel, pas la commande qu'elle juge équivalente.
|
|
133
150
|
* Du 22 au 28-10, des lots annoncés verts arrivaient rouges à la porte parce que la session avait joué `npm run build` (tsconfig.build.json : `src` seul)
|
|
134
|
-
* alors que la porte joue `npx tsc --noEmit` (test et scripts compris) : L330, L353. Vide sans commande déclarée.
|
|
151
|
+
* alors que la porte joue `npx tsc --noEmit` (test et scripts compris) : L330, L353. Vide sans commande déclarée ; sans `orchestrate.test`, la session joue elle-même la suite complète, une fois.
|
|
135
152
|
*/
|
|
136
153
|
export function gateText(gate) {
|
|
137
154
|
// Porte en parties (L512) : une ligne par partie, avec son dossier, jamais la liste sérialisée.
|
|
@@ -139,7 +156,10 @@ export function gateText(gate) {
|
|
|
139
156
|
const lines = [...tests, gate.build ? `- build: \`${gate.build}\`` : ''].filter(Boolean);
|
|
140
157
|
if (!lines.length)
|
|
141
158
|
return '';
|
|
142
|
-
|
|
159
|
+
// Sans `orchestrate.test`, le programme ne joue aucune suite (runChecks la saute) : la session est la seule à pouvoir la jouer.
|
|
160
|
+
if (!gate.test)
|
|
161
|
+
return `The program's gate, played once by the program after your writing session, is exactly:\n${lines.join('\n')}\nThe program plays no test suite for this project: play the full suite yourself, once, before committing, and the build command as written, not a narrower one (\`npm run build\` may cover less than the gate). A red at the gate sends the lot back to a correction pass even when your own run was green.`;
|
|
162
|
+
return `The program's gate, played once by the program after your writing session, is exactly:\n${lines.join('\n')}\nPlay only the tests of the code you touch (targeted: one file or filter, never the full suite), once, before committing: the full suite is not yours to replay (L518). Play the build command as written, not a narrower one (\`npm run build\` may cover less than the gate). A red at the gate sends the lot back to a correction pass even when your own run was green.`;
|
|
143
163
|
}
|
|
144
164
|
/**
|
|
145
165
|
* L'objectif d'un lot (L258) : celui gardé dans l'état du lot (`LotState.goal`, fixé au lancement : `--goal`, sinon la clé `goal:` du plan),
|
|
@@ -10,7 +10,7 @@ import { planLoader, readDocsConfig, readPlanConfig } from '../config.js';
|
|
|
10
10
|
import { PLAN_ACTIONS, leadAnswerMessage, planCommitMessage } from '../plan-commit.js';
|
|
11
11
|
import { articleRepoDir, articlesBrief, docSyncBrief, docSyncGaps, filesOf, gapMessage } from '../docsync.js';
|
|
12
12
|
import { testGateMissing } from '../testgate.js';
|
|
13
|
-
import { citesPriorWork, precheckNothingToFind } from './precheck.js';
|
|
13
|
+
import { citesPriorWork, keepPrecheckReading, localYesNote, precheckNothingToFind } from './precheck.js';
|
|
14
14
|
import { PROBE_FAILED, PROBE_RESERVE, probeStep, probeVars, scheduleProbe } from './probe.js';
|
|
15
15
|
import { changedFiles, dirtyPaths, gitCommonDir, isAncestor, readCommits, resolveCommit, restoreWorktree, tryGit } from '../git.js';
|
|
16
16
|
import { blockingLots, gitCommitsIn } from './table.js';
|
|
@@ -24,7 +24,7 @@ import { TEST_WORKERS_ENV, testWorkersText } from './workers.js';
|
|
|
24
24
|
import { baselineCandidates, gateLabel, gateReplayable, gateUnnamed, redAloneGate, redCandidates, replayGate, runGate } from './gate-parts.js';
|
|
25
25
|
import { withPlace } from './gate-place.js';
|
|
26
26
|
import { APP_TIMEOUT_S, startApp } from './app.js';
|
|
27
|
-
import { capturesText, gateText, headline, integrationText, localReading, newsText, objective, renderBrief, reposText, shotsText, TEMPLATES_DIR, tiersText, tmpText } from './briefs.js';
|
|
27
|
+
import { capturesText, gateText, headline, integrationText, localReading, precheckBrief, newsText, objective, renderBrief, reposText, shotsText, TEMPLATES_DIR, tiersText, tmpText } from './briefs.js';
|
|
28
28
|
import { SlotAbandoned } from './registry.js';
|
|
29
29
|
import { journalTokens, peakContext, mcpServersFor, playwrightDir, runSession, trackGroup, writeMcpConfig, enabledPlugins, settingsFiles } from './launch.js';
|
|
30
30
|
import { cleanPlaywrightOutput, pushed, remoteAdvances, snapshot } from './guard.js';
|
|
@@ -37,6 +37,7 @@ import { avalFicheText, ficheContracts, ficheRules, hasScreenPart } from './aval
|
|
|
37
37
|
import { ficheReviewText, reviewedSheetText } from './fiche.js';
|
|
38
38
|
import { bulletLines, maquetteText, reviewedText } from './maquette.js';
|
|
39
39
|
import { answeredText, checkSpec, costlyHypotheses, ficheHasBack, ficheReady, findFiche, hypothesisLines, answerAltersRule, hypothesisNote, LEAD_SOURCE, ruleIdsSourcedByLead, sourceRules, specText } from './spec.js';
|
|
40
|
+
import { handBackWithoutFinding } from './no-finding.js';
|
|
40
41
|
import { decisionsText, humanDecisions } from './decisions.js';
|
|
41
42
|
import { LOCAL_PRECHECK_UNREADABLE, PRECHECK_UNREADABLE, lotKey, lotRepoPaths, neighbourDirs } from './state.js';
|
|
42
43
|
import { advance, baseMoved, INTEGRATE_WAIT_MS, MAX_REPLAYS, mergeBase, patchId, rebaseOnto, repoBranch, takeRepoWrite } from './integrate.js';
|
|
@@ -593,8 +594,10 @@ function briefFor(c, kind, local = false) {
|
|
|
593
594
|
? `A previous session was interrupted; resume from these commits of the lot already present:\n${mine}`
|
|
594
595
|
: 'A previous session was interrupted before any commit of the lot; start again from the current state of the repository.';
|
|
595
596
|
}
|
|
596
|
-
|
|
597
|
-
|
|
597
|
+
// La lecture ne va qu'à la première passe implement : la session en cours (« running ») ne compte pas, toute autre passe implement déjà jouée l'exclut.
|
|
598
|
+
const firstPass = !l.steps.some((s) => s.kind === 'implement' && s.status !== 'running');
|
|
599
|
+
if (kind === 'implement')
|
|
600
|
+
vars.commits = precheckBrief(vars.commits, l.precheck, firstPass ? l.precheckLu : undefined);
|
|
598
601
|
if (l.pendingAnswer)
|
|
599
602
|
vars.reponse = `Answer from the human to your earlier question: ${l.pendingAnswer}`;
|
|
600
603
|
}
|
|
@@ -1367,6 +1370,8 @@ async function precheck(c) {
|
|
|
1367
1370
|
localYes = true;
|
|
1368
1371
|
rep = null;
|
|
1369
1372
|
}
|
|
1373
|
+
else if (rep)
|
|
1374
|
+
rep.lu = undefined; // « non » ou « partiel » du local, accepté sans relecture par Sonnet : sa lecture n'est pas gardée (L523)
|
|
1370
1375
|
}
|
|
1371
1376
|
}
|
|
1372
1377
|
if (!rep) {
|
|
@@ -1383,11 +1388,8 @@ async function precheck(c) {
|
|
|
1383
1388
|
return;
|
|
1384
1389
|
}
|
|
1385
1390
|
}
|
|
1386
|
-
if (localYes)
|
|
1387
|
-
l.warnings.push(rep.dejaPresent
|
|
1388
|
-
? 'contrôle préalable : « oui » du modèle local, confirmé par Sonnet'
|
|
1389
|
-
: `contrôle préalable : le modèle local a dit « oui », Sonnet dit « ${rep.dejaPresent} » — le local s'est trompé, repasser orchestrate.precheck à true`);
|
|
1390
|
-
}
|
|
1391
|
+
if (localYes)
|
|
1392
|
+
l.warnings.push(localYesNote(rep.dejaPresent));
|
|
1391
1393
|
const proofs = rep.preuves.length ? ` (${rep.preuves.join(' ; ')})` : '';
|
|
1392
1394
|
// « oui » sans preuve ne se vérifie pas : il vaut « partiel », l'implémentation part avec le constat.
|
|
1393
1395
|
const unproven = rep.dejaPresent === 'oui' && rep.preuves.length === 0;
|
|
@@ -1402,6 +1404,7 @@ async function precheck(c) {
|
|
|
1402
1404
|
l.precheck = `A pre-check found part of the deliverable already in the repository (do not redo it): ${rep.resume.trim()}${proofs}`;
|
|
1403
1405
|
l.warnings.push(`contrôle préalable : livrable en partie présent — ${rep.resume.trim()}${proofs}`);
|
|
1404
1406
|
}
|
|
1407
|
+
keepPrecheckReading(l, rep.lu);
|
|
1405
1408
|
l.next = firstWrite(c);
|
|
1406
1409
|
save(c);
|
|
1407
1410
|
}
|
|
@@ -2015,6 +2018,8 @@ async function runChecks(c, red, runs, place) {
|
|
|
2015
2018
|
if (!command)
|
|
2016
2019
|
continue;
|
|
2017
2020
|
l.gate = { label, started: new Date().toISOString() };
|
|
2021
|
+
if (label === 'tests')
|
|
2022
|
+
l.suiteRuns = (l.suiteRuns ?? 0) + 1;
|
|
2018
2023
|
save(c);
|
|
2019
2024
|
const r = await runGate(place.run, command); // porte en parties (L512) : toutes jouées, chacune dans son dossier ; le CheckRun porte le libellé « dir : run ; … »
|
|
2020
2025
|
const run = { label, command: gateLabel(command), code: r.code, ...place.stamp() };
|
|
@@ -2246,7 +2251,7 @@ async function work(c, kind) {
|
|
|
2246
2251
|
else if (made === 0)
|
|
2247
2252
|
l.warnings.push(kind === 'fix' ? 'fix sans nouveau commit : revue lancée sur les commits du lot, avec la raison de la session (choix)' : `${kind} sans nouveau commit : revue lancée sur les commits déjà faits du lot`);
|
|
2248
2253
|
const red = [];
|
|
2249
|
-
// Tests annoncés rouges (L213) : avec orchestrate.test, le programme rejoue et compare à la ligne de base, la session n'est crue que si les tests y sont rouges autrement que par des échecs de départ ; sans, la liste des échecs de base de la session, non vérifiée.
|
|
2254
|
+
// Tests annoncés rouges (L213) : avec orchestrate.test, le programme rejoue et compare à la ligne de base, la session n'est crue que si les tests y sont rouges autrement que par des échecs de départ, et jamais quand sa porte est verte (L522, quels que soient echecsDeBase et nouveauxEchecs) ; sans, la liste des échecs de base de la session, non vérifiée.
|
|
2250
2255
|
const claimed = { source: 'tests', gravite: 'bloquant', texte: `tests annoncés rouges par la session : ${rep.tests.commande} — ${rep.tests.resultat}` };
|
|
2251
2256
|
let deferred = false;
|
|
2252
2257
|
const declared = (rep.echecsDeBase ?? []).map((t) => t.trim()).filter(Boolean);
|
|
@@ -2278,12 +2283,8 @@ async function work(c, kind) {
|
|
|
2278
2283
|
}
|
|
2279
2284
|
if (red.length === 0)
|
|
2280
2285
|
await programChecks(c, red, runs);
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
const gateGreen = runs.some((r) => r.label === 'tests' && r.code === 0);
|
|
2284
|
-
const baseOnly = declared.length > 0 && !!newFailures && !newFailures.length;
|
|
2285
|
-
if (deferred && red.length === 0 && gateGreen && baseOnly)
|
|
2286
|
-
l.warnings.push(`tests annoncés rouges par la session, mais la porte du programme verte (${gateLabel(c.config.test ?? '')}, code 0) et aucun nouvel échec déclaré (nouveauxEchecs vide) : les échecs sont ceux de la base qu'elle déclare (${declared.length}) ; pas de correction`);
|
|
2286
|
+
if (deferred && red.length === 0 && runs.some((r) => r.label === 'tests' && r.code === 0 && !r.instables?.length))
|
|
2287
|
+
l.warnings.push(`tests annoncés rouges par la session, mais la porte du programme verte (${gateLabel(c.config.test ?? '')}, code 0) : l'annonce n'est pas crue, pas de correction`);
|
|
2287
2288
|
else if (deferred && red.length === 0 && !runs.some((r) => r.label === 'tests' && (r.dejaRouges || r.instables?.length)))
|
|
2288
2289
|
red.push(claimed);
|
|
2289
2290
|
if (red.length) {
|
|
@@ -2518,6 +2519,8 @@ async function reviewStep(c, kind) {
|
|
|
2518
2519
|
// L'UX est la dernière étape (L221) : conforme, le lot conclut avec la revue de code ; sinon une correction (jamais une revue de code d'office), la revue de code et l'UX rejouée suivent.
|
|
2519
2520
|
if (summary.conforme)
|
|
2520
2521
|
return () => conclude(c, l.code, noCommitNote(l));
|
|
2522
|
+
if (handBackWithoutFinding(c, l.constats, summary, stop))
|
|
2523
|
+
return;
|
|
2521
2524
|
toFix(c, l.constats, 'UX non conforme');
|
|
2522
2525
|
return;
|
|
2523
2526
|
}
|
|
@@ -2586,6 +2589,8 @@ async function reviewStep(c, kind) {
|
|
|
2586
2589
|
return;
|
|
2587
2590
|
}
|
|
2588
2591
|
const constats = blockingConstats(rep, 'code');
|
|
2592
|
+
if (handBackWithoutFinding(c, constats, summary, stop))
|
|
2593
|
+
return;
|
|
2589
2594
|
(l.reviewCounts ??= []).push(constats.length);
|
|
2590
2595
|
toFix(c, constats, 'revue non conforme');
|
|
2591
2596
|
}
|
|
@@ -3,13 +3,19 @@ import { lotKey } from './state.js';
|
|
|
3
3
|
/** Trace des tests instables sous charge (L354) : le lot (`instables`, journal et `cadence perf`), un avertissement du lot (tableau de fin de vague) et une ligne du journal de la vague. */
|
|
4
4
|
export function noteUnstable(c, tests) {
|
|
5
5
|
const l = c.lot;
|
|
6
|
-
const fresh = tests.filter((t) => !(l.instables ?? []).includes(t));
|
|
6
|
+
const fresh = [...new Set(tests)].filter((t) => !(l.instables ?? []).includes(t));
|
|
7
7
|
if (!fresh.length)
|
|
8
8
|
return;
|
|
9
9
|
l.instables = [...(l.instables ?? []), ...fresh];
|
|
10
10
|
const text = `${fresh.length} test(s) instable(s) sous charge (rouge(s) dans la suite, vert(s) rejoué(s) seul(s)), pas de passe de correction : ${fresh.join(' ; ')}`;
|
|
11
11
|
l.warnings.push(text);
|
|
12
12
|
c.wave.log(`${lotKey(l.project, l.lot)} · ${text}`);
|
|
13
|
+
// Le rouge de charge ne coûte plus de passe, mais la dette reste au lead : un lot à stabiliser (L522).
|
|
14
|
+
for (const t of fresh) {
|
|
15
|
+
const proposal = `[instable] ${t} — rouge dans la suite, vert rejoué seul : à stabiliser`;
|
|
16
|
+
if (!l.proposals.includes(proposal))
|
|
17
|
+
l.proposals.push(proposal);
|
|
18
|
+
}
|
|
13
19
|
c.wave.store.writeLot(l);
|
|
14
20
|
}
|
|
15
21
|
const seconds = (ms) => `${Math.round(ms / 100) / 10} s`.replace(/\.0 s$/, ' s');
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Une revue non conforme n'ouvre une passe de correction que si elle laisse un constat actionnable (L522).
|
|
2
|
+
import { lotKey } from './state.js';
|
|
3
|
+
/**
|
|
4
|
+
* Un constat est actionnable quand il est bloquant ou majeur et que son texte, rogné, n'est pas vide (`blockingConstats` ne garde déjà que les bloquants et majeurs).
|
|
5
|
+
* Les totaux annoncés peuvent être > 0 sans constat lisible (`summarize` retient le plus grand) : la session fix n'aurait rien à lire, le lot est rendu avec les totaux dans la cause.
|
|
6
|
+
* Ses constats sont vidés : une reprise (`resumedFrom && constats.length`) ne rouvre pas la passe. `stop` est celui du cycle. Vrai quand le lot est rendu.
|
|
7
|
+
*/
|
|
8
|
+
export function handBackWithoutFinding(c, constats, summary, stop) {
|
|
9
|
+
if (constats.some((k) => k.texte.trim()))
|
|
10
|
+
return false;
|
|
11
|
+
c.lot.constats = [];
|
|
12
|
+
const esc = c.lot.escalation;
|
|
13
|
+
if (esc && !esc.issue) { // la revue neuve de l'escalade (L337) n'a pas jugé le lot conforme : comme `toFix`, l'escalade est dite rendue et tracée
|
|
14
|
+
esc.issue = 'rendu';
|
|
15
|
+
c.wave.log(`${lotKey(c.lot.project, c.lot.lot)} · escalade ${esc.model} : lot rendu malgré l'escalade`);
|
|
16
|
+
}
|
|
17
|
+
stop(c, 'handed-back', `revue non conforme sans constat actionnable (${summary.bloquants} bloquant(s), ${summary.majeurs} majeur(s) annoncé(s), aucun constat lisible) : pas de passe de correction`);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
@@ -77,3 +77,37 @@ export function precheckNothingToFind(plan, root, lot, repos = [], goal) {
|
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
+
/** Taille permise de la lecture du précheck rangée dans l'état du lot, marqueur compris (L523). */
|
|
81
|
+
export const PRECHECK_LU_MAX = 4000;
|
|
82
|
+
const PRECHECK_LU_CUT = '[…tronqué]';
|
|
83
|
+
/** Ligne du journal quand Sonnet a relu un « oui » du modèle local (L146) : il le confirme ou le contredit. */
|
|
84
|
+
export function localYesNote(verdict) {
|
|
85
|
+
return verdict === 'oui'
|
|
86
|
+
? 'contrôle préalable : « oui » du modèle local, confirmé par Sonnet'
|
|
87
|
+
: `contrôle préalable : le modèle local a dit « oui », Sonnet dit « ${verdict} » — le local s'est trompé, repasser orchestrate.precheck à true`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Range la lecture du précheck (`lu` du rapport, facultatif : `checkShape` ne le contrôle pas) dans `precheckLu` du lot : rognée de ses
|
|
91
|
+
* espaces, coupée à `PRECHECK_LU_MAX` caractères marqueur compris (au plus : un caractère hors plan de base n'est pas coupé en deux). Elle remplace toute lecture rangée plus tôt. Rien n'est rangé quand `lu` est absent, vide ou d'espaces ; un `lu`
|
|
92
|
+
* qui n'est pas une chaîne est écarté avec un avertissement.
|
|
93
|
+
*/
|
|
94
|
+
export function keepPrecheckReading(lot, lu) {
|
|
95
|
+
delete lot.precheckLu; // un précheck rejoué sans lecture ne laisse pas l'ancienne
|
|
96
|
+
if (lu === undefined)
|
|
97
|
+
return;
|
|
98
|
+
if (typeof lu !== 'string') {
|
|
99
|
+
lot.warnings.push('contrôle préalable : champ lu ignoré (mal typé)');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const text = lu.trim();
|
|
103
|
+
if (!text)
|
|
104
|
+
return;
|
|
105
|
+
if (text.length <= PRECHECK_LU_MAX) {
|
|
106
|
+
lot.precheckLu = text;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
let end = PRECHECK_LU_MAX - PRECHECK_LU_CUT.length;
|
|
110
|
+
if (/[\uD800-\uDBFF]/.test(text[end - 1]))
|
|
111
|
+
end -= 1; // ne pas couper une paire de substitution en deux
|
|
112
|
+
lot.precheckLu = `${text.slice(0, end)}${PRECHECK_LU_CUT}`;
|
|
113
|
+
}
|
|
@@ -47,10 +47,10 @@ export const REVIEW_SCHEMA = {
|
|
|
47
47
|
},
|
|
48
48
|
required: ['bloquants', 'majeurs', 'mineurs', 'constats', 'sousTaches', 'nonVerifie', 'verdict'],
|
|
49
49
|
};
|
|
50
|
-
/** Sortie du contrôle préalable : `dejaPresent` vaut « oui » (rien à faire), « partiel » ou « non ». */
|
|
50
|
+
/** Sortie du contrôle préalable : `dejaPresent` vaut « oui » (rien à faire), « partiel » ou « non ». `lu` (L523, facultatif) est ce que le lecteur a lu : fichiers, code voisin, tests. */
|
|
51
51
|
export const PRECHECK_SCHEMA = {
|
|
52
52
|
type: 'object',
|
|
53
|
-
properties: { dejaPresent: { type: 'string', enum: ['oui', 'partiel', 'non'] }, preuves: strings, resume: str },
|
|
53
|
+
properties: { dejaPresent: { type: 'string', enum: ['oui', 'partiel', 'non'] }, preuves: strings, resume: str, lu: str },
|
|
54
54
|
required: ['dejaPresent', 'preuves', 'resume'],
|
|
55
55
|
};
|
|
56
56
|
/** Sortie de la sonde de cas limites (L497) : les tests commités, et `echecs` les tests qu'elle annonce rouges (« test — borne — attendu », un par entrée). */
|
|
@@ -248,8 +248,8 @@ export function renderDetail(lots, sel) {
|
|
|
248
248
|
}
|
|
249
249
|
/** Le tableau de fin de vague (ou de `--status`), une ligne par lot puis le total et les questions. `compact` (L441, `--status`) : sans le détail des lots, une seule ligne renvoie à `--status --detail`. */
|
|
250
250
|
export function renderTable(wave, lots, deps = {}) {
|
|
251
|
-
const rows = lots.map((l) => [`${l.project}:${l.lot}`, stateText(l, wave, deps.now ?? Date.now()), String(l.pass), review(l), ux(l), String(commits(l)), k(counted(l)), `${minutes(l)} min
|
|
252
|
-
const head = ['lot', 'état', 'passes', 'revue', 'UX', 'commits', 'tokens', 'durée'];
|
|
251
|
+
const rows = lots.map((l) => [`${l.project}:${l.lot}`, stateText(l, wave, deps.now ?? Date.now()), String(l.pass), review(l), ux(l), String(commits(l)), k(counted(l)), `${minutes(l)} min`, String(l.suiteRuns ?? '—')]);
|
|
252
|
+
const head = ['lot', 'état', 'passes', 'revue', 'UX', 'commits', 'tokens', 'durée', 'suites'];
|
|
253
253
|
const widths = head.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
254
254
|
const line = (r) => r.map((c, i) => c.padEnd(widths[i])).join(' ').trimEnd();
|
|
255
255
|
const out = [line(head), ...rows.map(line)];
|
package/docs/corrections.md
CHANGED
|
@@ -37,3 +37,5 @@ The cause `bug-code` alone is 69 % of the classified passes (74 of 107, 24→30-
|
|
|
37
37
|
## Passes launched on tests that ended without a code change (L510)
|
|
38
38
|
|
|
39
39
|
A pass with no review finding to read is a pass the gate launched on **red tests** right after a write session (`sans revue (tests rouges)` above). When such a pass ends with **no commit**, the session found nothing to change: the red was a false red (a timeout under load, a test that was already red) or the test needed no fix. `cadence corrections` prints, after the report, `N passes lancées sur des tests, M terminées sans changement de code (P %)` (nothing when there is no pass on tests) and adds `testsSansCode: { passes, sansCode }` to `--json`. The line is outside the report of the 10-10 baseline, which `test/corrections-mesure.test.ts` replays word for word. Two windows are measured with `--since` and `--until` (a window of creation dates of the waves, see above): the figures of the last two 7-day windows are in [mesures/tests-sans-code-2026-11-01.md](mesures/tests-sans-code-2026-11-01.md).
|
|
40
|
+
|
|
41
|
+
Where the wall time and the tokens of a lot go — tests, reads, news, captures, reviews, correction passes with their cause and cost (11.9 min and 126 k tokens a pass), gate, integration — is measured over the 15 days of 17-10 → 01-11 by `node scripts/temps-d-un-lot.mjs` (L517): [mesures/temps-d-un-lot.md](mesures/temps-d-un-lot.md), with the command to replay it and the three levers it sizes (L518, L519, L447).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Mesure avant/après du brief d'implémentation enrichi (L523)
|
|
2
|
+
|
|
3
|
+
Le brief `implement` porte désormais la grille du code-reviewer, l'ordre « tests des cas limites d'abord, relecture contre la grille avant le dernier commit », et, après un précheck, la lecture qu'il a faite (fichiers concernés, code voisin à imiter, tests existants). Ce document est le protocole qui mesure l'effet, et la ligne « avant » relevée par l'étude L517 (`docs/mesures/temps-d-un-lot.md`). Au moment de l'implémentation de L523 la copie en service de l'outil ne contient pas encore le brief enrichi (le lead ne la met à jour qu'après la revue, `npm run service -- <sha>`) : il n'existe donc aucune vague créée avec lui, et la ligne « après » est un emplacement à renseigner. Aucun chiffre « après » n'est écrit ici.
|
|
4
|
+
|
|
5
|
+
## Les trois mesures
|
|
6
|
+
|
|
7
|
+
Elles se relèvent avec `scripts/temps-d-un-lot.mjs` (livré par L517 ; il peut manquer d'une branche qui n'a pas encore reçu L517, ce document ne fait que le nommer) :
|
|
8
|
+
|
|
9
|
+
- les **passes par lot** : passes de correction divisées par les lots de la fenêtre ;
|
|
10
|
+
- la **cause des passes** : la part de `bug-code` (dont les cas limites) et de `derive-architecture` parmi les passes classées ;
|
|
11
|
+
- les **allers-retours de lecture** par lot : appels de lecture (Read, Grep, Glob, `grep`, `cat`, `git show`…) des sessions du lot.
|
|
12
|
+
|
|
13
|
+
## Borne
|
|
14
|
+
|
|
15
|
+
La borne est `<borne>` à relever : l'heure de la mise en service du brief enrichi, relevée par le lead hors vague, entre deux sessions (`date -u -d "$(stat -c %y ~/.local/share/cadence/service)" +%FT%TZ`, en UTC, après le `npm run service` du sha qui porte L523). Même règle que L222 ([autocontrole-avant-revue-2026-10-15.md](autocontrole-avant-revue-2026-10-15.md)) : c'est la date de création de la vague, `created` de son `wave.json`, qui dit si un lot a reçu le brief, non le début de l'`implement` : une vague rend tous ses briefs depuis la copie de l'outil figée à sa création.
|
|
16
|
+
|
|
17
|
+
- l'**avant** rejoue le script sur les lots des vagues créées jusqu'à la borne (`--until <borne>`) ;
|
|
18
|
+
- l'**après** le rejoue sur les lots des vagues créées après elle (`--since <borne>`).
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
node scripts/temps-d-un-lot.mjs --runs <dossier-des-projets>/.cadence/runs --until <borne> --corrections corrections.json
|
|
22
|
+
node scripts/temps-d-un-lot.mjs --runs <dossier-des-projets>/.cadence/runs --since <borne> --corrections corrections.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
où `corrections.json` est la sortie de `cadence corrections <dossier-des-projets> --classify --cache docs/mesures/corrections-classement.json --json` (même fenêtre).
|
|
26
|
+
|
|
27
|
+
La borne est **invalide quand une vague à cheval est encore reprenable** : une vague créée avant la borne, interrompue ou suspendue, qui serait reprise (`--resume`) après elle voit des passes se terminer après la borne, que ni `--until` ni `--since` ne lisent. Avant de relever la borne, terminer ces vagues ou ne plus les reprendre (liste : `jq -r 'select(.status != "done") | "\(.id) \(.status) \(.created)"' <dossier-des-projets>/.cadence/runs/*/wave.json`) ; sinon la reposer hors vague et rejouer les deux commandes.
|
|
28
|
+
|
|
29
|
+
## Résultats
|
|
30
|
+
|
|
31
|
+
| | Passes par lot | Part de bug-code (dont cas limites) | Part de dérive d'architecture | Allers-retours de lecture par lot |
|
|
32
|
+
|---|---|---|---|---|
|
|
33
|
+
| Avant (étude L517, 17-10 → 01-11) | 0,85 (620 passes, 730 lots) | 52 % (301 passes, dont 223 cas limites) | 10 % (58 passes) | 13,4 |
|
|
34
|
+
| Après (`--since <borne>`) | à renseigner | à renseigner | à renseigner | à renseigner |
|
|
35
|
+
|
|
36
|
+
Limite : tant qu'aucune vague n'a tourné avec le brief enrichi, la ligne « après » reste à renseigner par le lead ; ce document n'affirme aucun gain. Le sens attendu : moins de passes par lot, une part de `bug-code` et de dérive d'architecture en baisse, moins d'allers-retours de lecture quand le précheck a rendu sa lecture. Les lots sans précheck ne reçoivent pas la lecture : seule la grille et l'ordre « cas limites d'abord » les concernent.
|
package/docs/orchestrate.md
CHANGED
|
@@ -193,12 +193,14 @@ usability (code only when the lot is not `visible`). Failing tests (reported red
|
|
|
193
193
|
|
|
194
194
|
**A project without `orchestrate.test` is flagged (L327)**: without the key the wave never runs the suite itself — only the review does, if it decides to (it ran 12 test files of a project on 2026-10-25 and a red test reached « ready »). `cadence orchestrate --dry-run` writes one line per repository, under the first lot of that repository: `<repo> · porte de tests absente : orchestrate.test n'est pas déclaré, la vague ne lancera pas la suite (seule la revue le fera si elle le décide) · commande probable : npm test (à déclarer en orchestrate.test)`, and [`cadence lead tour`](lead.md#lead-tour) writes the same line for every project of the folder. The probable command is read from the repository, never run: `npm test` when `package.json` has a `scripts.test` (the `npm init` placeholder « no test specified » does not count), `pytest` when `pyproject.toml` names pytest, `mvn test` when there is a `pom.xml`; with none of them the line carries no command. A `cadence.yaml` that cannot be read gives no such line (it has its own error). Declaring `test:` (and `build:`) in the project's `cadence.yaml`, once the command is known to be green, removes the line.
|
|
195
195
|
|
|
196
|
-
**Tests already red at the start (L213)**: a lot is no longer handed back « tests rouges » for failures that were there before it. When `orchestrate.test` fails (or the session reports red tests), the program reads the failing tests from the runner's output (vitest/jest `FAIL`/`●`, pytest `FAILED`, go `--- FAIL:`, TAP `not ok`), replays the same command at the sha the lot started from (the parent of the lot's oldest work commit, plan-only commits left out, else the `HEAD` before its first session) in a throwaway `git worktree` outside the repository, and compares: if every failing test was already red, it is not a defect of the lot — no correction pass, the code review takes place, the reviewer is told, and the final table lists them apart (`déjà rouge au départ (<sha>) : <test>`, 30 at most then a count). The worktree gets links to the repository's ignored folders at any depth, such as `.venv` and, in a monorepo, `packages/*/node_modules`, when their parent exists at that sha; build outputs such as `dist` or `coverage` are left out. Each `node_modules` is rebuilt entry by entry, every entry linked to the live one except the workspace links of npm, pnpm and yarn (`node_modules/@x/shared -> ../../packages/shared`, a link to code of the repository outside any `node_modules`), which are remade towards the worktree's own copy so that the start sha is tested against its own sources, not the lot's (a workspace package that does not exist at the start sha gets no link). Known limit: only `node_modules` links are remade; whatever points into the live repository by an absolute path, such as an editable Python install (`.venv` with a `.pth` file naming the live `src`, `pip install -e`) or an absolute symlink in another ignored folder, still runs the lot's code in the baseline. Likewise, a workspace package consumed through its build output (`main: dist/...`) cannot load in the baseline worktree: output dirs are not linked, so such a monorepo keeps the pre-L213 « tests rouges » outcome unless `orchestrate.build` is declared (the build is then played first in the baseline worktree, see below). A lot resumed in a later wave keeps the same baseline: the sha is the parent of its oldest work commit, not the `HEAD` of the new wave, so a failure the lot itself introduced in an earlier wave is never excused. A test red now and not at the start stays a blocking finding, and only that one is given to the correction (the others are counted as « already red »). When the output names no test, or the baseline cannot be run, the tests stay red as before (no comparison is guessed). A failure the output does not tie to a test (unhandled error, a spec file that fails to load such as jest « Test suite failed to run » or vitest `FAIL a.test.ts [ a.test.ts ]`, coverage threshold) keeps the tests red. Without `orchestrate.test` the program cannot replay anything: a session that reports green tests and lists under `echecsDeBase` the failures it saw at the start sha gets them listed apart, marked « non vérifié » in the table, and the review takes place; a session that reports the tests red stays red, the brief asking it not to report red for base failures alone — unless (L215) the report says so in a structured way: with `vert: false`, a non-empty `echecsDeBase` and an empty `nouveauxEchecs` (the list of tests red now that were not red at the start sha), `vert: false` is not a reason to correct, the lot carries a « non vérifié » warning, the reviewer is told that the baseline is unverified and the review takes place. A non-empty `nouveauxEchecs` with `vert: false` triggers a correction pass (the new failures are in the finding); an absent `nouveauxEchecs`, or an empty `echecsDeBase`, keeps the correction. With `orchestrate.test` declared
|
|
196
|
+
**Tests already red at the start (L213)**: a lot is no longer handed back « tests rouges » for failures that were there before it. When `orchestrate.test` fails (or the session reports red tests), the program reads the failing tests from the runner's output (vitest/jest `FAIL`/`●`, pytest `FAILED`, go `--- FAIL:`, TAP `not ok`), replays the same command at the sha the lot started from (the parent of the lot's oldest work commit, plan-only commits left out, else the `HEAD` before its first session) in a throwaway `git worktree` outside the repository, and compares: if every failing test was already red, it is not a defect of the lot — no correction pass, the code review takes place, the reviewer is told, and the final table lists them apart (`déjà rouge au départ (<sha>) : <test>`, 30 at most then a count). The worktree gets links to the repository's ignored folders at any depth, such as `.venv` and, in a monorepo, `packages/*/node_modules`, when their parent exists at that sha; build outputs such as `dist` or `coverage` are left out. Each `node_modules` is rebuilt entry by entry, every entry linked to the live one except the workspace links of npm, pnpm and yarn (`node_modules/@x/shared -> ../../packages/shared`, a link to code of the repository outside any `node_modules`), which are remade towards the worktree's own copy so that the start sha is tested against its own sources, not the lot's (a workspace package that does not exist at the start sha gets no link). Known limit: only `node_modules` links are remade; whatever points into the live repository by an absolute path, such as an editable Python install (`.venv` with a `.pth` file naming the live `src`, `pip install -e`) or an absolute symlink in another ignored folder, still runs the lot's code in the baseline. Likewise, a workspace package consumed through its build output (`main: dist/...`) cannot load in the baseline worktree: output dirs are not linked, so such a monorepo keeps the pre-L213 « tests rouges » outcome unless `orchestrate.build` is declared (the build is then played first in the baseline worktree, see below). A lot resumed in a later wave keeps the same baseline: the sha is the parent of its oldest work commit, not the `HEAD` of the new wave, so a failure the lot itself introduced in an earlier wave is never excused. A test red now and not at the start stays a blocking finding, and only that one is given to the correction (the others are counted as « already red »). When the output names no test, or the baseline cannot be run, the tests stay red as before (no comparison is guessed). A failure the output does not tie to a test (unhandled error, a spec file that fails to load such as jest « Test suite failed to run » or vitest `FAIL a.test.ts [ a.test.ts ]`, coverage threshold) keeps the tests red. Without `orchestrate.test` the program cannot replay anything: a session that reports green tests and lists under `echecsDeBase` the failures it saw at the start sha gets them listed apart, marked « non vérifié » in the table, and the review takes place; a session that reports the tests red stays red, the brief asking it not to report red for base failures alone — unless (L215) the report says so in a structured way: with `vert: false`, a non-empty `echecsDeBase` and an empty `nouveauxEchecs` (the list of tests red now that were not red at the start sha), `vert: false` is not a reason to correct, the lot carries a « non vérifié » warning, the reviewer is told that the baseline is unverified and the review takes place. A non-empty `nouveauxEchecs` with `vert: false` triggers a correction pass (the new failures are in the finding); an absent `nouveauxEchecs`, or an empty `echecsDeBase`, keeps the correction. With `orchestrate.test` declared, when the program's own gate is green (`orchestrate.test` exits 0, L361; made unconditional by L522) the claim of red tests is never believed, whatever `echecsDeBase` and `nouveauxEchecs` hold (absent, empty or not, a non-empty `nouveauxEchecs` included): one warning (`tests annoncés rouges par la session, mais la porte du programme verte (<command>, code 0) : l'annonce n'est pas crue, pas de correction`), the review takes place, no correction pass and no « tests rouges » escalation. A red gate (non-zero exit) keeps the rules above. The program never guesses from the text of the result (« 24 failed »). The `implement` and `fix` briefs ask for `echecsDeBase` and `nouveauxEchecs`.
|
|
197
197
|
|
|
198
198
|
**The build is played before the tests (L333)**: the gate of the wave runs `orchestrate.build` first, then `orchestrate.test`, because tests that read the build output (end-to-end tests over `dist`) are red without it — on 2026-10-25 three projects had to declare `test: astro build && npm test` (13 tests out of 15 red without `dist`), which played the build twice on every pass. A red build hands the lot back as a single « build » finding and the tests are not run. The L213 baseline plays the build first in its throwaway worktree too, then the tests: without it the start sha, with no `dist`, would excuse a test wrongly or turn everything red. A build that fails at the start sha gives no baseline (the build says nothing about the tests there): the tests stay red as before, no comparison is guessed. Without `orchestrate.build` nothing changes. A project that wrote `build && test` into its `orchestrate.test` can drop the duplicate build.
|
|
199
199
|
|
|
200
200
|
**The program's gate is a visible step (L395)**: while `orchestrate.build` or `orchestrate.test` runs (after a writing session, at the rebased tip, and for the replay that follows a red), no claude session runs, and the lot used to read `implémentation` in `--status` and `→ implement` in the `cadence-hud` band: on 28-10 the gate of L392 ran 5 silent minutes. The lot's state now carries `gate` (`build` or `tests`, with its start), set when the command starts and removed when the checks end, red or green. `--status` prints the state `porte tests (3 min)` instead of `implémentation` (only for a running wave: a `gate` left in the state of a killed wave is not shown as running), and the band prints `porte@tests 3 min` where it printed `→ implement`. `--status --wave <wave>` is the same as `--status <wave>` (it used to show the last wave of the folder); two different ids are refused.
|
|
201
201
|
|
|
202
|
+
**No correction pass without a finding, and a red that passes alone is proposed, not corrected (L522)**: the study of L517 classified 580 correction passes of the waves of 2026-10-23 to 2026-11-01: 73 were « sans-constat » (13 %), and 48 of those 73 ended with no commit. Two causes are closed. (1) A non-conforme review, code or UX, that lists no actionable finding no longer opens a fix pass: a finding is actionable when it is blocking or major and its text, trimmed, is not empty (minors never are, on this path); the review can announce `bloquants: 1` with `constats: []`, and `conforme` is false on the announced totals. The lot is handed back (`handed-back`) with the cause `revue non conforme sans constat actionnable (N bloquant(s), M majeur(s) annoncé(s), aucun constat lisible) : pas de passe de correction` in the table and in the wave log; `pass` is left as it was, `next` is cleared (never `fix`: the lot is handed back) and the lot's findings are emptied, so a resume does not reopen the pass. A conforme review is untouched (minors pass, proposals). (2) A test red in the whole suite, absent from the baseline and green when replayed alone (the replay of L354, which needs `orchestrate.testReplay`) is still noted unstable as before (warning, wave log, `instables`) and now adds one lot proposal, `[instable] <test> — rouge dans la suite, vert rejoué seul : à stabiliser`, once per test; a « délai trop juste » test (L510) keeps its own warning and adds no proposal. The `fix` brief asks the session to replay each red test it is given alone before writing code, to leave a test that is green alone untouched and to list it under `choix` as unstable. **Measure**: the before/after reading is the `sans-constat` row of `cadence corrections` read by `node scripts/temps-d-un-lot.mjs --corrections <corrections.json>` (L517, unchanged by this lot): start of the window, 73 passes out of 580 classified (13 %), 48 of those 73 finished without a commit. The row also counts the passes opened on red tests with no review, so the drop expected after this lot is the unstable reds and the announced reds (L361 rule above), not the real red tests.
|
|
203
|
+
|
|
202
204
|
**A red test is replayed alone before it counts, only on a declared command (L354)**: on 2026-10-26, with 32 cores and a cap of 4 sessions, every suite ran vitest at 31 workers, and four correction passes (L344 11 min, L342 9, L349 7, B28.12 4) found nothing to fix — each time « a test that passes alone ». cadence does not guess how a file is replayed on your runner (a first version appended the files to `orchestrate.test` and, on a composed gate, excused a real regression), so the replay is **off unless the project declares its command**: `orchestrate.testReplay`, where `{files}` is replaced by the files of the red tests, e.g. `testReplay: "npx vitest run --maxWorkers=$CADENCE_TEST_WORKERS --minWorkers=1 {files}"` (a value without `{files}` is refused when the file is read). **Without it nothing is replayed and nothing is held for unstable**: a red test is counted as before. With it, a red test of the gate (`orchestrate.test`, played after every writing session and at the rebased tip) that is absent from the baseline is **replayed alone, twice at most**, by that command, in the lot's worktree (vitest names `file > suite > test`, pytest `file::test`, jest `FAIL file` and `● Suite › test`, the latter tied to the `FAIL` line that precedes it in the same output and replayed with that file; the file is the part before the first `::` or ` > `, so either of them inside a test title is not part of the path). Green at a replay, the test is **« instable sous charge »**: it is not counted, it triggers no correction pass, even when the session reported its tests red (`vert: false`: a session that plays the suite under the same load sees the same red; the announcement is not held against the lot when every red of the gate is unstable, as it is not when every red was already red at the start), the lot goes on to its review, and the trace is a lot warning (so a line of the final table), a journal line `<project>:<lot> · N test(s) instable(s) sous charge …`, the lot's `instables` list in its journal, and the `cadence perf` line `tests instables sous charge` (`instables` in `--json`). Red at both replays it is counted as before. A red name that designates no file (a plain `FAIL name`) cannot be replayed alone and is counted at once. **A replay in which the test did not run counts red**: only a replay that exits 0 after running tests proves a test green, and only for the tests whose file appears in the replay's output, as a whole path on a line of the runner — never in the launcher's echo of the command: npm 12 writes `npm notice run 'vitest' run … <files>` on stderr for `npx …` as for `npm test -- …`, npm and pnpm write `> vitest run … <files>`, yarn `$ …`, and those lines name every file asked for, played or not (a runner that silently skips a file it cannot find, such as vitest given a path of another workspace, exits 0 without playing it: that test stays red; with a reporter that does not list files, nothing is ever excused; `a.test.ts` is not found in `src/a.test.ts`); a replay that fails, or that runs no test (the runner's closing line `No test files found, exiting with code`, `No tests found, exiting with code` or `no tests ran in`, even with exit code 0; the same words inside a test title or a test's output do not count), leaves every test it was asked about red. In a vitest workspace the names read `|package| src/a.test.ts > case`: the `|package|` prefix is dropped to find the file. The reviewer is told which tests were red under load (`instables` of the check, also when the baseline holds red tests of its own), so it does not report them, and the correction finding lists only the tests still red after the replays. The baseline run is included: with `testReplay`, a red test of the L213 baseline (the gate played at the start sha, in its throwaway worktree) is replayed alone there, twice at most, before it is retained as « already red at the start »; green at a replay it is dropped from the baseline (a journal line `… · ligne de base <sha> : N test(s) rouge(s) sous charge, vert(s) rejoué(s) seul(s), non retenu(s) …`), so a test the lot really breaks is not excused by a load failure of the start sha. The baseline replays are not the lot's « instables »: they say nothing about the lot's code. **A composed gate is not supported (decision of 26-10)**: `orchestrate.testReplay` is refused when `cadence.yaml` is read if `orchestrate.test` has `&&` or `||` outside quotes, or a parenthesis (sub-shell `( … )`, `$( … )`, function): a first runner that goes red cuts the next one, so a green replay of its tests says nothing of the rest, and a replay command written for one runner of the gate cannot replay the tests of the other. Write the replay for a gate that launches a single runner; without `testReplay` a composed gate is played as before, nothing is replayed. A jest `FAIL` line may carry a duration (`FAIL src/a.test.ts (6.21 s)`) or a project name (`FAIL backend src/a.test.ts`): the file is its last word, and any other `PASS`/`FAIL` line ties no file to the `●` lines that follow. The `FAIL project file` line is itself a red name (`project file`) and is replayed with that file, like the `●` tests under it.
|
|
203
205
|
|
|
204
206
|
**A test that expires close to its limit even alone is a « délai trop juste », not a bug (L510)**: on 2026-10-31 a test of warhammer (L61) took 25 s alone for a 30 s delay: it passed alone, expired under the slightest load, and the gate sent four correction passes and the escalation for a defect that was not one. A composed gate declares its parts (`orchestrate.test` as a list, L512, below) to get the same. The replay alone now also reads the **duration** of the test (its own line `✓ file > test 25012ms`, else the `✓ file (3 tests) 24s` line of its file, an upper bound) and the **delay** it expired at in the whole-suite output (vitest `Test timed out in 30000ms`, jest `Exceeded timeout of 5000 ms for a test`). Green alone at **80 %** of that delay or more, the test is no longer filed with the unstable-under-load ones: no correction pass, the lot's `delaisJustes` (test, duration, delay) and a warning that names it (`délai trop juste, pas un bug … : test/a.test.ts > plan-data (25 s seul pour 30 s de délai) — relever le délai du test`), shown in the end-of-wave table. A test still red alone is a bug whatever its delay; a test with no delay in the suite output or no readable duration stays an ordinary unstable test.
|
|
@@ -216,7 +218,9 @@ orchestrate:
|
|
|
216
218
|
|
|
217
219
|
**Each session gets its share of the machine (L350)**: the wave computes `CADENCE_TEST_WORKERS = max(1, cores ÷ session cap)` (`--max-sessions` or `CADENCE_MAX_SESSIONS`; 32 cores and a cap of 4 give 8; a `CADENCE_TEST_WORKERS` already set in the environment that launches the wave wins when it is a positive integer). It is put in the environment of every session and of every command of the gate (tests, build, baseline, replays), recorded as `testWorkers` in the wave state, and the `implement`, `fix`, minors and librarian briefs tell the session to respect it: `vitest run --minWorkers=1 --maxWorkers=$CADENCE_TEST_WORKERS` (vitest 2 refuses `--maxWorkers` alone, its default minimum being the core count: « options.minThreads and options.maxThreads must not conflict »), `jest --maxWorkers=$CADENCE_TEST_WORKERS`, `pytest -n $CADENCE_TEST_WORKERS`. The gate command itself must honour it for the share to bind: cadence's own `vitest.config.ts` reads it (`poolOptions.forks.maxForks`, `threads.maxThreads`); in another project write `orchestrate.test: npx vitest run --minWorkers=1 --maxWorkers=$CADENCE_TEST_WORKERS` (or the equivalent of its runner).
|
|
218
220
|
|
|
219
|
-
**The session plays the gate's own commands (L399)**: the briefs of the writing sessions (implement, fix, minors pass, librarian pass) name the exact `orchestrate.test` and `orchestrate.build` of the project (`The program's gate, played after
|
|
221
|
+
**The session plays the gate's own commands (L399)**: the briefs of the writing sessions (implement, fix, minors pass, librarian pass) name the exact `orchestrate.test` and `orchestrate.build` of the project (`The program's gate, played once by the program after your writing session, is exactly: …`) and ask to run the build as written, not a narrower equivalent (the tests are targeted since L518, below). Measured on 22–28-10 (`docs/mesures/portes-rouges-2026-10-28.md`): sessions that played `npm run build` (`src` only) announced a green lot that `npx tsc --noEmit` (tests included) sent back to a correction pass. Without `orchestrate.test` and `orchestrate.build` there is no line.
|
|
222
|
+
|
|
223
|
+
**One full gate per lot (L518)**: the writing sessions no longer replay the full suite. Measured by `scripts/temps-d-un-lot.mjs` (study L517, `docs/mesures/temps-d-un-lot.md`): 1 945 full suites played inside the sessions (2.7 per lot, median 2, 1.8 min each) on top of the 1 337 the gate plays after the writing sessions; one UI separator (ol L239) replayed the suite 5 times. The briefs (implement, fix, minors pass, librarian pass) now say: play only the tests of the code you touch (targeted, once, before committing) and the build as written; the full suite is the program's gate, played once after the session, and `cadence verify --gate` takes its verdict back (L397) instead of playing it again. The `{{gate}}` text carries this when the project declares `orchestrate.test`; with `orchestrate.build` alone the text lists the build and tells the session to play the full suite itself, once (the program plays no suite then); with no gate declared the briefs tell the session to play the full suite itself, once, since nobody else will. The wave measures it: the `suites` column of the final table (and of `--status`) is the number of times the program played the full `orchestrate.test` for the lot (`suiteRuns` in the lot state: one per writing pass, replays alone of red tests not counted; `—` when none was played, a gate without `orchestrate.test` or a state written before L518). The suites a session plays itself stay measured by `scripts/temps-d-un-lot.mjs` from the transcripts (the program does not read them), so the table shows the gate's share only: with the briefs followed, a lot of one writing session reads `1`.
|
|
220
224
|
|
|
221
225
|
**Review sized to the lot (L108)**: a lot whose `estimate` is ≤ `orchestrate.review.threshold` (0.25 day by default)
|
|
222
226
|
is *light*: one single review (Sonnet by default, `code-reviewer` agent, same criteria), no minors pass — the minors are
|
|
@@ -238,7 +242,7 @@ orchestrate:
|
|
|
238
242
|
escalation: { model: fable } # no default: absent = nothing changes
|
|
239
243
|
```
|
|
240
244
|
|
|
241
|
-
The name is passed as it stands to `claude --model` — no closed list `haiku|sonnet|opus` (the same holds for `@<model>` of a lot and for `orchestrate.review.light` / `full`, L345: the three aliases keep their meaning, any other name goes through unchanged, and `--dry-run` prints `⚠ modèle « <name> » hors des alias …` for each such name, without refusing), and no default value, because the account may not have the model. When the review is still not compliant after the planned passes (`MAX_PASSES`, or `MAX_PASSES_CONVERGING` when it converges), or when the lot would be handed back « tests rouges » after them, the wave plays **one** correction and then **one** fresh code review, both on that model, before handing the lot back. The lot is handed back only if that escalated review is not compliant (`… ; rendu malgré l'escalade <model>`); the escalated correction that leaves the tests red hands it back without a review. A compliant escalated review ends the lot as any compliant review does (minors pass, UX review, `ready`). The UX review keeps its own rule: a lot handed back for a non-compliant UX review is not escalated. The escalation counts as a correction pass (`passes` of the table) and is played at most once per lot, a resume included (`escalation` in the lot's journal).
|
|
245
|
+
The name is passed as it stands to `claude --model` — no closed list `haiku|sonnet|opus` (the same holds for `@<model>` of a lot and for `orchestrate.review.light` / `full`, L345: the three aliases keep their meaning, any other name goes through unchanged, and `--dry-run` prints `⚠ modèle « <name> » hors des alias …` for each such name, without refusing), and no default value, because the account may not have the model. When the review is still not compliant after the planned passes (`MAX_PASSES`, or `MAX_PASSES_CONVERGING` when it converges), or when the lot would be handed back « tests rouges » after them, the wave plays **one** correction and then **one** fresh code review, both on that model, before handing the lot back. The lot is handed back only if that escalated review is not compliant (`… ; rendu malgré l'escalade <model>`); an escalated review that is not compliant and lists no actionable finding (L522) hands the lot back at once, with the outcome `revue non conforme sans constat actionnable …` and the journal line `escalade <model> : lot rendu malgré l'escalade`, no further correction; the escalated correction that leaves the tests red hands it back without a review. A compliant escalated review ends the lot as any compliant review does (minors pass, UX review, `ready`). The UX review keeps its own rule: a lot handed back for a non-compliant UX review is not escalated. The escalation counts as a correction pass (`passes` of the table) and is played at most once per lot, a resume included (`escalation` in the lot's journal).
|
|
242
246
|
|
|
243
247
|
The lot's budget is respected: the escalation needs what a correction and its review need (`REVIEW_RESERVE`, as for any correction pass), and the budget of a lot in a project that declares `escalation` reserves its share for it (`ESCALATION_RESERVE`, see **Budget**). When the budget does not cover it, it is **not played** and the outcome says so (`escalade <model> non jouée (budget du lot …)`); the lot is handed back to the lead with its findings. The final table prints one line per escalated lot, `<project>:<lot> — escalade <model> : <outcome>` (`en cours` only while the lot still runs — the table also serves `--status` and a suspended wave —; a lot withdrawn (`--drop`), failed by an error or stopped by an incident of the repository before the model's verdict says `engagée, lot rendu avant le verdict du modèle`: the model did not judge it and it is not counted against the model —, compliant review, lot handed back despite the escalation — for the human or an arbitration of the lead —, not played, or an escalated session that returned nothing (empty output, launch error, cut, timeout; the only case said `session escaladée sans résultat`) — then the model never judged the lot and the lead takes it up, `rendu malgré l'escalade` is said only for a lot handed back on a verdict). A lot whose escalated review was compliant but is handed back later (non-compliant UX review, refused integration) keeps `revue neuve conforme` and says its lot line is the reason, the wave log traces the three moments, and `cadence perf` counts the escalated lots of each window and their outcome (`docs/perf.md`). The price grid of `cadence perf` knows `sonnet`, `opus` and `haiku`: the cost of an escalated step on another model reads `n/d`.
|
|
244
248
|
|
|
@@ -422,6 +426,7 @@ Once the sheet exists, the steps after it all read it (L174, `src/orchestrate/av
|
|
|
422
426
|
- **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).
|
|
423
427
|
- **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`).
|
|
424
428
|
- **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`.
|
|
429
|
+
- **The grid of the code-reviewer, edge cases first, and the pre-check's reading (L523).** The study L517 found `bug-code` in 52 % of the correction passes (223 of them edge cases) and architecture drift in 10 %, and 13.4 reading round trips per lot. `implement.md` (not `fix.md`) therefore carries a fourth fixed block, « Grid of the code-reviewer (L523) », a paragraph of its own between « Limits and rules » and the real walkthrough (plain text, no marker, no new variable): the families the review applies to the diff (code bug, architecture drift, a test missing or that cannot fail, dead code, an error swallowed, documentation that does not follow, a written convention not followed), the order « write the test of each edge case of the goal and see it fail for the right reason BEFORE the first line of code » (a case the sheet does not cover is neither coded nor tested: `"nonVerifie"` or `"choix"`), and the re-reading of the diff against the grid before the last commit (a point not held goes under `"choix"` or `"nonVerifie"`, no new report field). The pre-check carries the other half: its report has an optional `lu` field (the files the lot concerns as `path:line`, the neighbouring code to imitate, the existing tests opened). The program stores it, trimmed and cut at 4000 characters (the end `[…tronqué]` included), in `precheckLu` of the lot's state when the verdict is `non`, `partiel` or an unproven `oui`; nothing is stored when `lu` is absent, empty or not a string (the last case adds the warning `contrôle préalable : champ lu ignoré (mal typé)`), for a proven `oui` (the lot is handed back), or for a `non`/`partiel` of the local model with `orchestrate.precheck: local` (accepted without a re-read by Sonnet, its reading is not trusted; after a local `oui` it is Sonnet's `lu` that is stored). The first `implement` pass then gets, after the pre-check finding and a blank line, a block « Pre-check reading (L523) »: start from those files, code and tests without searching for them again, reopen a file only to modify it or when the reading is not enough, re-read the lines you edit. A pass resumed after an interruption and a `fix` pass never get it (the reading dates from the start of the lot). `precheck` and `precheckVerdict` keep their meaning, an older state without `precheckLu` replays unchanged, and `cadence corrections` still reads `precheckVerdict`. Effect: [before/after measure](mesures/brief-implementation-enrichi-avant-apres.md) (passes per lot, cause of the passes, reading round trips, with `scripts/temps-d-un-lot.mjs`).
|
|
425
430
|
- **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).
|
|
426
431
|
- **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.
|
|
427
432
|
- **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.
|
package/docs/whats-new.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## In two lines
|
|
6
6
|
|
|
7
|
+
- **1.37.0**: one full test suite per lot, played by the gate (`suites` column); no correction pass without a finding; a richer implementation brief; `scripts/temps-d-un-lot.mjs` measures where a lot's time goes.
|
|
7
8
|
- **1.36.0**: `cadence deliver` no longer waits for the CI of a sha whose commits only touch the plan, with timed steps and a `deliveries-steps.log` journal; `orchestrate --add` puts a lot in a live wave.
|
|
8
9
|
- **1.35.0**: a test already red before a lot, or that only needs more time, no longer costs correction passes, including on a gate declared in parts; QA passes run in browsers of their own; design agents check contrast and AI tics.
|
|
9
10
|
- **1.34.1**: republishes 1.34.0, whose npm publication failed after the tag; same features (edge-case probe, steadier `mise-en-service` tests).
|
|
@@ -64,6 +65,8 @@ Each is a paragraph in [docs/whats-new.md](whats-new.md) and a full section in [
|
|
|
64
65
|
|
|
65
66
|
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).
|
|
66
67
|
|
|
68
|
+
**1.37.0**: the first release of the efficiency work, sized by a measure of 721 lots (L517, `docs/mesures/temps-d-un-lot.md`, script `node scripts/temps-d-un-lot.mjs`): a lot took 25 minutes and 265k tokens, tests were 62 % of the writing sessions' wall time and a correction pass cost about 12 minutes. The writing sessions of `cadence orchestrate` now play only the targeted tests of the code they touch; the full suite is played once by the program as the gate, `cadence verify --gate` takes its verdict back, and the final table and `--status` count the full suites of each lot in a `suites` column (L518, `docs/orchestrate.md`). A non-compliant review that lists no blocking or major finding no longer opens a correction pass: the lot is handed back with the announced totals as the cause; a test red in the suite and green alone is proposed as a lot to stabilise, and a green program gate always outweighs a session's claim of red tests (L522). The `implement` brief carries the code reviewer's grid, « edge-case tests first, seen failing, then code » and a re-reading of the diff before the last commit; the pre-check passes what it read (`precheckLu`) to the first implementation so it does not read the repository again (L523). The tests workflow tolerates the npm « previously published » refusal of its publish dry-run on a non-release commit, and nothing else (L515). `raf --version` and `raf -v` print the version, and `cadence orchestrate alpha:L1` launched from inside `alpha` says why the folder is not found (L376).
|
|
69
|
+
|
|
67
70
|
**1.36.0**: `cadence deliver` stops waiting for a CI that proves nothing. When every commit since the previous delivery touches only the plan, it says `→ CI : non attendue (aucun fichier hors plan depuis <sha>)`, then runs the deploy and the checks; as soon as one commit touches anything else, or the proof is missing (no previous delivery, rewritten history, no plan, empty range), it waits as before (L516, `docs/deliver.md`). 42 % of the app deliveries measured on 2026-11-01 were such shas. Every step line of the output now starts with its UTC time and `✓ livré` ends with its duration, and each step played appends a JSON line to `deliveries-steps.log`, so a slow delivery can be read step by step. `cadence orchestrate --add <project:lot> [--wave <wave>]` adds a lot to a live wave instead of launching a second one: the lot is checked like a launched one, queued in the wave's `adds.log` and played as soon as a session slot is free; the refusal of a second wave in the same repository now names this command, with the lot and the wave (L421, `docs/orchestrate.md`). A session running in a sibling repository is no longer handed back for a commit on another repository's main branch once a lot was added to the wave.
|
|
68
71
|
|
|
69
72
|
**1.35.0**: the wave's gate stops paying for reds that need no fix. With `orchestrate.testReplay`, a red that stays red alone in the lot is replayed alone at the start sha, and one already failing there is « déjà rouge »: no correction pass, no escalation; a test that expired in the whole suite and passes alone at 80 % or more of its delay is a « délai trop juste », a warning naming the test with its duration and its delay (L510, `docs/orchestrate.md`). On a gate played in several folders, `orchestrate.test` takes a list of parts `[{ dir, run, replay? }]` and each part plays, replays and attributes its own reds, so a composed gate gets the same two verdicts (L512, `docs/orchestrate.md`, « A gate declared in parts »; evatosorus `node --test` is not covered yet). `cadence corrections` now counts the passes launched on tests that ended without a code change (L510, `docs/corrections.md`). The gate can also run on another machine through `orchestrate.gate.host`, by ssh at the pushed sha, with a local fallback and a lost connection replayed locally, never counted red (L403, `docs/remote-gate.md`). The `qa-reviewer` opens a browser context and a profile of its own, so two QA passes started together no longer share a tab (L511, `docs/skills.md`). The `spec-writer` joins a `contraste.md` (WCAG 2.2 ratios, 4.5:1 at least) to each sheet, and the `ux-reviewer` gets a delivery grid and a static check of AI tics (L513, `docs/orchestrate.md`). Small fixes: `cadence release` prints its local tag before the gate and removes it when red (L508, `docs/releasing.md`), `raf ux` and `raf review` print the commit command for the regenerated plan (L509), and `cadence deliver` outside a wave commits the plan it wrote for the « article à rafraîchir » lot (L420, `docs/news.md`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylad/cadence",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.37.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",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"NOTICE",
|
|
21
21
|
"docs/raf.md",
|
|
22
22
|
"docs/orchestrate.md",
|
|
23
|
+
"docs/mesures/brief-implementation-enrichi-avant-apres.md",
|
|
23
24
|
"docs/deliver.md",
|
|
24
25
|
"docs/remote-gate.md",
|
|
25
26
|
"docs/spec.md",
|
|
@@ -8,7 +8,7 @@ Documentation behind the lot:
|
|
|
8
8
|
{{constats}}
|
|
9
9
|
|
|
10
10
|
Read the diff of each commit above (`git show <sha>`), the project's CLAUDE.md, the README, the pages of `docs/`, the CHANGELOG and the news entries; update each document the change makes false or leaves out; where the README is a short home page, the detail goes in the pages of `docs/` and the README receives only a link. A document that the change really leaves true is left alone, but the gap stays open for the program until it is acknowledged: {{ack}} A session that neither writes the documentation nor acknowledges the gap hands the lot back to the lead: it never goes to review. Say why under "choix" as well.
|
|
11
|
-
Rules: commit each document as soon as it is right, with explicit paths (never `git add -A` or `commit -a`), and a message that cites the lot (`docs({{lot}}): …`); run the project's documentation checks if it has any, and
|
|
11
|
+
Rules: commit each document as soon as it is right, with explicit paths (never `git add -A` or `commit -a`), and a message that cites the lot (`docs({{lot}}): …`); run the project's documentation checks if it has any, and the targeted tests that read the documents plus the build, before reporting (the full suite is the program's gate: do not replay it; with no gate below, play the full suite yourself, once); do not push, deliver, run `raf done`, `raf ux` or `raf review`.
|
|
12
12
|
Never run `git checkout` or `git switch` to another commit or branch in this repository: the orchestrator watches it and a moved HEAD breaks the other sessions of the wave. To look at another sha, use a throwaway `git worktree add --detach <tmp> <sha>`, removed afterwards.
|
|
13
13
|
{{elsewhere}}
|
|
14
14
|
Do not stop to ask: an interpretation you settle yourself goes under "choix" with the alternative you did not take; "questions" stays empty. Making no documentation commit is a valid outcome only with the acknowledgement above.
|
|
@@ -10,7 +10,7 @@ Minor findings:
|
|
|
10
10
|
|
|
11
11
|
Fix each minor finding that is right, and nothing else. Rules: test first; commit each fix as soon as
|
|
12
12
|
its tests pass, with explicit paths (never `git add -A` or `commit -a`), and a message that cites the
|
|
13
|
-
lot (`fix({{lot}}): …`); run the
|
|
13
|
+
lot (`fix({{lot}}): …`); run the targeted tests of the code you touch (once, before committing) and the build, before reporting: the full suite is played once by the program as the gate given below, do not replay it (with no gate below, play the full suite yourself, once); do not push,
|
|
14
14
|
deliver, run `raf done`, `raf ux` or `raf review`. Read the project's CLAUDE.md first.
|
|
15
15
|
Never run `git checkout` or `git switch` to another commit or branch in this repository: the orchestrator watches it and a moved HEAD breaks the other sessions of the wave. To look at another sha, use a throwaway `git worktree add --detach <tmp> <sha>`, removed afterwards.
|
|
16
16
|
Do not stop to ask. A finding that is wrong, or not worth its change, is not fixed: list it under
|
|
@@ -9,9 +9,10 @@ Commits of the lot so far:
|
|
|
9
9
|
Findings to fix:
|
|
10
10
|
{{constats}}
|
|
11
11
|
|
|
12
|
+
Red tests named above (L522): before writing any code, replay each red test named alone, with the project's own single-test command. A test that is red alone is a defect: fix it. A test that is green when replayed alone is unstable under load, not a defect of the lot: do not change code for it, and list it under « choix » of your report as unstable, to be proposed to the lead for its own lot.
|
|
13
|
+
|
|
12
14
|
Rules: test first; commit each fix as soon as its tests pass, with explicit paths (never
|
|
13
|
-
`git add -A` or `commit -a`), and a message that cites the lot (`fix({{lot}}): …`); run the
|
|
14
|
-
full test suite and build before reporting; do not push, deliver, run `raf done`, `raf ux` or
|
|
15
|
+
`git add -A` or `commit -a`), and a message that cites the lot (`fix({{lot}}): …`); run the targeted tests of the code you touch (once, before committing) and the build, before reporting: the full suite is played once by the program as the gate given below, do not replay it (with no gate below, play the full suite yourself, once); do not push, deliver, run `raf done`, `raf ux` or
|
|
15
16
|
`raf review`. Read the project's CLAUDE.md first.
|
|
16
17
|
Sealed test of ANOTHER lot: if what your own sealed tests require contradicts a sealed acceptance test of another lot (an `acceptation-<lot>.json` that is not yours), it is a question to the lead naming both tests (file and `Rn — …` name of each): never modify, rewrite or reseal it and never pick a side. Stop there and put the question in your report. The program compares the fingerprints of the sealed tests of every other lot at the end of each session and hands your lot back, naming the first offending commit, before any review.
|
|
17
18
|
Never run `git checkout` or `git switch` to another commit or branch in this repository: the orchestrator watches it and a moved HEAD breaks the other sessions of the wave. To look at another sha, use a throwaway `git worktree add --detach <tmp> <sha>`, removed afterwards.
|
|
@@ -23,7 +24,7 @@ Self-check before handing back (the recurring findings of the reviews), covering
|
|
|
23
24
|
- no false comment: a comment, a docstring or a message never describes a behaviour the code no longer has or does not have.
|
|
24
25
|
A point you cannot hold is written under "choix" or "nonVerifie" of your report, not left unsaid.
|
|
25
26
|
|
|
26
|
-
Auto-verification once the tests are green, before the review: when the
|
|
27
|
+
Auto-verification once the tests are green, before the review: when the targeted tests and the build pass, re-read the complete diff of your fix commits (`git diff <HEAD before your first fix>..HEAD`, run in each repository where you committed a fix: the project and any neighbouring repository above) 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. What you find in your own fix, fix it at once (a `fix({{lot}}): …` commit) and rerun the targeted tests and the build; touch nothing else. Then write in "resume" a paragraph that starts with « auto-vérification »: what you re-read, what you found and fixed, what you could not hold. It is one reading of the diff, not a second review: no subagent, no new exploration, no re-reading of files the diff does not touch.
|
|
27
28
|
|
|
28
29
|
If a finding is wrong, say so under "choix" with the reason. If you make no commit at all, "choix" MUST say why: a session with no commit and no reason is handed back to the lead without a new review (the review would reread the same commits). Ask (under "questions") only when a fix needs a decision that passes the rule below.
|
|
29
30
|
A question is legitimate only if it names what it would change: the scope of the lot, the architecture, or a costly rollback (data, production, public API);
|
|
@@ -32,7 +33,7 @@ a spacing, colour or label value when a measurement or a rule justifies it; the
|
|
|
32
33
|
a News entry: only if a finding asks for it; a written rule of the project's CLAUDE.md (apply it); who launches the review or UX pass (never the session: the program and the lead do).
|
|
33
34
|
"not my job to touch the plan" is not a question: say it in the report.
|
|
34
35
|
Never stop to ask about how to organise your own work (split or squash a commit, order of the sub-parts, whether to continue, "I have no question"): decide, list it under "choix", go on. Leave "questions" empty rather than writing a placeholder such as "no question".
|
|
35
|
-
Tests already red before the lot: if the suite is red, check whether the same tests are red at the sha the lot started from (the parent of the oldest work commit that cites {{lot}}, plan-only commits left out, or the `HEAD` before this session if the lot has none: a throwaway `git worktree add --detach <tmp> <sha>`, removed afterwards). Failures already there are not a defect of this lot: do not try to fix them and do not report your tests as red for them; list their names under "echecsDeBase" in your report (one test per entry, as the runner prints it), list under "nouveauxEchecs" the tests that are red now and were NOT red at that sha (an empty list when there are none: with `vert: false`, a non-empty "echecsDeBase" and an empty "nouveauxEchecs" the lot goes to review without a correction pass; any entry in "nouveauxEchecs" with `vert: false` triggers one), and say under "nonVerifie" if you could not check. The program replays the suite itself and compares to the same baseline.
|
|
36
|
+
Tests already red before the lot: if the suite is red, check whether the same tests are red at the sha the lot started from (the parent of the oldest work commit that cites {{lot}}, plan-only commits left out, or the `HEAD` before this session if the lot has none: a throwaway `git worktree add --detach <tmp> <sha>`, removed afterwards). Failures already there are not a defect of this lot: do not try to fix them and do not report your tests as red for them; list their names under "echecsDeBase" in your report (one test per entry, as the runner prints it), list under "nouveauxEchecs" the tests that are red now and were NOT red at that sha (an empty list when there are none: with `vert: false`, a non-empty "echecsDeBase" and an empty "nouveauxEchecs" the lot goes to review without a correction pass; any entry in "nouveauxEchecs" with `vert: false` triggers one, unless the program's gate is green (L522): then the claim is not believed and nothing is opened), and say under "nonVerifie" if you could not check. The program replays the suite itself and compares to the same baseline.
|
|
36
37
|
A finding you contest (it describes a state older than the code, or you think it wrong) is not a question and not a reason to stop: do not ask the lead to arbitrate. Write it under "choix" with the evidence (the command, the line, the commit), fix what is right, go on; the next fresh review re-reads it.
|
|
37
38
|
You are one short session of an orchestrated wave: do not launch subagents (the Agent tool is disabled).
|
|
38
39
|
Give your final report as the structured output (commits, tests, build, what you could not verify, choices, questions).
|
|
@@ -5,8 +5,7 @@ Goal: {{objectif}}
|
|
|
5
5
|
{{fiche}}
|
|
6
6
|
{{decisions}}
|
|
7
7
|
Rules: test first; commit each sub-part as soon as its tests pass, with explicit paths (never
|
|
8
|
-
`git add -A` or `commit -a`), and a message that cites the lot (`feat({{lot}}): …`); run the
|
|
9
|
-
full test suite and build before reporting; do not push, deliver, run `raf done`, `raf ux` or
|
|
8
|
+
`git add -A` or `commit -a`), and a message that cites the lot (`feat({{lot}}): …`); run the targeted tests of the code you touch (once, before committing) and the build, before reporting: the full suite is played once by the program as the gate given below, do not replay it (with no gate below, play the full suite yourself, once); do not push, deliver, run `raf done`, `raf ux` or
|
|
10
9
|
`raf review`.
|
|
11
10
|
Sealed test of ANOTHER lot: if what your own sealed tests require contradicts a sealed acceptance test of another lot (an `acceptation-<lot>.json` that is not yours), it is a question to the lead naming both tests (file and `Rn — …` name of each): never modify, rewrite or reseal it and never pick a side. Stop there and put the question in your report. The program compares the fingerprints of the sealed tests of every other lot at the end of each session and hands your lot back, naming the first offending commit, before any review.
|
|
12
11
|
Never run `git checkout` or `git switch` to another commit or branch in this repository: the orchestrator watches it and a moved HEAD breaks the other sessions of the wave. To look at another sha, use a throwaway `git worktree add --detach <tmp> <sha>`, removed afterwards.
|
|
@@ -24,9 +23,11 @@ Limits and rules, before the walkthrough (a code bug found by the review is the
|
|
|
24
23
|
- 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
24
|
- 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
25
|
|
|
26
|
+
Grid of the code-reviewer (L523), the one the review applies to your diff: code bug (wrong result, unhandled error, input trusted raw); architecture drift (a responsibility in the wrong module, a duplicated block, a layer skipped); a test missing or that cannot fail; dead code; an error swallowed; documentation that does not follow the change; a written convention of the project not followed. Edge cases first: for each edge case of the goal (the « edge cases » point above: empty, absent, twice, wrong order, wrong type, refused, the real shape of the data, the other callers of what you change), write its test BEFORE the first line of code and see it fail for the right reason, then code; a case the lot does not cover is neither coded nor tested, it goes under "nonVerifie" or "choix". Before your last commit, re-read your diff against this grid and fix what it shows; a point you do not hold goes under "choix" or "nonVerifie" of the report (no new field).
|
|
27
|
+
|
|
27
28
|
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.
|
|
28
29
|
|
|
29
|
-
Auto-verification once the tests are green, before the review: when the
|
|
30
|
+
Auto-verification once the tests are green, before the review: when the targeted tests and the build pass, re-read the complete diff of the lot, commit by commit (the commits `raf commits {{lot}}` lists, each with `git show <sha>`; never a `git diff` range, which would take in the commits of other lots) against the grid of the code-reviewer agent (the paragraph « Grid of the code-reviewer (L523) » above): code bug (wrong result, unhandled error, input trusted); architecture drift (a responsibility put in the wrong module, a duplicated block, a layer skipped); docs/architecture.md (or the project's architecture documentation) up to date; edge cases (the list of the edge-cases point above, each with its test); one test per changed behaviour. What you find, fix it at once (test first, a `fix({{lot}}): …` commit) and rerun the targeted tests and the build. Then write in "resume" a paragraph that starts with « auto-vérification »: what you re-read, what you found and fixed, what you could not hold. It is one reading of the diff, not a second review: no subagent, no new exploration, no re-reading of files the diff does not touch.
|
|
30
31
|
|
|
31
32
|
Decide minor interpretation questions yourself (the wording of a message, a name, a default, the
|
|
32
33
|
reading of an ambiguous line of the lot) and list each one under "choix" in your report, with the
|
|
@@ -39,7 +40,7 @@ a written rule of the project's CLAUDE.md (apply it); who launches the review or
|
|
|
39
40
|
Never stop to ask about how to organise your own work (split or squash a commit, order of the sub-parts, whether to continue, "I have no question"): decide, list it under "choix", go on. Leave "questions" empty rather than writing a placeholder such as "no question".
|
|
40
41
|
If the lot's commits already cover its open sub-tasks, do not ask whether to continue: make no commit and say so
|
|
41
42
|
in your report (the review that follows re-reads those commits). If they do not cover them all, implement the rest.
|
|
42
|
-
Tests already red before the lot: if the suite is red, check whether the same tests are red at the sha the lot started from (the parent of the oldest work commit that cites {{lot}}, plan-only commits left out, or the `HEAD` before this session if the lot has none: a throwaway `git worktree add --detach <tmp> <sha>`, removed afterwards). Failures already there are not a defect of this lot: do not try to fix them and do not report your tests as red for them; list their names under "echecsDeBase" in your report (one test per entry, as the runner prints it), list under "nouveauxEchecs" the tests that are red now and were NOT red at that sha (an empty list when there are none: with `vert: false`, a non-empty "echecsDeBase" and an empty "nouveauxEchecs" the lot goes to review without a correction pass; any entry in "nouveauxEchecs" with `vert: false` triggers one), and say under "nonVerifie" if you could not check. The program replays the suite itself and compares to the same baseline.
|
|
43
|
+
Tests already red before the lot: if the suite is red, check whether the same tests are red at the sha the lot started from (the parent of the oldest work commit that cites {{lot}}, plan-only commits left out, or the `HEAD` before this session if the lot has none: a throwaway `git worktree add --detach <tmp> <sha>`, removed afterwards). Failures already there are not a defect of this lot: do not try to fix them and do not report your tests as red for them; list their names under "echecsDeBase" in your report (one test per entry, as the runner prints it), list under "nouveauxEchecs" the tests that are red now and were NOT red at that sha (an empty list when there are none: with `vert: false`, a non-empty "echecsDeBase" and an empty "nouveauxEchecs" the lot goes to review without a correction pass; any entry in "nouveauxEchecs" with `vert: false` triggers one, unless the program's gate is green (L522): then the claim is not believed and nothing is opened), and say under "nonVerifie" if you could not check. The program replays the suite itself and compares to the same baseline.
|
|
43
44
|
Report: commits (sha + subject), tests and build results with their numbers, what you could not
|
|
44
45
|
verify, choices made, open questions.
|
|
45
46
|
|
|
@@ -9,4 +9,5 @@ Answer `oui` only when every part of the lot is present, with a proof for each (
|
|
|
9
9
|
some parts are present (say which); `non` otherwise. When in doubt, answer `non`: an implementation session follows.
|
|
10
10
|
You are read-only: do not modify, commit, push or run `raf start|done|note|review|ux`. Do not launch subagents.
|
|
11
11
|
Give your report as the structured output: `dejaPresent` (oui | partiel | non), `preuves` (the proofs), `resume` (one sentence).
|
|
12
|
+
Also give, in the optional `lu` field, what you read to look: the files the lot concerns (`path:line`), the neighbouring code to imitate and the existing tests you opened, one line each (a few lines, not a copy of the code). The implementation session starts from it instead of searching again; leave `lu` out when you read nothing of use.
|
|
12
13
|
{{tmp}}
|