@sylad/cadence 1.27.0 → 1.28.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/agents/doc-keeper.md +17 -0
- package/agents/ux-reviewer.md +18 -0
- package/dist/cli.js +48 -8
- package/dist/config-set.js +2 -1
- package/dist/config.js +16 -1
- package/dist/lead.js +24 -7
- package/dist/orchestrate/cycle.js +29 -2
- package/dist/priority.js +77 -0
- package/dist/verify-gate.js +3 -2
- package/docs/cadence-hud.md +2 -0
- package/docs/deliver.md +2 -0
- package/docs/fonctionnement-lead-dark.svg +2 -2
- package/docs/fonctionnement-lead-light.svg +2 -2
- package/docs/lead-workflow-dark.svg +2 -2
- package/docs/lead-workflow-light.svg +2 -2
- package/docs/lead.md +12 -4
- package/docs/orchestrate.md +5 -2
- package/docs/raf.md +1 -0
- package/docs/session.md +7 -0
- package/docs/skills.md +4 -0
- package/docs/whats-new.md +3 -0
- package/package.json +1 -1
- package/skills/lead/SKILL.md +8 -5
|
@@ -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.28.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.28.0",
|
|
5
5
|
"author": { "name": "Sylvain Ladoire" },
|
|
6
6
|
"homepage": "https://github.com/Sylad/cadence",
|
|
7
7
|
"repository": "https://github.com/Sylad/cadence",
|
package/agents/doc-keeper.md
CHANGED
|
@@ -17,3 +17,20 @@ Follow the brief you are given. The method:
|
|
|
17
17
|
5. **A document outside the repository** is yours only when the configuration says so: the brief then lists the entries of `docs.articles` (the cadence.yaml of the project: « this project is also described by this file of that repository », with the absolute path of the repository, open to you for reading). Never guess such a file, never go looking for one. Do not edit it, and never write to the plan of that repository (no `raf add`, no edit of its plan file): write a proposed lot instead — a title and a body that name the repository, the file, the passage and the sentence to change — and put it in the `resume` of your report, starting with « lot proposé ». The lead decides whether it enters that repository's plan.
|
|
18
18
|
|
|
19
19
|
Never push, deliver, or run `raf done|start|note|review|ux`. Never run `git checkout` or `git switch` to another commit or branch in this repository. Do not launch subagents. Do not stop to ask: an interpretation you settle yourself goes under "choix" with the alternative you did not take. A document you could not check against the code goes under "nonVerifie". Give the report in the structured output the brief asks for.
|
|
20
|
+
|
|
21
|
+
## Short reply
|
|
22
|
+
|
|
23
|
+
Only when the brief carries a line `Report file: <absolute path>` (the lead, which keeps its context
|
|
24
|
+
small by reading a report only when it needs to). That line is the only trigger; an orchestrated
|
|
25
|
+
session and a call by hand without it have none.
|
|
26
|
+
|
|
27
|
+
- The full report, with its measurements, is written to that file with Bash. Use the path the brief gives
|
|
28
|
+
and no other: it is outside the repository, so the working tree stays as you found it. The
|
|
29
|
+
verdict is the last line of the file. Write the file before you reply.
|
|
30
|
+
- Reply to the caller with 15 lines at most, and nothing before or after them:
|
|
31
|
+
`Verdict: <the one-line verdict>`; `Findings:` one line per finding, most severe first, with its rank and the rule or the
|
|
32
|
+
measure it rests on (e.g. `README.md:12 — still says the old flag, `git show <sha>` line 40`); `Not verified: <what could not be run or seen>`
|
|
33
|
+
(`Not verified: none` when nothing); `Sub-tasks: <n> — <the lines to add to the plan, by their first words>`; `Report: <the path>`. A « lot proposé » for a document outside the repository goes in `Sub-tasks:` (title first), its body in the file.
|
|
34
|
+
- If you cannot write the file, say so on the first line of the reply and give the whole report in the reply, as if
|
|
35
|
+
the brief had no report file: a verdict whose evidence nobody can read is lost.
|
|
36
|
+
- With no `Report file:` line in the brief, give the complete report in your reply.
|
package/agents/ux-reviewer.md
CHANGED
|
@@ -98,3 +98,21 @@ A short report:
|
|
|
98
98
|
- Judge on taste, or propose a new visual identity.
|
|
99
99
|
- Report a finding you have not seen in a capture or measured.
|
|
100
100
|
- Edit code, commit, or record `raf ux` yourself: the session that owns the lot does it.
|
|
101
|
+
|
|
102
|
+
## Short reply
|
|
103
|
+
|
|
104
|
+
Only when the brief carries a line `Report file: <absolute path>` (the lead, which keeps its context
|
|
105
|
+
small by reading a report only when it needs to). That line is the only trigger; an orchestrated
|
|
106
|
+
session and a call by hand without it have none.
|
|
107
|
+
|
|
108
|
+
- The full report, with its measurements, is written to that file with Bash. Use the path the brief gives
|
|
109
|
+
and no other: it is outside the repository, so the working tree stays as you found it. The
|
|
110
|
+
verdict is the last line of the file. Write the file before you reply.
|
|
111
|
+
- Reply to the caller with 15 lines at most, and nothing before or after them:
|
|
112
|
+
`Verdict: <the one-line verdict>`; `Findings:` one line per finding, most severe first, with its rank and the rule or the
|
|
113
|
+
measure it rests on (e.g. `major — /players: target 18×18 px, WCAG 2.5.8`); `Not verified: <what could not be run or seen>`
|
|
114
|
+
(`Not verified: none` when nothing); `Sub-tasks: <n> — <the lines to add to the plan, by their first words>`; `Report: <the path>`.
|
|
115
|
+
Add a last line `Captures: <the absolute path of the capture folder>` (the folder the brief names): the report file does not replace the captures, which the lead opens itself.
|
|
116
|
+
- If you cannot write the file, say so on the first line of the reply and give the whole report in the reply, as if
|
|
117
|
+
the brief had no report file: a verdict whose evidence nobody can read is lost.
|
|
118
|
+
- With no `Report file:` line in the brief, give the complete report in your reply.
|
package/dist/cli.js
CHANGED
|
@@ -22,7 +22,8 @@ import { dueLine, isRecurring, recurringByDue } from './recurring.js';
|
|
|
22
22
|
import { schedule } from './schedule.js';
|
|
23
23
|
import { AGENTS_DIR, installAgents, installSkills, SKILLS_DIR } from './skills.js';
|
|
24
24
|
import { configSet } from './config-set.js';
|
|
25
|
-
import {
|
|
25
|
+
import { movePriority } from './priority.js';
|
|
26
|
+
import { findProjects, readDepth, leadTour, TITLE_MAX, truncate, readLeadUx, tourFreeLine, readLeadContextThreshold, tourLine, tourNoTestLines, tourWorktreeLines } from './lead.js';
|
|
26
27
|
import { configuredCap, expiredHoldLines, slotsLine, orchestrate, realOrchestrateDeps } from './orchestrate/command.js';
|
|
27
28
|
import { addHold, releaseHold } from './orchestrate/registry.js';
|
|
28
29
|
import { activeLock, REPO_LOCK } from './orchestrate/lock.js';
|
|
@@ -53,6 +54,7 @@ const HELP = `raf — plan « reste à faire » versionné dans le dépôt, reli
|
|
|
53
54
|
raf start <id> raf done <id> [--force] raf drop <id> [--reason texte]
|
|
54
55
|
raf todo <id> [--reason texte] remet en attente un lot démarré sans commit de travail (doing → todo, started retiré, note datée) ; refusé s'il a des commits ; rouvre un lot done ou dropped (→ todo, finished retiré, note « rouvert ») — la seule façon de rouvrir, archivé compris (il revient dans le fichier du plan)
|
|
55
56
|
raf note <id> "texte"
|
|
57
|
+
--verbose avec done, ux, review, note, move ou public : imprime la ligne « id état titre entier » du lot touché ; avec hook post-commit : le titre entier (sinon 60 caractères au plus)
|
|
56
58
|
raf title <id> "nouveau titre" remplace le titre d'un lot, de tout statut (done compris), et note l'ancien dans une note datée
|
|
57
59
|
raf did <id> ["texte"] un lot récurrent (--every) a été refait : le « dû depuis N j » repart de aujourd'hui
|
|
58
60
|
raf ux enable revue UX obligatoire avant « done » pour les lots --visible
|
|
@@ -74,6 +76,7 @@ const HELP = `raf — plan « reste à faire » versionné dans le dépôt, reli
|
|
|
74
76
|
cadence orchestrate <projet>:<lot>[@modèle]… [--budget 2M] [--goal "…"] [--dry-run] [--wave nom] [--continue [--until HH:MM] [--priority a,b]] une session claude neuve par étape ; --status [vague] [--watch [--interval s]] ; --resume [vague | --wave vague] [--budget 1M] [--budget projet:lot=+300k] [--answer projet:lot "réponse"] [--drop projet:lot] ; --drop projet:lot [--wave vague] ; --stop-after-current [--wave vague] ; --waits [--since AAAA-MM-JJ]
|
|
75
77
|
mode worktree par lot (orchestrate.worktrees: true) (activé par défaut dans cadence, désactivé ailleurs tant que la clé est absente) ; ses options : --resume --wave <vague> --integrate projet:lot (rejoue un lot rendu à l'intégration) · --resume --wave <vague> --replay projet:lot (idem, et après une écriture de plan refusée) · --remove-worktree projet:lot [--abandon-commits] (retire le worktree et la branche conservés) ; la liste des worktrees conservés : orchestrate --status et lead tour
|
|
76
78
|
cadence lead tour [dossier] [--idle 3] [--depth n] [--json] le tableau du lead, sans modèle : une ligne par sous-dossier qui a un plan, ou la ligne du dossier seul s'il est lui-même un projet (en cours, dérive, notes de clôture, prochain lot prêt, dépôt), précédées de l'en-tête perf : une ligne par ⚠ de cadence perf, rien si tout est stable
|
|
79
|
+
cadence lead priority <projet> --before|--after <projet> [dossier] [--depth n] range un projet dans la liste priority: du cadence.yaml du dossier (ordre du tour, de orchestrate --continue et du panneau des lots), commentaires gardés ; ne commite pas
|
|
77
80
|
cadence lead hold <étiquette> [--ttl minutes] | lead release <étiquette> déclare (60 min par défaut), puis rend, une session lancée à la main par le lead : elle est soustraite des créneaux libres de orchestrate --status et de lead tour ; au TTL échu, les deux la signalent en ⚠ jusqu'au release
|
|
78
81
|
cadence corrections [dossier] [--classify [--model m]] [--cache fichier] [--cache-sous-causes fichier] [--sample n] [--until date] [--since date] [--json] mesure des passes de correction des journaux dossier/.cadence/runs : lots à ≥1 et ≥2 corrections, par projet, visibles ou non, passes par cause (classées par Ollama avec --classify, hors quota)
|
|
79
82
|
cadence perf [dossier] [--since date] [--json] tableau de bord du workflow, sans modèle : deux fenêtres (7 derniers jours contre les 7 précédents) lues dans dossier/.cadence/runs et les plans, chaque vitesse à côté de sa qualité, ⚠ au-delà de 25 % de dégradation ; tokens par lot selon l'estimate, part de la revue, délais création → done → livraison, durée de mur par étape, coût en euros par lot livré (grille cadence.yaml perf.prices) ; --since est un jour qui tronque les fenêtres
|
|
@@ -197,6 +200,7 @@ function dispatch(argv, io) {
|
|
|
197
200
|
ignored: { type: 'boolean' },
|
|
198
201
|
notes: { type: 'boolean' },
|
|
199
202
|
json: { type: 'boolean' },
|
|
203
|
+
verbose: { type: 'boolean' },
|
|
200
204
|
classify: { type: 'boolean' },
|
|
201
205
|
model: { type: 'string' },
|
|
202
206
|
cache: { type: 'string' },
|
|
@@ -230,10 +234,12 @@ function dispatch(argv, io) {
|
|
|
230
234
|
if (command === 'lead') {
|
|
231
235
|
if (rest[0] === 'hold' || rest[0] === 'release')
|
|
232
236
|
return leadHold(rest, values.ttl, io, today);
|
|
237
|
+
if (rest[0] === 'priority')
|
|
238
|
+
return leadPriority(rest, values.before, values.after, values.depth, io);
|
|
233
239
|
if (values.ttl !== undefined)
|
|
234
240
|
throw new RafError('--ttl ne va qu\'avec lead hold');
|
|
235
241
|
if (rest[0] !== 'tour' || rest.length > 2)
|
|
236
|
-
throw new RafError('usage : cadence lead tour [dossier] [--idle 3] [--depth n] [--json] | lead hold <étiquette> [--ttl minutes] | lead release <étiquette>');
|
|
242
|
+
throw new RafError('usage : cadence lead tour [dossier] [--idle 3] [--depth n] [--json] | lead priority <projet> --before|--after <projet> [dossier] | lead hold <étiquette> [--ttl minutes] | lead release <étiquette>');
|
|
237
243
|
const idle = values.idle === undefined ? undefined : Number(values.idle);
|
|
238
244
|
if (idle !== undefined && !(Number.isInteger(idle) && idle >= 0))
|
|
239
245
|
throw new RafError(`--idle invalide : ${values.idle}`);
|
|
@@ -460,6 +466,7 @@ function dispatch(argv, io) {
|
|
|
460
466
|
io.out(`${rest[0]} → ${status}`);
|
|
461
467
|
if (status === 'done')
|
|
462
468
|
warnIntegrating(root, io.err);
|
|
469
|
+
touchedLot(plan, rest[0], values.verbose, io);
|
|
463
470
|
if (status === 'done' && !rest[0].includes('/')) {
|
|
464
471
|
const lot = plan.lot(rest[0]);
|
|
465
472
|
if (lot.visible && !loadEntries(newsDir).some((e) => e.lots.includes(lot.id))) {
|
|
@@ -515,6 +522,7 @@ function dispatch(argv, io) {
|
|
|
515
522
|
plan.note(rest[0], rest.slice(1).join(' '), today);
|
|
516
523
|
plan.save();
|
|
517
524
|
warnIntegrating(root, io.err);
|
|
525
|
+
touchedLot(plan, rest[0], values.verbose, io);
|
|
518
526
|
return 0;
|
|
519
527
|
}
|
|
520
528
|
case 'did': {
|
|
@@ -551,6 +559,7 @@ function dispatch(argv, io) {
|
|
|
551
559
|
if (holding)
|
|
552
560
|
throw new RafError(`move : orchestration en cours (vague ${holding.wave}, pid ${holding.pid}) — attendre que les lots de ce dépôt soient finis (le verrou tombe dès que tous sont prêts ou rendus), puis relancer`);
|
|
553
561
|
plan.save();
|
|
562
|
+
touchedLot(plan, rest[0], values.verbose, io);
|
|
554
563
|
return 0;
|
|
555
564
|
}
|
|
556
565
|
case 'public': {
|
|
@@ -562,6 +571,7 @@ function dispatch(argv, io) {
|
|
|
562
571
|
refuseLongTitle(rest.slice(1).join(' '));
|
|
563
572
|
plan.setPublic(rest[0], values.clear ? null : rest.slice(1).join(' '));
|
|
564
573
|
plan.save();
|
|
574
|
+
touchedLot(plan, rest[0], values.verbose, io);
|
|
565
575
|
return 0;
|
|
566
576
|
}
|
|
567
577
|
case 'screen': {
|
|
@@ -600,7 +610,7 @@ function dispatch(argv, io) {
|
|
|
600
610
|
}
|
|
601
611
|
case 'ux':
|
|
602
612
|
case 'review':
|
|
603
|
-
return gate(command, rest, loadPlan, root, today, io);
|
|
613
|
+
return gate(command, rest, loadPlan, root, today, io, !!values.verbose);
|
|
604
614
|
case 'commits': {
|
|
605
615
|
need(1, 'commits <lot>');
|
|
606
616
|
const plan = loadPlan();
|
|
@@ -749,7 +759,7 @@ function dispatch(argv, io) {
|
|
|
749
759
|
return 0;
|
|
750
760
|
}
|
|
751
761
|
if (rest[0] === 'post-commit')
|
|
752
|
-
return postCommit(existsSync(planPath) ? loadPlan : null, newsDir, root, today, io, readHookConfig(configPath).autostart);
|
|
762
|
+
return postCommit(existsSync(planPath) ? loadPlan : null, newsDir, root, today, io, readHookConfig(configPath).autostart, !!values.verbose);
|
|
753
763
|
if (rest[0] === 'pre-commit')
|
|
754
764
|
return preCommit(existsSync(planPath) ? loadPlan : null, readHookConfig(configPath).autostart, root, today, io);
|
|
755
765
|
if (rest[0] === 'pre-push')
|
|
@@ -854,7 +864,7 @@ const GATES = {
|
|
|
854
864
|
review: { on: 'revue de code obligatoire pour les lots à commits', already: 'revue de code déjà active' },
|
|
855
865
|
};
|
|
856
866
|
/** « raf ux … » et « raf review … » ont la même forme : activer la porte, ou noter le verdict d'un lot. */
|
|
857
|
-
function gate(kind, rest, loadPlan, root, today, io) {
|
|
867
|
+
function gate(kind, rest, loadPlan, root, today, io, verbose) {
|
|
858
868
|
if (rest.length < 1)
|
|
859
869
|
throw new RafError(`usage : raf ${kind} enable | ${kind} <lot> "verdict"`);
|
|
860
870
|
const plan = loadPlan();
|
|
@@ -879,8 +889,19 @@ function gate(kind, rest, loadPlan, root, today, io) {
|
|
|
879
889
|
plan.recordReview(rest[0], verdict, today, plan.readonly ? null : (lotWork(plan, root, rest[0])[0]?.sha ?? null), neighbours?.repos.length ? repoShas(plan, neighbours.repos, rest[0]) : undefined);
|
|
880
890
|
}
|
|
881
891
|
plan.save();
|
|
892
|
+
touchedLot(plan, rest[0], verbose, io);
|
|
882
893
|
return 0;
|
|
883
894
|
}
|
|
895
|
+
/** Un titre de lot n'est jamais rendu en entier par défaut (L453) : ~300 caractères répétés à chaque commande, chaque commit de plan. */
|
|
896
|
+
function shortTitle(title, verbose) {
|
|
897
|
+
return verbose ? title : truncate(title, TITLE_MAX);
|
|
898
|
+
}
|
|
899
|
+
/** `--verbose` (L453) : la ligne `id état titre entier` du lot touché par `done`, `ux`, `review`, `note`, `move`, `public` ; sans lui, rien de plus (une sous-tâche n'a pas de ligne). */
|
|
900
|
+
function touchedLot(plan, id, verbose, io) {
|
|
901
|
+
const lot = verbose ? plan.lots().find((l) => l.id === id) : undefined;
|
|
902
|
+
if (lot)
|
|
903
|
+
io.out(`${lot.id} ${lot.status} ${lot.title}`);
|
|
904
|
+
}
|
|
884
905
|
function knownLot(plan, id, subtask) {
|
|
885
906
|
const lot = plan.lots().find((l) => l.id === id);
|
|
886
907
|
if (!lot)
|
|
@@ -1021,7 +1042,7 @@ function preCommit(load, mode, root, today, io) {
|
|
|
1021
1042
|
}
|
|
1022
1043
|
return 0;
|
|
1023
1044
|
}
|
|
1024
|
-
function postCommit(load, newsDir, root, today, io, mode) {
|
|
1045
|
+
function postCommit(load, newsDir, root, today, io, mode, verbose) {
|
|
1025
1046
|
if (!load)
|
|
1026
1047
|
return 0;
|
|
1027
1048
|
try {
|
|
@@ -1050,7 +1071,7 @@ function postCommit(load, newsDir, root, today, io, mode) {
|
|
|
1050
1071
|
else if (lot.status === 'todo' && !planning)
|
|
1051
1072
|
io.err(`raf: ${r.lot} est encore todo${plan.readonly ? '' : ` — raf start ${r.lot}`}`);
|
|
1052
1073
|
else
|
|
1053
|
-
io.err(`raf: ${r.lot} (${lot.status}) ${lot.title}`);
|
|
1074
|
+
io.err(`raf: ${r.lot} (${lot.status}) ${shortTitle(lot.title, verbose)}`);
|
|
1054
1075
|
}
|
|
1055
1076
|
const entries = loadEntries(newsDir);
|
|
1056
1077
|
for (const id of new Set(refs.map((r) => r.lot))) {
|
|
@@ -1445,6 +1466,23 @@ function specReseal(args, reason, porteur, ctx, io) {
|
|
|
1445
1466
|
const HOLD_TTL_MINUTES = 60;
|
|
1446
1467
|
/** Longueur maximale de l'étiquette encodée : un nom de fichier tient en 255 octets, `.json.tmp` en prend 9. */
|
|
1447
1468
|
const HOLD_LABEL_MAX = 200;
|
|
1469
|
+
/** `cadence lead priority <projet> --before|--after <projet> [dossier]` (L449) : range un projet dans la liste `priority:` du cadence.yaml du dossier (celui du tour), sans modèle ni commit. */
|
|
1470
|
+
function leadPriority(rest, before, after, depth, io) {
|
|
1471
|
+
const usage = 'usage : cadence lead priority <projet> --before|--after <projet> [dossier] [--depth n]';
|
|
1472
|
+
if (rest.length < 2 || rest.length > 3 || (before === undefined) === (after === undefined))
|
|
1473
|
+
throw new RafError(usage);
|
|
1474
|
+
const place = before !== undefined ? 'before' : 'after';
|
|
1475
|
+
const levels = depth === undefined ? undefined : Number(depth);
|
|
1476
|
+
if (levels !== undefined && !(Number.isInteger(levels) && levels >= 1))
|
|
1477
|
+
throw new RafError(`--depth invalide : ${depth} (un entier ≥ 1)`);
|
|
1478
|
+
const parent = resolve(io.cwd, rest[2] ?? '.');
|
|
1479
|
+
if (!existsSync(parent))
|
|
1480
|
+
throw new RafError(`lead priority : dossier introuvable : ${parent}`);
|
|
1481
|
+
const target = (before ?? after);
|
|
1482
|
+
const moved = movePriority(parent, rest[1], place, target, levels);
|
|
1483
|
+
io.out(moved.changed ? `${rest[1]} placé ${place === 'before' ? 'avant' : 'après'} ${target} dans priority: de ${join(parent, 'cadence.yaml')} (ordre : ${moved.order.join(' > ')}) — le fichier est modifié, à commiter` : `${rest[1]} est déjà ${place === 'before' ? 'avant' : 'après'} ${target} : rien à changer`);
|
|
1484
|
+
return 0;
|
|
1485
|
+
}
|
|
1448
1486
|
/** `cadence lead hold <étiquette> [--ttl minutes]` / `lead release <étiquette>` (L255) : inscrit, ou rend, une session lancée à la main par le lead, qui compte dans les créneaux libres. */
|
|
1449
1487
|
function leadHold(rest, ttl, io, today) {
|
|
1450
1488
|
const [verb, label] = rest;
|
|
@@ -1702,12 +1740,14 @@ async function sessionAll(rest, values, io) {
|
|
|
1702
1740
|
}
|
|
1703
1741
|
return worst;
|
|
1704
1742
|
}
|
|
1705
|
-
/** Le contexte de la session de `--session <id>`, sinon de `CLAUDE_CODE_SESSION_ID`, sinon (variable absente) de la seule session du dossier courant, tel que la bande cadence-hud le publie ; illisible = code 2, à lire comme « au seuil ». */
|
|
1743
|
+
/** Le contexte (et sa position par rapport à `lead.contextThreshold`, en tokens) de la session de `--session <id>`, sinon de `CLAUDE_CODE_SESSION_ID`, sinon (variable absente) de la seule session du dossier courant, tel que la bande cadence-hud le publie ; illisible = code 2, à lire comme « au seuil ». */
|
|
1706
1744
|
function sessionContext(io, session) {
|
|
1707
1745
|
const r = readHudContext(io.env, io.now().getTime(), { cwd: io.cwd, session });
|
|
1708
1746
|
if (!r.ok)
|
|
1709
1747
|
throw new RafError(`session context : contexte illisible — ${r.reason} ; à traiter comme au seuil`);
|
|
1710
1748
|
io.out(`ctx ${Math.round(r.ctx.percent)} % (${r.ctx.tokens}/${r.ctx.window})`);
|
|
1749
|
+
const threshold = readLeadContextThreshold(io.cwd);
|
|
1750
|
+
io.out(`seuil ${threshold} tokens : ${r.ctx.tokens >= threshold ? 'atteint' : 'sous le seuil'}`);
|
|
1711
1751
|
return 0;
|
|
1712
1752
|
}
|
|
1713
1753
|
function session([sub, ...args], ctx, values) {
|
package/dist/config-set.js
CHANGED
|
@@ -6,7 +6,7 @@ import { isNode, isScalar, parse, parseDocument } from 'yaml';
|
|
|
6
6
|
import { defaultPlan, readDocsConfig, readHookConfig, readMigrateConfig, readNewsConfig, readOrchestrateConfig, readPerfConfig, readPlanConfig, readReferencesConfig, readSessionConfig } from './config.js';
|
|
7
7
|
import { parseDeliverConfig } from './deliver.js';
|
|
8
8
|
import { commitPaths, dirtyPaths, gitRoot, restorePaths } from './git.js';
|
|
9
|
-
import { findProjects, isProject, readDepth, readLeadUx } from './lead.js';
|
|
9
|
+
import { findProjects, isProject, readDepth, readLeadContextThreshold, readLeadUx } from './lead.js';
|
|
10
10
|
import { readPriority } from './orchestrate/continue.js';
|
|
11
11
|
import { Plan, RafError } from './plan.js';
|
|
12
12
|
const KEY = /^[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)*$/;
|
|
@@ -26,6 +26,7 @@ export function validateConfig(file, root) {
|
|
|
26
26
|
readReferencesConfig(file);
|
|
27
27
|
readMigrateConfig(file);
|
|
28
28
|
readLeadUx(dirname(file));
|
|
29
|
+
readLeadContextThreshold(dirname(file));
|
|
29
30
|
readDepth(dirname(file));
|
|
30
31
|
readPriority(dirname(file));
|
|
31
32
|
const text = readFileSync(file, 'utf8');
|
package/dist/config.js
CHANGED
|
@@ -338,7 +338,7 @@ export function readVerifyConfig(file) {
|
|
|
338
338
|
return { inPlace: v.inPlace };
|
|
339
339
|
}
|
|
340
340
|
const REVIEW_KEYS = ['threshold', 'light', 'full'];
|
|
341
|
-
const ORCH_KEYS = ['start', 'verdict', 'test', 'testReplay', 'build', 'worktreeDeps', 'accept', 'maquette', 'maquetteReviews', 'fiche', 'ux', 'precheck', 'prodAccess', 'worktrees', 'testGate', 'spec', 'review', 'escalation', 'continue', 'effort', 'permissionMode', 'addDirs', 'timeouts'];
|
|
341
|
+
const ORCH_KEYS = ['start', 'verdict', 'test', 'testReplay', 'build', 'worktreeDeps', 'gate', 'accept', 'maquette', 'maquetteReviews', 'fiche', 'ux', 'precheck', 'prodAccess', 'worktrees', 'testGate', 'spec', 'review', 'escalation', 'continue', 'effort', 'permissionMode', 'addDirs', 'timeouts'];
|
|
342
342
|
/** Clé `orchestrate:` de cadence.yaml. Absente : les défauts (auto, 45 min d'implémentation, 25 min de revue). */
|
|
343
343
|
export function readOrchestrateConfig(file) {
|
|
344
344
|
const config = { precheck: true, maquette: true, maquetteReviews: 3, fiche: true, precheckLocal: false, prodAccess: false, worktrees: false, spec: { dir: 'docs/specs' }, review: { threshold: 0.25, light: 'sonnet', full: 'opus' }, effort: { precheck: 'low', spec: 'medium', maquette: 'high', fiche: 'medium', accept: 'medium', implement: 'medium', fix: 'medium', review: 'high', ux: 'high' }, permissionMode: 'auto', addDirs: [], timeouts: { work: 45 * 60_000, review: 25 * 60_000 } };
|
|
@@ -392,6 +392,21 @@ export function readOrchestrateConfig(file) {
|
|
|
392
392
|
throw bad('worktreeDeps : liste de chemins relatifs au dépôt, sans « .. » (node_modules, .venv…)');
|
|
393
393
|
config.worktreeDeps = deps.map((x) => x.trim().replace(/\/$/, ''));
|
|
394
394
|
}
|
|
395
|
+
if (o.gate != null) {
|
|
396
|
+
if (!isObject(o.gate))
|
|
397
|
+
throw bad('gate : objet attendu (gate: { readsPlan: true })');
|
|
398
|
+
for (const k of Object.keys(o.gate))
|
|
399
|
+
if (k !== 'readsPlan')
|
|
400
|
+
throw bad(`gate.${k} inconnu (attendu : readsPlan)`);
|
|
401
|
+
const r = o.gate.readsPlan;
|
|
402
|
+
const msg = 'gate.readsPlan : true, false ou liste de chemins de tests relatifs au dépôt, sans « .. »';
|
|
403
|
+
if (r === true)
|
|
404
|
+
config.gate = { readsPlan: true };
|
|
405
|
+
else if (Array.isArray(r) && r.length && r.every((x) => typeof x === 'string' && x.trim() && !isAbsolute(x) && !x.split(/[\\/]/).includes('..')))
|
|
406
|
+
config.gate = { readsPlan: r.map((x) => x.trim()) };
|
|
407
|
+
else if (r !== false)
|
|
408
|
+
throw bad(msg);
|
|
409
|
+
}
|
|
395
410
|
if (o.maquette != null) {
|
|
396
411
|
if (typeof o.maquette !== 'boolean')
|
|
397
412
|
throw bad('maquette : true ou false attendu');
|
package/dist/lead.js
CHANGED
|
@@ -16,11 +16,11 @@ import { cadenceHome } from './orchestrate/registry.js';
|
|
|
16
16
|
import { staleNotes, staleText } from './stale-notes.js';
|
|
17
17
|
import { noTestGate, noTestText } from './orchestrate/no-test.js';
|
|
18
18
|
import { KEPT_TITLE, keptLine, keptTrees } from './orchestrate/worktree.js';
|
|
19
|
-
/**
|
|
20
|
-
|
|
19
|
+
/** Le bloc `lead:` du cadence.yaml du dossier parent (absent = `undefined`) ; un `lead` qui n'est pas une table est refusé en nommant `key`. */
|
|
20
|
+
function readLeadBlock(parent, key, expected) {
|
|
21
21
|
const file = join(parent, 'cadence.yaml');
|
|
22
22
|
if (!existsSync(file))
|
|
23
|
-
return
|
|
23
|
+
return undefined;
|
|
24
24
|
let raw;
|
|
25
25
|
try {
|
|
26
26
|
raw = parse(readFileSync(file, 'utf8'));
|
|
@@ -30,16 +30,33 @@ export function readLeadUx(parent) {
|
|
|
30
30
|
}
|
|
31
31
|
const l = raw?.lead;
|
|
32
32
|
if (l == null)
|
|
33
|
-
return
|
|
34
|
-
const ux = l.ux;
|
|
33
|
+
return undefined;
|
|
35
34
|
if (typeof l !== 'object' || Array.isArray(l))
|
|
36
|
-
throw new RafError(`${file} :
|
|
35
|
+
throw new RafError(`${file} : ${key} doit être ${expected}`);
|
|
36
|
+
return { file, block: l };
|
|
37
|
+
}
|
|
38
|
+
/** Clé `lead.ux` du cadence.yaml du dossier parent (L197) : qui pose le verdict UX (`raf ux`) d'un lot visible. `human` (défaut) : l'humain ; `lead` : le lead, l'humain n'étant consulté qu'en cas de doute. */
|
|
39
|
+
export function readLeadUx(parent) {
|
|
40
|
+
const found = readLeadBlock(parent, 'lead.ux', '« lead » ou « human »');
|
|
41
|
+
const ux = found?.block.ux;
|
|
37
42
|
if (ux == null)
|
|
38
43
|
return 'human';
|
|
39
44
|
if (ux !== 'lead' && ux !== 'human')
|
|
40
|
-
throw new RafError(`${file} : lead.ux doit être « lead » ou « human »`);
|
|
45
|
+
throw new RafError(`${found.file} : lead.ux doit être « lead » ou « human »`);
|
|
41
46
|
return ux;
|
|
42
47
|
}
|
|
48
|
+
/** Seuil de contexte du lead quand `lead.contextThreshold` n'est pas posé (L437). */
|
|
49
|
+
export const CONTEXT_THRESHOLD_DEFAULT = 150_000;
|
|
50
|
+
/** Clé `lead.contextThreshold` du cadence.yaml du dossier (L437) : le nombre de tokens de contexte à partir duquel le lead ne lance plus de vague. En tokens et non en % : 60 % d'une fenêtre de 1M valait 600k. Un entier ≥ 1, sinon refus. */
|
|
51
|
+
export function readLeadContextThreshold(parent) {
|
|
52
|
+
const found = readLeadBlock(parent, 'lead.contextThreshold', 'un entier ≥ 1 (tokens)');
|
|
53
|
+
const t = found?.block.contextThreshold;
|
|
54
|
+
if (t == null)
|
|
55
|
+
return CONTEXT_THRESHOLD_DEFAULT;
|
|
56
|
+
if (typeof t !== 'number' || !Number.isInteger(t) || t < 1)
|
|
57
|
+
throw new RafError(`${found.file} : lead.contextThreshold doit être un entier ≥ 1 (tokens), pas ${JSON.stringify(t)}`);
|
|
58
|
+
return t;
|
|
59
|
+
}
|
|
43
60
|
/** Clé `depth:` du cadence.yaml du dossier parent (L201, issue #8), à côté de `priority:` : combien de niveaux `lead tour` parcourt (1 : sous-dossiers directs, le défaut). */
|
|
44
61
|
export function readDepth(parent) {
|
|
45
62
|
const file = join(parent, 'cadence.yaml');
|
|
@@ -6,7 +6,7 @@ import { delimiter, dirname, isAbsolute, join, posix, relative, resolve } from '
|
|
|
6
6
|
import { copyFileSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { tmpdir } from 'node:os';
|
|
8
8
|
import { coveredTasks, lotCommits, lotWork } from '../audit.js';
|
|
9
|
-
import { readDocsConfig, readPlanConfig } from '../config.js';
|
|
9
|
+
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';
|
|
@@ -2318,15 +2318,42 @@ async function work(c, kind) {
|
|
|
2318
2318
|
const noCommitNote = (l) => (l.warnings.some((w) => w.startsWith('passe des mineurs sans commit')) ? ' sans commit' : '');
|
|
2319
2319
|
/** Revue de code qui clôt le cycle : complète, ou courte une fois la passe des mineurs jouée. */
|
|
2320
2320
|
const codeReview = (l) => (l.minorPass ? 'review-small' : 'review');
|
|
2321
|
+
/** Note d'un lot que sa session a rendu `visible` après le lancement de la vague (L454) : son verdict UX n'existe pas encore, le lead le sait. */
|
|
2322
|
+
const VISIBLE_ADDED = 'visible ajouté en cours de vague, étape UX à jouer';
|
|
2323
|
+
/**
|
|
2324
|
+
* Relit `visible` dans le plan du worktree après une session d'écriture (L454) : un lot que sa session a rendu visible (warhammer L37 et L43,
|
|
2325
|
+
* 30-10) est lancé non visible, et sortait « prêt » sans étape UX. Il le devient dans l'état du lot, donc l'UX est jouée (`uxAfterReview`, ou la
|
|
2326
|
+
* revue courte d'un petit lot) ; sans application déclarée, il est rendu avec la note. Un plan illisible, ou un lot qui n'est plus visible
|
|
2327
|
+
* dans le plan, laisse l'état tel quel (retirer `visible` en cours de vague n'est pas traité).
|
|
2328
|
+
*/
|
|
2329
|
+
function addedVisible(c) {
|
|
2330
|
+
const l = c.lot;
|
|
2331
|
+
if (l.visible)
|
|
2332
|
+
return;
|
|
2333
|
+
let now;
|
|
2334
|
+
try {
|
|
2335
|
+
now = (wd(c) === l.repo ? c.loadPlan() : planLoader(wd(c))()).lot(l.lot).visible;
|
|
2336
|
+
}
|
|
2337
|
+
catch {
|
|
2338
|
+
return;
|
|
2339
|
+
}
|
|
2340
|
+
if (!now)
|
|
2341
|
+
return;
|
|
2342
|
+
l.visible = true;
|
|
2343
|
+
if (!c.config.ux)
|
|
2344
|
+
l.uxNote = `${VISIBLE_ADDED} (aucune application déclarée : cadence.yaml orchestrate.ux)`;
|
|
2345
|
+
c.wave.log(`${lotKey(l.project, l.lot)} · ${VISIBLE_ADDED}`);
|
|
2346
|
+
}
|
|
2321
2347
|
/**
|
|
2322
2348
|
* Première étape après un travail d'écriture (L221) : la revue de code, jamais l'UX. La revue UX d'un lot visible est jouée une seule fois,
|
|
2323
2349
|
* après la dernière revue de code conforme (`uxAfterReview`), sur le code qui sera livré.
|
|
2324
2350
|
*/
|
|
2325
2351
|
function firstReview(c) {
|
|
2326
2352
|
const l = c.lot;
|
|
2353
|
+
addedVisible(c);
|
|
2327
2354
|
if (l.small)
|
|
2328
2355
|
return l.visible || l.minorPass ? 'review-small' : 'review'; // passe unique code + ergonomie ; sans écran, une revue de code
|
|
2329
|
-
if (l.visible && !c.config.ux)
|
|
2356
|
+
if (l.visible && !c.config.ux && !l.uxNote?.startsWith(VISIBLE_ADDED))
|
|
2330
2357
|
l.uxNote = 'UX à faire par le lead (aucune application déclarée : cadence.yaml orchestrate.ux)';
|
|
2331
2358
|
return codeReview(l);
|
|
2332
2359
|
}
|
package/dist/priority.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { basename, join, relative, sep } from 'node:path';
|
|
3
|
+
import { Document, isScalar, isSeq, parseDocument } from 'yaml';
|
|
4
|
+
import { findProjects, readDepth } from './lead.js';
|
|
5
|
+
import { priorityRank, readPriority } from './orchestrate/continue.js';
|
|
6
|
+
import { RafError } from './plan.js';
|
|
7
|
+
import { setKey } from './config-set.js';
|
|
8
|
+
/**
|
|
9
|
+
* `cadence lead priority <projet> --before|--after <projet>` (L449) : place `project` juste avant ou après `target` dans l'ordre des projets du dossier,
|
|
10
|
+
* en réécrivant la liste `priority:` de son cadence.yaml (créé s'il manque). Les projets sont nommés comme `lead tour` les nomme.
|
|
11
|
+
* La liste écrite nomme tous les projets du dossier, dans l'ordre affiché : un nom qui en couvrait plusieurs par préfixe (`maritime` pour `maritime-atlas`) devient explicite
|
|
12
|
+
* (le commentaire de sa ligne passe sur le premier projet qu'il couvrait), et un nom qui ne couvre aucun projet reste en fin de liste. Le reste du fichier, commentaires compris, est gardé (setKey). Un fichier illisible, ou une clé qui n'est pas une liste, est refusé sans rien écrire.
|
|
13
|
+
*/
|
|
14
|
+
export function movePriority(parent, project, place, target, depth) {
|
|
15
|
+
const file = join(parent, 'cadence.yaml');
|
|
16
|
+
const declared = readPriority(parent); // refuse un yaml illisible ou une clé qui n'est pas une liste de noms
|
|
17
|
+
const projects = findProjects(parent, depth ?? readDepth(parent)).map((dir) => ({ key: basename(dir), name: relative(parent, dir).split(sep).join('/') }));
|
|
18
|
+
const byName = new Map(projects.map((p) => [p.name, p]));
|
|
19
|
+
for (const n of [project, target])
|
|
20
|
+
if (!byName.has(n))
|
|
21
|
+
throw new RafError(`projet inconnu : ${n} (projets de ${parent} : ${projects.map((p) => p.name).join(', ') || 'aucun'})`);
|
|
22
|
+
if (project === target)
|
|
23
|
+
throw new RafError(`${project} : le même projet des deux côtés`);
|
|
24
|
+
const keys = projects.map((p) => p.key);
|
|
25
|
+
const twice = keys.find((k, i) => keys.indexOf(k) !== i);
|
|
26
|
+
if (twice)
|
|
27
|
+
throw new RafError(`deux projets s'appellent ${twice} : la liste priority: les nomme par leur dossier et ne peut pas les distinguer`);
|
|
28
|
+
const ranked = [...projects].sort((a, b) => priorityRank(a.key, declared) - priorityRank(b.key, declared) || a.key.localeCompare(b.key) || a.name.localeCompare(b.name));
|
|
29
|
+
const without = ranked.filter((p) => p.name !== project);
|
|
30
|
+
const at = without.findIndex((p) => p.name === target) + (place === 'after' ? 1 : 0);
|
|
31
|
+
const moved = [...without.slice(0, at), byName.get(project), ...without.slice(at)];
|
|
32
|
+
const order = moved.map((p) => p.name);
|
|
33
|
+
if (order.every((n, i) => n === ranked[i].name))
|
|
34
|
+
return { order, changed: false };
|
|
35
|
+
const unmatched = declared.filter((n) => !keys.some((k) => k === n || k.startsWith(`${n}-`)));
|
|
36
|
+
const list = [...moved.map((p) => p.key), ...unmatched];
|
|
37
|
+
// la liste écrite doit restituer l'ordre voulu : un nom qui en préfixe un autre (`shop` et `shop-web`) les rangerait ensemble
|
|
38
|
+
const back = [...projects].sort((a, b) => priorityRank(a.key, list) - priorityRank(b.key, list) || a.key.localeCompare(b.key) || a.name.localeCompare(b.name)).map((p) => p.name);
|
|
39
|
+
if (back.some((n, i) => n !== order[i])) {
|
|
40
|
+
const clash = keys.find((k) => keys.some((o) => o !== k && o.startsWith(`${k}-`)));
|
|
41
|
+
throw new RafError(`${project} ne peut pas être placé ${place === 'before' ? 'avant' : 'après'} ${target} : la liste priority: nomme un projet par son dossier, et ${clash ?? 'un nom'} est le début du nom d'un autre projet (« ${clash ?? 'x'}- »), qui le suit toujours — rien n'est écrit`);
|
|
42
|
+
}
|
|
43
|
+
const text = existsSync(file) ? readFileSync(file, 'utf8') : '';
|
|
44
|
+
const old = parseDocument(text).get('priority', true);
|
|
45
|
+
// les noms déjà dans la liste gardent leur nœud, donc le commentaire de leur ligne ; un nom qui couvrait des projets par préfixe (`maritime`)
|
|
46
|
+
// donne le sien au premier projet qu'il couvre, dans l'ordre écrit, sous le nom explicite ; les autres sont des nœuds neufs
|
|
47
|
+
const kept = new Map();
|
|
48
|
+
const scalars = isSeq(old) ? old.items.filter((item) => isScalar(item) && typeof item.value === 'string') : [];
|
|
49
|
+
for (const item of scalars)
|
|
50
|
+
if (!kept.has(item.value.trim()))
|
|
51
|
+
kept.set(item.value.trim(), item); // deux fois le même nom : le premier nœud compte, comme pour le rang
|
|
52
|
+
for (const item of scalars) {
|
|
53
|
+
const n = item.value.trim();
|
|
54
|
+
if (kept.get(n) !== item || keys.includes(n))
|
|
55
|
+
continue; // un nom qui est déjà un projet garde son nœud et n'en donne pas une copie à `nom-…`
|
|
56
|
+
const covered = list.find((k) => !kept.has(k) && k.startsWith(`${n}-`));
|
|
57
|
+
if (covered === undefined)
|
|
58
|
+
continue;
|
|
59
|
+
const explicit = item.clone();
|
|
60
|
+
explicit.value = covered;
|
|
61
|
+
kept.set(covered, explicit);
|
|
62
|
+
}
|
|
63
|
+
const value = new Document(list);
|
|
64
|
+
if (isSeq(value.contents)) {
|
|
65
|
+
value.contents.items = list.map((n) => kept.get(n) ?? value.createNode(n));
|
|
66
|
+
value.contents.flow = isSeq(old) && old.flow === true; // la liste en ligne reste en ligne
|
|
67
|
+
// la séquence porte le commentaire de la ligne de la clé et celui écrit avant son premier nom : la nouvelle les reprend
|
|
68
|
+
if (isSeq(old)) {
|
|
69
|
+
value.contents.commentBefore = old.commentBefore;
|
|
70
|
+
value.contents.comment = old.comment;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const next = setKey(text, 'priority', value);
|
|
74
|
+
if (next !== null)
|
|
75
|
+
writeFileSync(file, next);
|
|
76
|
+
return { order, changed: next !== null };
|
|
77
|
+
}
|
package/dist/verify-gate.js
CHANGED
|
@@ -127,7 +127,8 @@ async function playGate(ctx) {
|
|
|
127
127
|
}
|
|
128
128
|
let recorded = { reason: 'aucune porte de vague enregistrée pour ce sha' };
|
|
129
129
|
try {
|
|
130
|
-
|
|
130
|
+
// Un test qui lit le plan (orchestrate.gate.readsPlan, L450) voit un autre contenu après un `raf done` : les commits de plan seuls ne sont pas traversés.
|
|
131
|
+
recorded = recordedGate(ctx.launchDirs ?? [], repo, sha, steps, config.gate ? undefined : ctx.planFile);
|
|
131
132
|
}
|
|
132
133
|
catch (e) {
|
|
133
134
|
recorded = { reason: `état des vagues illisible (${gitFailure(e)})` };
|
|
@@ -143,7 +144,7 @@ async function playGate(ctx) {
|
|
|
143
144
|
out(`verify --gate ${short} : vert`);
|
|
144
145
|
return 0;
|
|
145
146
|
}
|
|
146
|
-
const replayReason = `verdict rejoué : ${recorded.reason}`;
|
|
147
|
+
const replayReason = `verdict rejoué : ${recorded.reason}${config.gate && ctx.planFile ? ' (orchestrate.gate.readsPlan : un test lit le plan, les commits de plan seuls ne reprennent pas le verdict d\'un sha dessous)' : ''}`;
|
|
147
148
|
const obstacle = mainTreeObstacle(repo, sha);
|
|
148
149
|
const mode = ctx.verify?.inPlace;
|
|
149
150
|
if (mode === true && obstacle) {
|
package/docs/cadence-hud.md
CHANGED
|
@@ -81,6 +81,8 @@ A project shows at most 5 lots (L411), the first ones in the order above (so tho
|
|
|
81
81
|
|
|
82
82
|
**Reordering (L402).** Drag a lot onto another lot of the same project and of the same display group (or a sub-task onto another sub-task of its lot, same group): press on the row, move with the button held, release. While you drag, the row you started from shows `●` and the row under the pointer shows where it will land, `⇡` (before it) or `⇣` (after it), or a red `⊘` when it belongs to another parent or to another display group (L410) and the drop will be refused. The keyboard does the same wherever the panel is drawn by a `Client` region (terminal and desktop app): click a row (or press ↑ / ↓ once the panel has the focus), then **Alt**+↑ / **Alt**+↓ moves the selected lot before its previous sibling / after its next one. Only the rows the panel shows take part (L411): a lot hidden behind `+ N lots`, or the sub-lots of a closed lot, are not rows, so they are never a target — Alt+↓ on the fifth lot, the last row before the `+ N lots` button, moves nothing, like Alt+↓ on the last row of a project; open the rest (or the lot) first, and the drop runs `raf move` as usual. The `+ N lots` / `moins` button sits outside the `Client` region, after the project's rows. The drop runs `raf move <id> --before|--after <id>` ([raf move](raf.md)) in the project folder, then the panel reads the plans again and says, under the title, `✔ L13 placé avant L10 — le plan est modifié, à commiter (raf move ne commite pas)`: the panel never commits, the plan file is yours to commit. Everything is relative to what the panel shows, in the L410 order, not to the order of the plan: the row you drop on, and the "previous" and "next" sibling of Alt+↑ / Alt+↓, are the ones displayed around the lot, and `raf move` is given that lot as its target. **A lot is reordered only inside its display group** (L410: in progress, free, blocked): dropping a lot on a lot of another group is refused, `raf` is not called and nothing moves. While you drag, the row under the pointer shows the red `⊘` (as for another parent), and on release the panel says why, by group: `✖ L411 est en cours : un lot libre se range parmi les libres` (the words: `en cours` / `libre` / `bloqué`; the lot is named by what it is, `un lot` or `une sous-tâche`). A parent with an in-progress sub-lot is shown with the lots in progress, so it counts as one of them. Alt+↑ / Alt+↓ toward a displayed neighbour of another group gets the same refusal; at the edge of the displayed rows (or before the `+ N lots` button) nothing happens. (Case that led to this: plan L402 L403 L407 L411, L403 free dropped on L411 in progress moved L403 below L407 and hid it behind `+ 11 lots`.) Inside a group the screen follows the plan, so a gesture that goes through always shows: the lot moves where you dropped it. A drop that would leave the order of the group as it is — the lot is already right before (or after) the target among the lots of its group — runs nothing and says `• L10 est déjà avant L13 dans le plan : rien à déplacer`; no gesture of the panel leads there (the screen follows the plan inside a group), the check guards a request the drawing did not make. The same holds between the sub-tasks of a lot. The `déjà` check is made on the table the panel last read: from a successful `raf move` until the end of a re-reading started *after* it (`cadence lead tour` takes several seconds; a reading already in flight read the old plan, so another one follows it) the table is held stale: no `déjà` short-circuit, `raf move` runs and decides (the group refusal stays: it depends on the states, not on the order). A refusal is written under the title with its reason and nothing moves: a wave holds the repository (`move : orchestration en cours …`), a plan kept by another tool (`plan en lecture seule : … — le modifier avec l'outil du projet`, the message does not name that tool's command: `raf` does not know it, so look in the project), an archived lot, different parents (`L402 et L401/2 n'ont pas le même parent …`) or different display groups (`L411 est en cours : …`), the last two checked before `raf` runs; `raf` missing from the `PATH` says so. On a surface with no `Client` region (mobile, editor) the panel stays read-only (`lecture seule` in its title).
|
|
83
83
|
|
|
84
|
+
**Reordering the projects (L449).** Above the tree, `ordre des projets (priority:) — glisser, ou Alt+↑ / Alt+↓` and one row per project (`cadence 7 lots non clos`; `illisible` for a project the tour could not read), in the order of `priority:`. It is drawn only where the lots can be reordered (a `Client` region: terminal and desktop app), when the folder has two projects or more, and when the panel is tall enough to keep a row for each project and one lot under it (otherwise the tree has the room). Drag a project onto another (`●` the row you hold, `⇡` / `⇣` where it will land) or click it and press **Alt**+↑ / **Alt**+↓ (↑ / ↓ alone select): the drop runs `cadence lead priority <project> --before|--after <project> <folder>` ([lead priority](lead.md#lead-priority)), which rewrites the `priority:` list of the `cadence.yaml` of the parent folder, comments kept; the panel then reads the plans again and says under the title `✔ ol placé avant cadence — priority: du cadence.yaml est modifiée, à commiter`. This order is the one of the tour, of `cadence orchestrate --continue` and of the panel itself. The panel never commits. A refusal is written under the title with its reason (`✖ /dev/cadence.yaml illisible : …`, a `priority:` that is not a list) and nothing is written; `cadence` missing from the `PATH` says so; a project already at that place says `• … est déjà avant …`. The error row `cadence.yaml` that the tour adds for an unreadable file is not a project and cannot be moved or targeted.
|
|
85
|
+
|
|
84
86
|
States: `▶ en cours` (doing), `○ libre` (todo, no open `after:`), `⊘ bloqué` (todo, an `after:` is still open); a lot in a wave takes the wave's state and words, as the band writes them (`… en attente`, `⟳ implémente`, `⟳ relit`, `⟳ corrige`, `? QUESTION`, `✔ prêt`, `↩ rendu`, `✖ échec`, `⏸ suspendu`). In a native plan the sub-tasks of an open lot (`L401/2`) are its sub-lots (`├`/`└`); they only read `▶ en cours` or `○ à faire`, and are not counted in the project's `N lots non clos`. In a plan kept by another tool the sub-lots come from `parent:`. An `after:` id that is closed or unknown is struck through and blocks nothing. A project the tour could not read shows `illisible : <error>`; the others stay.
|
|
85
87
|
|
|
86
88
|
Before the first reading the panel says `Lecture des plans…`; if `cadence lead tour` does not answer it says `Lots illisibles : cadence lead tour n'a pas répondu. Nouvelle lecture dans une minute.` (a failed re-reading keeps the last table); a `cadence` too old to give the `lots` field says `Lots indisponibles : cadence est trop ancien, mettez-le à jour.`; with no open lot, `Aucun lot non clos dans les projets de ce dossier.`. A tree taller than the panel ends with `… +N lots de plus`. Under 60 columns the `after:` list moves to its own line and the title is cut with `…`, never the id or the state.
|
package/docs/deliver.md
CHANGED
|
@@ -308,6 +308,8 @@ verify --gate 6b0d9aa : vert
|
|
|
308
308
|
|
|
309
309
|
Nothing runs: no worktree, no copy of the dependencies, exit 0. Otherwise the gate is **replayed** exactly as described above, and the line right after the one telling where it ran gives the reason: `verdict rejoué : aucune porte de vague enregistrée pour ce sha` (no wave state, another sha such as the head after an integration that rebased, a commit that also touches another file (a plan-only commit is crossed, L444), another repository), `arbre différent de celui contrôlé`, `arbre non enregistré` (state written before L397), or `l'enregistrement (vague …, lot …) ne couvre pas en vert test (…)` (a red, missing or changed step). A recorded red is never repeated as a verdict: it is replayed. The verdict read is the one of the wave's gate (with `orchestrate.testReplay` applied), not a new run: to force a replay, ask for a sha the wave did not check.
|
|
310
310
|
|
|
311
|
+
**A test that reads the plan (L450).** Crossing the plan-only commits assumes that no test reads the plan. On 30-10 avatar-pandora's `plan-page.e2e`, which reads `.cadence/raf.yaml`, went red after the `raf done` of L8 while the head gate, taking over the wave's verdict under that commit, saw nothing. A project whose gate has such a test declares it: `orchestrate.gate: { readsPlan: true }` (or `readsPlan: [e2e/plan-page.e2e.ts]`, the paths of the tests that read it; a non-empty list of relative paths without `..`, anything else is refused when `cadence.yaml` is read). The verdict of a sha *below* plan-only commits is then never taken over: the gate is replayed at the sha asked, and the reason line ends with `(orchestrate.gate.readsPlan : un test lit le plan, …)`. A verdict recorded at the very sha asked is still taken over. Absent or `false`: unchanged. The pre-push hook keeps crossing plan-only commits (its rule, L432, is not changed here).
|
|
312
|
+
|
|
311
313
|
```yaml
|
|
312
314
|
# cadence.yaml
|
|
313
315
|
verify:
|
|
@@ -50,11 +50,11 @@
|
|
|
50
50
|
<text x="920" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Delivers</text>
|
|
51
51
|
<text x="920" y="186.5" text-anchor="middle" font-size="11.5" fill="#8b949e">push + deliver</text>
|
|
52
52
|
<rect x="1000" y="146" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
53
|
-
<text x="1064" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3"><
|
|
53
|
+
<text x="1064" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">< limit ?</text>
|
|
54
54
|
<text x="1064" y="186.5" text-anchor="middle" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#8b949e">session context</text>
|
|
55
55
|
<rect x="1144" y="146" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
56
56
|
<text x="1208" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Close</text>
|
|
57
|
-
<text x="1208" y="186.5" text-anchor="middle" font-size="11.5" fill="#8b949e">if
|
|
57
|
+
<text x="1208" y="186.5" text-anchor="middle" font-size="11.5" fill="#8b949e">if limit reached</text>
|
|
58
58
|
<rect x="280" y="246" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
59
59
|
<text x="344" y="271.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Lots</text>
|
|
60
60
|
<text x="344" y="286.5" text-anchor="middle" font-size="11.5" fill="#8b949e">figure 2, per lot</text>
|
|
@@ -50,11 +50,11 @@
|
|
|
50
50
|
<text x="920" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Delivers</text>
|
|
51
51
|
<text x="920" y="186.5" text-anchor="middle" font-size="11.5" fill="#57606a">push + deliver</text>
|
|
52
52
|
<rect x="1000" y="146" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
|
|
53
|
-
<text x="1064" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328"><
|
|
53
|
+
<text x="1064" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">< limit ?</text>
|
|
54
54
|
<text x="1064" y="186.5" text-anchor="middle" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#57606a">session context</text>
|
|
55
55
|
<rect x="1144" y="146" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
|
|
56
56
|
<text x="1208" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Close</text>
|
|
57
|
-
<text x="1208" y="186.5" text-anchor="middle" font-size="11.5" fill="#57606a">if
|
|
57
|
+
<text x="1208" y="186.5" text-anchor="middle" font-size="11.5" fill="#57606a">if limit reached</text>
|
|
58
58
|
<rect x="280" y="246" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
|
|
59
59
|
<text x="344" y="271.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Lots</text>
|
|
60
60
|
<text x="344" y="286.5" text-anchor="middle" font-size="11.5" fill="#57606a">figure 2, per lot</text>
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
<text x="1140" y="703.5" text-anchor="middle">findings → the human</text>
|
|
131
131
|
</g>
|
|
132
132
|
<g id="ctx"><rect x="1241" y="337" width="158" height="66" rx="8" fill="#0c2d6b" stroke="#58a6ff" stroke-width="1.5"/>
|
|
133
|
-
<text x="1320" y="358.5" text-anchor="middle" font-weight="600">Context under
|
|
133
|
+
<text x="1320" y="358.5" text-anchor="middle" font-weight="600">Context under limit?</text>
|
|
134
134
|
<text x="1320" y="374.5" text-anchor="middle" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" font-size="11.5">session context</text>
|
|
135
135
|
<text x="1320" y="390.5" text-anchor="middle">error = at threshold</text>
|
|
136
136
|
</g>
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
<polyline points="1320,403 1320,650" fill="none" stroke="#8b949e" stroke-width="1.6" marker-end="url(#arr)"/>
|
|
176
176
|
<text x="1326" y="440" text-anchor="start" font-size="11" fill="#8b949e" class="lbl">yes: chain</text>
|
|
177
177
|
<polyline points="1399,370 1421,370" fill="none" stroke="#8b949e" stroke-width="1.6" marker-end="url(#arr)"/>
|
|
178
|
-
<text x="1410" y="329" text-anchor="middle" font-size="11" fill="#8b949e" class="lbl"
|
|
178
|
+
<text x="1410" y="329" text-anchor="middle" font-size="11" fill="#8b949e" class="lbl">limit reached: close</text>
|
|
179
179
|
<polyline points="1500,337 1500,203" fill="none" stroke="#8b949e" stroke-width="1.6" marker-end="url(#arr)"/>
|
|
180
180
|
<polyline points="1320,716 1320,746 420,746 420,716" fill="none" stroke="#8b949e" stroke-width="1.6" marker-end="url(#arr)"/>
|
|
181
181
|
<text x="870" y="740" text-anchor="middle" font-size="11" fill="#8b949e" class="lbl">next wave: back to step 2, the limits still apply</text>
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
<text x="1140" y="703.5" text-anchor="middle">findings → the human</text>
|
|
131
131
|
</g>
|
|
132
132
|
<g id="ctx"><rect x="1241" y="337" width="158" height="66" rx="8" fill="#ddf4ff" stroke="#0969da" stroke-width="1.5"/>
|
|
133
|
-
<text x="1320" y="358.5" text-anchor="middle" font-weight="600">Context under
|
|
133
|
+
<text x="1320" y="358.5" text-anchor="middle" font-weight="600">Context under limit?</text>
|
|
134
134
|
<text x="1320" y="374.5" text-anchor="middle" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" font-size="11.5">session context</text>
|
|
135
135
|
<text x="1320" y="390.5" text-anchor="middle">error = at threshold</text>
|
|
136
136
|
</g>
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
<polyline points="1320,403 1320,650" fill="none" stroke="#57606a" stroke-width="1.6" marker-end="url(#arr)"/>
|
|
176
176
|
<text x="1326" y="440" text-anchor="start" font-size="11" fill="#57606a" class="lbl">yes: chain</text>
|
|
177
177
|
<polyline points="1399,370 1421,370" fill="none" stroke="#57606a" stroke-width="1.6" marker-end="url(#arr)"/>
|
|
178
|
-
<text x="1410" y="329" text-anchor="middle" font-size="11" fill="#57606a" class="lbl"
|
|
178
|
+
<text x="1410" y="329" text-anchor="middle" font-size="11" fill="#57606a" class="lbl">limit reached: close</text>
|
|
179
179
|
<polyline points="1500,337 1500,203" fill="none" stroke="#57606a" stroke-width="1.6" marker-end="url(#arr)"/>
|
|
180
180
|
<polyline points="1320,716 1320,746 420,746 420,716" fill="none" stroke="#57606a" stroke-width="1.6" marker-end="url(#arr)"/>
|
|
181
181
|
<text x="870" y="740" text-anchor="middle" font-size="11" fill="#57606a" class="lbl">next wave: back to step 2, the limits still apply</text>
|
package/docs/lead.md
CHANGED
|
@@ -74,10 +74,14 @@ picture, not its source.
|
|
|
74
74
|
The lead asks both reviewers (`code-reviewer`, `qa-reviewer`) for a short reply with a `Report file: <path>` line in
|
|
75
75
|
the brief (L440): the full report goes to that file, outside the repository, and the agent answers in 5 lines at most
|
|
76
76
|
(`Verdict:`, `Blocking:`, `Follow-ups:`, `Report:`). The lead reads the file only when it needs the detail; twenty
|
|
77
|
-
reports of 3 to 5k each had swollen its context on 29-10.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
reports of 3 to 5k each had swollen its context on 29-10. The same line goes to the `ux-reviewer` and the `doc-keeper` (L452), which answer in 15 lines at most
|
|
78
|
+
(`Verdict:`, `Findings:` with the rule or measure of each, `Not verified:`, `Sub-tasks:`, `Report:`): about 25 reports of 2 to 4k had taken
|
|
79
|
+
the lead's context from 80k to 466k tokens on 30-10.
|
|
80
|
+
7. **Threshold and chaining.** Before every new wave the lead reads `cadence session context`. While the context is under
|
|
81
|
+
the threshold it chains: `cadence orchestrate --continue` draws the next ready lot of the plan, in the
|
|
82
|
+
declared priority, without asking the human « next? ». The threshold is in tokens, not in %: `lead.contextThreshold` in the `cadence.yaml` of the folder where the lead runs
|
|
83
|
+
(an integer ≥ 1, `150000` by default, because 60 % of a 1M window is 600k), and `session context` prints its verdict on a
|
|
84
|
+
second line (`seuil 150000 tokens : sous le seuil` or `atteint`). When it is reached, or when the figure cannot
|
|
81
85
|
be read (exit code 2: the band is not loaded, or its figure is stale), it starts nothing. A lot
|
|
82
86
|
that was not handed back ready is reported, not chained over; a question or a UX reservation
|
|
83
87
|
pending on a lot never stops the chain on the other lots.
|
|
@@ -119,6 +123,10 @@ The mechanics around it (the life of a lot in the plan, a lot inside a wave, put
|
|
|
119
123
|
[fonctionnement.md](fonctionnement.md). See also the [lead tour](lead.md#lead-tour) section below and the [orchestrate](orchestrate.md#orchestrate)
|
|
120
124
|
page.
|
|
121
125
|
|
|
126
|
+
## lead priority
|
|
127
|
+
|
|
128
|
+
`cadence lead priority <project> --before|--after <project> [folder] [--depth n]` (L449) puts a project right before or after another one in the order of the projects of the folder: it rewrites the `priority:` list of the folder's `cadence.yaml` (created when missing), the list `lead tour`, `cadence orchestrate --continue` and the [lots panel](cadence-hud.md#the-lots-panel-hud-lots-l401) all read. The projects are named as `lead tour` names them (the folder name, or the path from the folder at `depth:` 2 and more). The list written names **every** project of the folder, in the displayed order: a name that covered several projects by prefix (`shop` for `shop-web`) becomes explicit (the comment on its line moves to the first project it covered), a name that matches no project stays at the end of the list. The rest of the file is kept as it is, comments included (the list stays in the style it had, block or `[a, b]`). Nothing is written, and the answer says so (`… est déjà avant … : rien à changer`), when the project is already at that place. A `cadence.yaml` that cannot be read, a `priority:` that is not a list of names, an unknown project, the same project on both sides or two projects with the same folder name are refused; a move the list cannot express is refused too: when a project's name begins another's (`shop` and `shop-web`, matched by prefix as `priority:` always does), `shop-web` cannot be put after a project that follows `shop`, so the move is refused naming the clash instead of announcing a placement that does not happen. In every refusal the file is left untouched. The command never commits: the `cadence.yaml` change is yours to commit.
|
|
129
|
+
|
|
122
130
|
## lead hold and release
|
|
123
131
|
|
|
124
132
|
`cadence lead hold <label> [--ttl minutes]` and `cadence lead release <label>` (L255) declare the sessions the lead starts by hand — a fix subagent, a review, a QA pass launched outside an orchestrated wave. No wave carries such a session, so the slot registry could not see it: on 2026-10-23 the lead ran 4 sessions under a cap of 3 while `créneaux libres` said one slot was free. Orchestrated waves read the holds too (L424): a wave takes no session slot while the held sessions and the running ones already reach the cap, so a hold can no longer be overtaken by a wave starting a session on the « free » slot. `hold` registers the session under its label for `--ttl` minutes (an integer ≥ 1, 60 by default; holding the same label again renews it instead of counting twice); `release` hands it back (releasing a label that is not held answers `aucune session « <label> » inscrite` and exits 0). After the release (L416), `release` prints what it leaves: the slots line, the `prêts, ressources libres` line when a ready lot is free, and `⚠ N créneau(x) libre(s) sur M : lancer un lot prêt ou une session, un créneau vide ne sert à rien` while at least one slot is free (on 2026-10-29 a cap of 3 was taken for 2 and a slot stayed empty for more than an hour); `hold` prints neither. A hold whose time has run out stops counting, but is not forgotten: nothing stops the session (L278), so `cadence lead tour` and `cadence orchestrate --status` print `⚠ session du lead échue : <label> (inscrite depuis 5 h 10 min, échue depuis 2 h 10 min) — la reprendre (cadence lead hold <label> --ttl <minutes>) ou l'arrêter (cadence lead release <label>)` after the slots line, and the `cadence-hud` band shows the same ⚠ row, until the lead renews the hold or releases it (an expired hold nobody touched is dropped 24 h after its expiry). The `--ttl` is therefore the maximum duration declared for the session; the `lead` skill says to renew or stop it at each ⚠. The line is not part of `--json`.
|
package/docs/orchestrate.md
CHANGED
|
@@ -133,8 +133,8 @@ Both keys are optional; no `continue:` key keeps the old behaviour. A lot whose
|
|
|
133
133
|
**Chaining without the human (L148)**: the `lead` skill, once the human has chosen the first lots and the order, does not ask « next? »
|
|
134
134
|
after a lot that comes back ready: it re-verifies it, records `raf done`, pushes, delivers, has the `qa-reviewer` check the
|
|
135
135
|
delivered app, then starts the next wave with `--continue`, in the declared priority — one project at a time, within the two-session
|
|
136
|
-
limit. Before each new wave it reads the context of its own session with `cadence session context` (the figure of the `ctx` segment of the `cadence-hud` band, which publishes the context of each session to `~/.cadence/orchestrate/hud-context/<session id>.json`; the command picks the file named by `$CLAUDE_CODE_SESSION_ID`, set in the environment of the session's Bash, never "the latest file of the folder" — two sessions of one folder alternate; without the band loaded in that session the command exits with code 2, and a missing or stale figure counts as at the threshold);
|
|
137
|
-
|
|
136
|
+
limit. Before each new wave it reads the context of its own session with `cadence session context` (the figure of the `ctx` segment of the `cadence-hud` band, which publishes the context of each session to `~/.cadence/orchestrate/hud-context/<session id>.json`; the command picks the file named by `$CLAUDE_CODE_SESSION_ID`, set in the environment of the session's Bash, never "the latest file of the folder" — two sessions of one folder alternate; without the band loaded in that session the command exits with code 2, and a missing or stale figure counts as at the threshold); once it has reached
|
|
137
|
+
**`lead.contextThreshold`** (tokens, `150000` by default, see [session](session.md)) it starts nothing: it runs `session-close` in each project touched, writes its memory and records three lines for next
|
|
138
138
|
time (`cadence session next`). Stays with the human: the first choice and the order, the questions a session raised, the UX
|
|
139
139
|
reservations; a lot that is not ready, a red delivery or a blocking QA finding is reported and not chained over.
|
|
140
140
|
|
|
@@ -183,6 +183,8 @@ usability (code only when the lot is not `visible`). Failing tests (reported red
|
|
|
183
183
|
|
|
184
184
|
**One UX review per lot (L221)**: the UX review of a `visible` lot used to run before the code review and again after every correction (finance L75 on 2026-10-15: three Opus UX sessions for one useful verdict). It now runs once, as the last step, on the code the review found compliant: after the single pass on minor findings and its short review when there is one. Without `orchestrate.ux`, or when the app does not start (port taken, silent app), no UX session is played: the lot concludes on the compliant code review and keeps its note (« UX à faire par le lead », « UX non vérifiée : … »). The lot budget bounds writing, never this review; an exhausted wave budget suspends it like any session. Every write step (implementation, correction) clears the lot's UX state (`ux`, `uxVerdict`, UX findings), so a stale UX verdict never sends a compliant code review back to a correction. A resumed wave whose state says `next = ux` (written before this change) replays the code review first unless the last compliant one read the current `HEAD`. The recorded `raf review` verdict and sha are the code review's; a commit made by someone else since that review hands the lot back, as before. A small lot and a lot with no screen keep their single review. The measure before/after is in [`docs/mesures/ux-une-fois-2026-10-15.md`](mesures/ux-une-fois-2026-10-15.md).
|
|
185
185
|
|
|
186
|
+
**A lot made visible by its own session (L454)**: `visible` is read from the plan when the wave starts, and a session may add it (warhammer L37 and L43 on 2026-10-30: the lot left « ready » with « — » in the UX column and no UX step). After every writing session (`implement`, `fix`), before the first review, the orchestrator re-reads `visible` in the plan of the lot's worktree: a lot that became visible follows the path of a visible lot — the UX review after the compliant code review (or the single `review-small` pass of a small lot) — and the table shows its UX column. Without `orchestrate.ux`, no UX session can be played: the lot concludes and carries the note « visible ajouté en cours de vague, étape UX à jouer », under the table, for the lead. An unreadable plan, or a `visible` removed during the wave, leaves the lot as it was; a `screen:` added during the wave does not reopen the spec stage.
|
|
187
|
+
|
|
186
188
|
**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.
|
|
187
189
|
|
|
188
190
|
**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 (L361), the same structured report is honoured when the program's own gate is green (`orchestrate.test` exits 0): `vert: false` with a non-empty `echecsDeBase` and an empty `nouveauxEchecs` goes straight to the review, with a warning, without a correction pass and without the « tests rouges » escalation; without those two fields the claim of red tests still triggers the correction. The program never guesses from the text of the result (« 24 failed »). The `implement` and `fix` briefs ask for `echecsDeBase` and `nouveauxEchecs`.
|
|
@@ -870,6 +872,7 @@ orchestrate:
|
|
|
870
872
|
testReplay: npx vitest run --minWorkers=1 --maxWorkers=$CADENCE_TEST_WORKERS {files} # replay alone of a red test before it counts (L354, see « A red test is replayed alone »; `cadence verify --gate` applies it too, L387, `docs/deliver.md`); `{files}` required; absent: no replay, nothing held for unstable (optional)
|
|
871
873
|
build: npm run build # run BEFORE the tests (L333); both results go to the reviewer, who does not redo them (optional)
|
|
872
874
|
worktreeDeps: [node_modules, .venv] # untracked dependencies that `cadence verify --gate` copies into its throwaway worktree (L348, L389; the gate runs in the main tree instead when it is clean at the sha, `verify.inPlace`, `docs/deliver.md`); absent: the `node_modules` of every level and the `.venv` are copied (never data or `.env`, L392), the other ignored folders are linked as in the worktrees of a wave (optional)
|
|
875
|
+
gate: { readsPlan: true } # a test of the gate reads the plan (`.cadence/raf.yaml`): `cadence verify --gate` no longer takes over a wave's verdict under commits that change only the plan (L444), it replays; `true` or the list of the paths of the tests that read it; absent or `false`: those commits are crossed (L450, `docs/deliver.md`) (optional)
|
|
873
876
|
precheck: true # default: before the first implementation of a lot with no commit whose title cites a finding or an earlier lot, and whose title or `goal:` cites a finding (`constat`, `revue`, `review`, `audit`) or a plan id (an earlier `L<number>`, also followed by a hyphen as in `L12-L15` or `L12-bis`; any other id such as `Q-CAN5`; `project:L<number>`), a read-only Sonnet session checks whether the deliverable is already in the repository (see below); false skips it, local runs it on claude-local (Ollama) with Sonnet as fallback, for every lot with no commit whatever its title (L296); a lot created after the last code commit of the repository is not pre-checked, local or Sonnet, unless it cites a lot that already has commits (L322)
|
|
874
877
|
accept: npx vitest run -c vitest.acceptation.config.ts # accept step (see « The accept step »): the dedicated command that runs ONLY the acceptance tests of the lot (optional; without it the step is skipped with a warning)
|
|
875
878
|
maquette: true # default: UX review of the mockup between spec and accept (see « The mockup review »); false skips it
|
package/docs/raf.md
CHANGED
|
@@ -66,6 +66,7 @@ raf gantt # .cadence/gantt.html (next to the plan)
|
|
|
66
66
|
| `raf todo <id> [--reason text]` | put a started lot back to `todo` (`doing` → `todo`, `started:` removed, dated note « remis en attente : reason »): refused when work commits cite the lot (plan-only and version commits do not count, as `raf commits`), or on a sub-task; sub-tasks and other keys are left as they are. **Reopening** (L396): on a `done` or `dropped` lot, `raf todo` is THE command — never the YAML — that reopens it (`→ todo`, `finished:` removed, `started:` kept so its commits stay its own, dated note « rouvert (était done) : reason »); `raf start`/`raf done`/`raf drop` on a closed lot say so (`L3 est done ; le rouvrir si c'est voulu : raf todo L3`). An archived lot reopened this way comes back into the plan file at that write |
|
|
67
67
|
| `raf migrate [--dry-run]` | move `docs/plan/raf.yaml` to `.cadence/raf.yaml` and rewrite the readers of the old path in the repository, see [Moving the plan: `raf migrate`](#moving-the-plan-raf-migrate) |
|
|
68
68
|
| `raf note <id> "text"` | dated note — keep decisions next to the work |
|
|
69
|
+
| `--verbose` on `raf done`, `ux`, `review`, `note`, `move`, `public` · `raf hook post-commit --verbose` (L453) | those commands print no title of the lot they touch (nothing in the lead's context but `L12 → done` and the `plan non commité` command, which is unchanged); `--verbose` adds one line `L12 doing <full title>` (nothing for a sub-task). The post-commit hook warning `raf: L12 (doing) <title>` cuts the title at 60 characters (59 and `…` beyond; a title of 60 stays whole), `--verbose` on the hook gives it whole. `raf show`, `raf list`, `raf now` and the session tables are not concerned |
|
|
69
70
|
| `raf title <id> "new title"` | replace the title of a lot of any status (`done` included) and add a dated note « titre remplacé : « old title » »; `raf now`, `raf show`, `raf list`, `session start` and the proposals read the new one. Refused on an empty or identical title, an unknown lot or a sub-task |
|
|
70
71
|
| `raf archive [--before date] [--dry-run]` | move the `done` and `dropped` lots (those finished strictly before `--before`, when given; a lot with no `finished:` date stays then) out of the FILE `.cadence/raf.yaml` into `.cadence/archive/<year>.yaml`, the year of their end (L396). **The archive is a storage detail, invisible to every reader**: the loaded plan presents all the lots, active and archived, **in the order they had before the archiving** — each archived lot takes its place by number among the lots of the plan file (just before the first active lot with a greater number), the order of the plan file itself never moves — so `raf show`, `raf list`, `raf commits`, `raf check`, `raf now`, `raf gantt`, `cadence news`, `cadence perf`, `session start`, `lead tour`, `cadence ref coverage`, `cadence deliver`, the post-commit hook and `cadence orchestrate` print exactly what they printed before the archiving — nothing has to « also look in the archive », a lot id is never reused, and a commit or a News entry citing an archived lot is not « inconnu ». What changes is the file: the plan the sessions open stays short (the cadence plan weighed 559 KB, ≈ 150 k tokens) and the briefs of the implementation, the sheets, the code review and the pre-check tell the sessions never to open `.cadence/archive/`. **Writing**: every lot goes back to the file it came from — `raf note`, `raf review`, `raf ux`, `raf title`… on an archived lot rewrite its year file, the plan file is untouched; a lot reopened (`raf todo <id>`, or `status: doing` set by hand in a year file) comes back to the end of the plan file at the next write, so the archive only ever holds `done` and `dropped` lots. **Lossless, whatever fails**: the year files are written before the plan file (should the plan then fail to write, a moved lot is at worst duplicated — which the next load refuses by name — never lost), and any exception during the write (`EACCES` on a read-only file, a full disk, a `plan.onWrite` command that fails) puts the plan, the year files and the declared files back as they were — a year file born of that write disappears — and the error says so (`écriture du plan refusée (EACCES, …) — plan et archive inchangés`). **Guards**: a closed lot with an open gap (what `raf check` signals — code review, UX review, docs.sync, News entry…) stays in the plan with the gap as its printed reason (`L1 reste dans le plan : écart ouvert — L1 est visible et terminé sans entrée Nouveautés …`); a lot named in `after:` by another, or counted by a `gate:`, is archived like any other — the readers see the archive, so the dependency and the count read the same. A same id found in two files (plan and archive, or two years) is a named error that stops every command (`identifiant en double : L1 est dans .cadence/archive/2025.yaml et dans .cadence/raf.yaml`) until one copy is removed by hand — never a silent choice of one copy. `--dry-run` lists without writing; `--before` takes `YYYY-MM-DD`; refused on a read-only plan (its tool keeps its own history) and while the plan is still at the legacy `docs/plan/raf.yaml` (the archive would land under `docs/`, published, and `raf migrate` would not move it: `raf migrate` first, the refusal says so). For `cadence orchestrate`, the year files are plan files like `.cadence/raf.yaml`: a session that commits one has « touché le plan ». Like every plan write, it is not committed: the command to commit the plan and the archive files together, at explicit paths, is printed (`plan.commit` template, action `N lot(s) archivé(s)`); replaying moves nothing twice. The archive files are kept with the plan: touching them is not working on a lot. **The published plan**: a generator run by `plan.onWrite` (below) that reads only the file `.cadence/raf.yaml` publishes a plan without the archived lots; `raf archive` says so on stderr when `plan.onWrite` is declared, the generator must also read `.cadence/archive/*.yaml` (key `lots`, same lot format as the plan) |
|
|
71
72
|
| `raf show <id> [--notes]` | one lot: status, dates, `goal : …` right after the title when the lot has a `goal:` (native key or `plan.fields.goal`, L272: the objective that replaces the title in the `orchestrate` briefs), `after`, public title, then for a visible lot two stable lines — `visible : oui` (followed by `screen : …` when the lot has one) and `ux : <verdict>` or `ux : absent` (the `raf ux` verdict, L439: what the `livreur` agent reads before closing a visible lot; a lot that is not visible prints neither line), `spec`, `uses : banc, passerelle` when the lot has `uses:`, dated notes in order, then the counted commits (as `raf commits`, neighbouring repositories included); `--notes` prints the notes alone |
|
package/docs/session.md
CHANGED
|
@@ -20,8 +20,15 @@ cadence session context # `ctx 42 % (84000/200000)`: the context of Y
|
|
|
20
20
|
# overrides it; with no variable, the folder decides only if a single session publishes for it
|
|
21
21
|
# (two sessions in one folder = exit 2, "pass --session"); exit 2 too when no fresh figure
|
|
22
22
|
# (band not loaded) = treat as at the threshold
|
|
23
|
+
# a second line gives the verdict against `lead.contextThreshold` (tokens, 150000 by default):
|
|
24
|
+
# `seuil 150000 tokens : sous le seuil` or `… : atteint` (tokens ≥ threshold)
|
|
23
25
|
```
|
|
24
26
|
|
|
27
|
+
`lead.contextThreshold` is read in the `cadence.yaml` of the folder where `session context` runs (the lead's folder): an integer ≥ 1,
|
|
28
|
+
in tokens, because a percentage moves with the window (60 % is 120k on 200k, 600k on 1M). A zero, a negative, a decimal or
|
|
29
|
+
a non-number is refused with the key's name (exit 2, read as at the threshold). It is not scaled to the window: on a window
|
|
30
|
+
smaller than the threshold the verdict never reads `atteint`, so set a lower one there.
|
|
31
|
+
|
|
25
32
|
`session start` records its time in the worktree's state (`session.json`, under the git dir); the next
|
|
26
33
|
`session close` takes it as its window and names it in the section title: `Commits de la période (depuis
|
|
27
34
|
l'ouverture de 09:12)` (with the date when it is not today). A close never moves the boundary, refused
|
package/docs/skills.md
CHANGED
|
@@ -50,6 +50,8 @@ repository with `cadence skills install` (to `.claude/skills/cadence-*` and
|
|
|
50
50
|
edits code. Read-only for real: its `tools:` list is `Read, Grep, Glob, Bash` and the
|
|
51
51
|
Playwright tools (`mcp__playwright`, and `mcp__plugin_playwright_playwright` when the
|
|
52
52
|
browser comes from the Playwright plugin) — no `Edit`, no `Write`. In a wave it gives its captures the absolute path of the capture directory its brief names (a bare name would land in the repository); outside a wave, a path under `.playwright-mcp/` (e.g. `.playwright-mcp/page-home.png`; the Playwright MCP resolves a bare file name against the repository, where it would land at the root); `qa-reviewer` follows it too.
|
|
53
|
+
**Short reply (L452):** with a `Report file: <absolute path>` line in the brief it answers in 15 lines at most (`Verdict:`, `Findings:` with
|
|
54
|
+
the rule or measure of each, `Not verified:`, `Sub-tasks:`, `Report:`, `Captures:`) and writes the full report to that file (outside the repository, verdict last).
|
|
53
55
|
- **code-reviewer** (agent): any stack; given a repository and a lot id, it reads
|
|
54
56
|
the diff itself from the commits that cite the lot — not the author's summary —
|
|
55
57
|
and the project's CLAUDE.md, when there is one, for its conventions; findings grounded in a
|
|
@@ -69,6 +71,8 @@ repository with `cadence skills install` (to `.claude/skills/cadence-*` and
|
|
|
69
71
|
document behind the lot; the lead calls it by hand. A document outside the repository only when the project's
|
|
70
72
|
`cadence.yaml` names it (`docs.articles`): the agent then proposes a lot for that repository's plan and never writes it
|
|
71
73
|
([news](news.md#the-librarian-doc-keeper)).
|
|
74
|
+
**Short reply (L452):** with a `Report file: <absolute path>` line in the brief it answers in 15 lines at most (`Verdict:`, `Findings:` with
|
|
75
|
+
the rule or measure of each, `Not verified:`, `Sub-tasks:`, `Report:`) and writes the full report to that file (outside the repository, verdict last).
|
|
72
76
|
- **reference-analyst** (agent, L265): given the folder of a document registered by `cadence ref add` and the file to
|
|
73
77
|
write, it looks at every rendered page whole, then every zone at full resolution, measures with `cadence ref measure`
|
|
74
78
|
(never by eye), copies every text verbatim, links each figure to the passages that cite it and writes one reading in
|
package/docs/whats-new.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## In two lines
|
|
6
6
|
|
|
7
|
+
- **1.28.0**: projects are reordered from the lots panel or `cadence lead priority`; the lead's context threshold is in tokens; reviewers answer the lead in a few lines; the gate replays under plan commits when tests read the plan.
|
|
7
8
|
- **1.27.0**: `cadence corrections --classify` names the sub-cause of each code-bug pass and the dominant link of the chain, `cadence perf` shows it; the mockup review checks hierarchy, tokens, texts and extra accessibility.
|
|
8
9
|
- **1.26.0**: a new `livreur` agent delivers a ready lot for the lead (gate, UX verdict, `raf done`, push, deliver) and hands back one line; `raf show` prints `visible` and `ux` for a visible lot.
|
|
9
10
|
- **1.25.1**: publishes 1.25.0 (tagged, never published); tests now pass in a fresh clone and `cadence release` replays them in one; the panel of lots shows each project's last delivery.
|
|
@@ -54,6 +55,8 @@ Each is a paragraph in [docs/whats-new.md](whats-new.md) and a full section in [
|
|
|
54
55
|
|
|
55
56
|
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).
|
|
56
57
|
|
|
58
|
+
**1.28.0**: the lead spends less of its context, and the projects are ordered where they are read. `cadence lead priority <project> --before|--after <project>` rewrites the `priority:` list of the folder's `cadence.yaml`, comments kept, and the lots panel does the same by drag and drop or Alt+↑/↓ in a region « ordre des projets » above the tree (L449, `docs/lead.md`, `docs/cadence-hud.md`); a move it cannot write back faithfully (a name that prefixes another) is refused with its reason. The lead's context threshold is a number of tokens, `lead.contextThreshold` (150000 by default), read by `cadence session context` (L437). `ux-reviewer`, `doc-keeper` and the reviewers answer the lead in a few lines and keep the full report in the file named by `Report file:` (L452), and `raf` cuts the titles it echoes to 60 characters (L453). `orchestrate.gate.readsPlan` lets a project whose tests read the plan say so: `cadence verify --gate` then replays the suite under plan-only commits instead of taking over the wave's verdict (L450). A lot made `visible` by its own session during a wave now goes through the UX step instead of coming out ready without it (L454).
|
|
59
|
+
|
|
57
60
|
**1.27.0**: the code-bug bottleneck is named, and the mockup review looks at more. `cadence corrections` sub-classifies the « bug de code » passes (L447, `docs/corrections.md`): one of seven sub-causes (edge case, broken test, misread contract, forgotten caller, false statement, empty test, other) per pass, asked of the local model with `--classify` and cached in `.cadence/corrections-sous-causes.json` (`--cache-sous-causes`); the report gains the section « Bug de code par sous-cause » and the dominant link of the chain (at least 10 passes sub-classified and 40 % of them), `--json` the key `bugCode`, and `cadence perf` prints the same count under « causes dominantes », read-only; nothing changes for a window with no `bug-code` pass. On the 24→30-10 window the dominant link is the `implement` brief (`docs/mesures/bug-code-2026-10-30.md`): 96 of the 136 `bug-code` passes are edge cases, so the « edge cases » bullet of the L428 block of `implement.md` now asks for the list before the first line of code and for each listed input to be played once on the real path before reporting (marked `[L447]`); the next measure counts L428 and L447 together, from 2026-10-30. The mockup review (L448, `docs/orchestrate.md`) checks four more families of design criteria, each tied to a named rule or a count: visual hierarchy (one primary element per state), consistency with the product tokens (values that do not come from the product's CSS variables), quality of the texts (error, empty and loading states, action labels, destructive confirmations, one word per notion) and complementary accessibility (focus order, name, role and label, error as text, text at 200 %). It applies in mockup mode only, with no new review or session and the same severity scale and report; the spec writer and its brief apply the drawing-time criteria before the review. Measure: « sessions ux par lot visible » of `cadence perf` (1.6 on the window of 30-10, target 1.2).
|
|
58
61
|
|
|
59
62
|
**1.26.0**: the lead hands the delivery of a ready lot to a subagent. The `livreur` agent (L439, installed by `cadence skills install` like the others) takes one ready lot and one sha given by the lead, and runs `cadence verify --gate --sha`, the UX verdict according to `lead.ux`, `raf done` and the plan commit it prints, `git push origin <sha>:main` and `cadence deliver`, then hands back one line (`L84 livré 3d6edf4, QA à lancer <url>`) or one `Question : …`. It first checks that the sha is an ancestor of HEAD, has no lot that is not done below it and is not already in `origin/main` and in the delivery log; it launches no agent, never the `qa-reviewer` (the lead does, after the line), and the lead launches one `livreur` at a time (`docs/lead.md`). A relaunch on a lot already done weighs both proofs (`origin/main` and `deliveries.log`), so « pushed, never delivered » is no longer masked by « déjà clos ». It reads `git diff --name-only origin/main..<sha>` right after `git fetch origin main`, before the gate and the push, to decide whether a QA pass is due and refuses a brief without the base URL before any action. `raf show <lot>` prints two new stable lines for a visible lot, `visible : oui` (then `screen : …` when the lot has one) and `ux : <verdict>` or `ux : absent` (`docs/raf.md`): that is how the `livreur` knows whether the lot is visible and whether the human's UX verdict is recorded (`lead.ux: human`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylad/cadence",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.28.0",
|
|
4
4
|
"description": "A small, repo-native working method: a versioned plan linked to your commits, a changelog with screenshots, session rituals and deliveries proven by their effect.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Sylvain Ladoire",
|
package/skills/lead/SKILL.md
CHANGED
|
@@ -80,7 +80,7 @@ like (the human's words) and keeps the rest verbatim. **A stale title is no reas
|
|
|
80
80
|
|
|
81
81
|
A lot that adds or changes a screen is `visible`: after the implementation, have the
|
|
82
82
|
`ux-reviewer` agent review it (give it the URL or the way to run the app) and bring its verdict and
|
|
83
|
-
proposed sub-tasks back to the human — or, where the folder's `cadence.yaml` says `lead.ux: lead`, form the UX verdict yourself (see « The UX verdict » in section 2c).
|
|
83
|
+
proposed sub-tasks back to the human (add a line `Report file: <absolute path>` outside the repository, and the folder of the captures: it answers in 15 lines at most — verdict, findings with their rule or measure, not verified, sub-tasks, the paths — and keeps the measurements in that file, L452) — or, where the folder's `cadence.yaml` says `lead.ux: lead`, form the UX verdict yourself (see « The UX verdict » in section 2c).
|
|
84
84
|
|
|
85
85
|
### 2b. A wave through `cadence orchestrate`
|
|
86
86
|
|
|
@@ -157,12 +157,15 @@ one named by `$CLAUDE_CODE_SESSION_ID` (Claude Code sets it in the environment o
|
|
|
157
157
|
whatever the folder; `--session <id>` overrides it). Without the variable it falls back to the folder only if a single
|
|
158
158
|
session publishes for it; two sessions in one folder make it exit 2 ("pass --session"), never guess the latest. It prints
|
|
159
159
|
`ctx 42 % (84000/200000)`, the figure the `ctx` segment of the band shows, which the band publishes to
|
|
160
|
-
a file because it is only a rendering the model cannot see
|
|
160
|
+
a file because it is only a rendering the model cannot see, then a second line, `seuil 150000 tokens : sous le seuil` or
|
|
161
|
+
`… : atteint`: the threshold is in **tokens**, not in %, because a percentage moves with the window (60 % is 120k on 200k and
|
|
162
|
+
600k on 1M). It is `lead.contextThreshold` in the `cadence.yaml` of the folder where the lead runs (150000 by default); read the
|
|
163
|
+
second line, do not recompute it. If the command fails (exit code 2: the band is not loaded in
|
|
161
164
|
this session, several sessions share the folder without the variable, or the figure is older than two minutes), treat it as
|
|
162
165
|
**at the threshold**: you cannot prove you are under it, and without the band loaded the lead does not chain.
|
|
163
|
-
|
|
166
|
+
When the second line says `atteint` (the context has reached the threshold), start nothing: at the threshold the lead runs `session-close` in each project touched, writes its
|
|
164
167
|
memory (filtered, as `session-close` says), and records three lines for next time with `cadence session next` — the wave
|
|
165
|
-
that was about to start comes first among them.
|
|
168
|
+
that was about to start comes first among them. While it says `sous le seuil`, chain. The lead says in one sentence which side of the threshold
|
|
166
169
|
it is on each time it chains or stops; it never waits for the figure to move (never a polling loop).
|
|
167
170
|
|
|
168
171
|
**What stays with the human**: the first choice and the order; the questions a session raised (the lead brings them
|
|
@@ -193,7 +196,7 @@ is recorded by the lead's own `raf ux` or, for a lot it hands over, by the `livr
|
|
|
193
196
|
|
|
194
197
|
0. After an orchestrated wave the review is already done, by a fresh session: read the table, then go to
|
|
195
198
|
point 2. For a lot delegated by hand:
|
|
196
|
-
0b. For a lot delegated by hand, `raf check` or the `dérive` column reporting `documentation en retard` is handled **before** the review: hand the lot to the `doc-keeper` agent (a fresh subagent; the absolute path of the project and the lot id; it updates the documents, or only lists them if you say read-only). Read the `docs.articles` key of the project's `cadence.yaml` yourself and put its entries (`repo`, `file`, and the absolute path of that repository, `repo` being relative to the project) in the brief, with read access to that repository for the subagent: the agent examines a document outside the repository only if the brief names it, and with no entry (the key is absent by default) you name none. A proposed lot it returns for a document outside the repository (`docs.articles`) is yours to decide: it never writes to that plan.
|
|
199
|
+
0b. For a lot delegated by hand, `raf check` or the `dérive` column reporting `documentation en retard` is handled **before** the review: hand the lot to the `doc-keeper` agent (a fresh subagent; the absolute path of the project and the lot id; it updates the documents, or only lists them if you say read-only). Read the `docs.articles` key of the project's `cadence.yaml` yourself and put its entries (`repo`, `file`, and the absolute path of that repository, `repo` being relative to the project) in the brief, with read access to that repository for the subagent: the agent examines a document outside the repository only if the brief names it, and with no entry (the key is absent by default) you name none. A proposed lot it returns for a document outside the repository (`docs.articles`) is yours to decide: it never writes to that plan. Add a line `Report file: <absolute path>` (outside the repository) to the brief: it answers in 15 lines at most and writes its full report there (L452); read the file only to apply a document it only listed.
|
|
197
200
|
1. The `code-reviewer` agent, as a fresh subagent (most capable model), reviews the lot. Give it
|
|
198
201
|
the absolute path of the project and the lot id, **not** the author's report: it reads the diff
|
|
199
202
|
itself from the commits that cite the lot, runs the checks, and returns real defects only,
|