@sylad/cadence 1.22.1 → 1.23.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/dist/cli.js +15 -8
- package/dist/orchestrate/command.js +10 -5
- package/docs/cadence-hud.md +13 -3
- package/docs/fonctionnement.md +1 -1
- package/docs/lead.md +3 -3
- package/docs/orchestrate.md +2 -2
- package/docs/whats-new.md +3 -0
- package/package.json +1 -1
- package/skills/lead/SKILL.md +2 -2
|
@@ -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.23.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.23.0",
|
|
5
5
|
"author": { "name": "Sylvain Ladoire" },
|
|
6
6
|
"homepage": "https://github.com/Sylad/cadence",
|
|
7
7
|
"repository": "https://github.com/Sylad/cadence",
|
package/dist/cli.js
CHANGED
|
@@ -23,7 +23,7 @@ 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
25
|
import { findProjects, readDepth, leadTour, readLeadUx, tourFreeLine, tourLine, tourNoTestLines, tourWorktreeLines } from './lead.js';
|
|
26
|
-
import { configuredCap, expiredHoldLines,
|
|
26
|
+
import { configuredCap, expiredHoldLines, slotsLine, orchestrate, realOrchestrateDeps } from './orchestrate/command.js';
|
|
27
27
|
import { addHold, releaseHold } from './orchestrate/registry.js';
|
|
28
28
|
import { activeLock, REPO_LOCK } from './orchestrate/lock.js';
|
|
29
29
|
import { cadenceHome, liveWaves, openLotsIn, openWavesIn, waveLotsIn } from './orchestrate/registry.js';
|
|
@@ -227,7 +227,7 @@ function dispatch(argv, io) {
|
|
|
227
227
|
// Le tour parcourt les sous-dossiers (ou, sans sous-projet, prend le dossier lui-même s'il est un projet) : il ne lit pas le plan du dossier courant.
|
|
228
228
|
if (command === 'lead') {
|
|
229
229
|
if (rest[0] === 'hold' || rest[0] === 'release')
|
|
230
|
-
return leadHold(rest, values.ttl, io);
|
|
230
|
+
return leadHold(rest, values.ttl, io, today);
|
|
231
231
|
if (values.ttl !== undefined)
|
|
232
232
|
throw new RafError('--ttl ne va qu\'avec lead hold');
|
|
233
233
|
if (rest[0] !== 'tour' || rest.length > 2)
|
|
@@ -264,11 +264,10 @@ function dispatch(argv, io) {
|
|
|
264
264
|
for (const l of tourNoTestLines(rows))
|
|
265
265
|
io.out(l);
|
|
266
266
|
// les créneaux libres viennent du registre, le lead les lit au lieu de les calculer (L141)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const free = slots ? tourFreeLine(rows) : null;
|
|
267
|
+
// toujours imprimés, même sans vague ni session du lead (L416) : un créneau vide se lit, il ne se déduit pas d'une ligne absente
|
|
268
|
+
io.out(slotsLine(configuredCap(io.env), io.now()).line);
|
|
269
|
+
// en face des créneaux libres, les lots prêts dont aucune ressource n'est prise (L252)
|
|
270
|
+
const free = tourFreeLine(rows);
|
|
272
271
|
if (free)
|
|
273
272
|
io.out(free);
|
|
274
273
|
// une session du lead dont le TTL est échu (L278) : rien ne l'arrête, le tour la signale (hors --json, comme les créneaux)
|
|
@@ -1400,7 +1399,7 @@ const HOLD_TTL_MINUTES = 60;
|
|
|
1400
1399
|
/** Longueur maximale de l'étiquette encodée : un nom de fichier tient en 255 octets, `.json.tmp` en prend 9. */
|
|
1401
1400
|
const HOLD_LABEL_MAX = 200;
|
|
1402
1401
|
/** `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. */
|
|
1403
|
-
function leadHold(rest, ttl, io) {
|
|
1402
|
+
function leadHold(rest, ttl, io, today) {
|
|
1404
1403
|
const [verb, label] = rest;
|
|
1405
1404
|
const usage = verb === 'hold' ? 'usage : cadence lead hold <étiquette> [--ttl minutes]' : 'usage : cadence lead release <étiquette>';
|
|
1406
1405
|
if (rest.length !== 2 || !label.trim())
|
|
@@ -1409,6 +1408,14 @@ function leadHold(rest, ttl, io) {
|
|
|
1409
1408
|
if (ttl !== undefined)
|
|
1410
1409
|
throw new RafError(`--ttl ne va qu'avec lead hold (${usage})`);
|
|
1411
1410
|
io.out(releaseHold(cadenceHome(), label) ? `session « ${label} » rendue` : `aucune session « ${label} » inscrite`);
|
|
1411
|
+
// ce que la libération laisse (L416) : les créneaux, les lots prêts qui peuvent les prendre, et un ⚠ tant qu'un créneau reste vide
|
|
1412
|
+
const { line, free } = slotsLine(configuredCap(io.env), io.now());
|
|
1413
|
+
io.out(line);
|
|
1414
|
+
const ready = tourFreeLine(leadTour(resolve(io.cwd), today));
|
|
1415
|
+
if (ready)
|
|
1416
|
+
io.out(ready);
|
|
1417
|
+
if (free > 0)
|
|
1418
|
+
io.out(`⚠ ${free} ${free === 1 ? 'créneau libre' : 'créneaux libres'} sur ${configuredCap(io.env)} : lancer un lot prêt ou une session, un créneau vide ne sert à rien`);
|
|
1412
1419
|
return 0;
|
|
1413
1420
|
}
|
|
1414
1421
|
// le nom de fichier encode l'étiquette (6 octets par lettre accentuée) : au-delà, le système de fichiers refuse (ENAMETOOLONG)
|
|
@@ -593,16 +593,21 @@ function duration(ms) {
|
|
|
593
593
|
const s = Math.round(ms / 1000);
|
|
594
594
|
return s < 60 ? `${s} s` : `${Math.floor(s / 60)} min ${String(s % 60).padStart(2, '0')} s`;
|
|
595
595
|
}
|
|
596
|
+
/** La ligne des créneaux (L141, L255) et leur nombre libre : toujours, même sans vague ni session du lead (L416 : « libres : 3 sur 3 » se lit, il ne se déduit pas d'une absence de ligne). */
|
|
597
|
+
export function slotsLine(cap = DEFAULT_MAX_SESSIONS, now = new Date()) {
|
|
598
|
+
const home = cadenceHome();
|
|
599
|
+
const holds = liveHolds(home, now);
|
|
600
|
+
const free = freeSlots(liveWaves(home), cap, holds.length);
|
|
601
|
+
const lead = holds.length ? ` · sessions du lead : ${holds.length} (${holds.map((h) => h.label).join(', ')})` : '';
|
|
602
|
+
return { line: `vagues en cours : ${liveWaves(home).length} · sessions en cours : ${liveSlots(home).length}${lead} · créneaux libres : ${free} sur ${cap}`, free };
|
|
603
|
+
}
|
|
596
604
|
/** Vagues vivantes (toutes, quel que soit leur dossier de départ), sessions du lead (L255), créneaux libres et dépôts qu'elles tiennent. Rien sans vague ni session du lead. */
|
|
597
605
|
export function liveLines(cap = DEFAULT_MAX_SESSIONS, now = new Date()) {
|
|
598
606
|
const home = cadenceHome();
|
|
599
607
|
const waves = liveWaves(home);
|
|
600
|
-
|
|
601
|
-
if (waves.length === 0 && holds.length === 0)
|
|
608
|
+
if (waves.length === 0 && liveHolds(home, now).length === 0)
|
|
602
609
|
return [];
|
|
603
|
-
const
|
|
604
|
-
const lead = holds.length ? ` · sessions du lead : ${holds.length} (${holds.map((h) => h.label).join(', ')})` : '';
|
|
605
|
-
const lines = [`vagues en cours : ${waves.length} · sessions en cours : ${slots}${lead} · créneaux libres : ${freeSlots(waves, cap, holds.length)} sur ${cap}`];
|
|
610
|
+
const lines = [slotsLine(cap, now).line];
|
|
606
611
|
for (const w of waves)
|
|
607
612
|
lines.push(` ${w.wave} (pid ${w.pid}, depuis ${w.started}) lancée depuis ${w.cwd} · plafond ${w.cap ?? '?'} · dépôts : ${w.repos.join(', ')}`);
|
|
608
613
|
return lines;
|
package/docs/cadence-hud.md
CHANGED
|
@@ -59,12 +59,12 @@ answers `argument inconnu : … (attendu : cmd, projets, lots)` and leaves the b
|
|
|
59
59
|
|
|
60
60
|
### The lots panel (`/hud lots`, L401)
|
|
61
61
|
|
|
62
|
-
`/hud lots` opens a **panel**,
|
|
62
|
+
`/hud lots` opens a **panel**, beside the conversation (inline above the prompt on the main screen); the same command closes it. It is never opened on its own, and the command opens it whatever the width of the window, Claude placing it; when Claude cannot place it, the command answers `panneau des lots non placé : <reason>` and nothing opens. For each project of the folder (the order of `priority:`) it draws the open lots as a tree, with their state and their `after:` links:
|
|
63
63
|
|
|
64
64
|
```
|
|
65
|
-
Lots
|
|
65
|
+
Lots glisser un lot, ou Alt+↑ / Alt+↓ lu à 15:52
|
|
66
66
|
▾ cadence 7 lots non clos
|
|
67
|
-
|
|
67
|
+
▾L401 Panneau des lots (mod cadence-hud, Pane) 2 sous-lots ▶ en cours
|
|
68
68
|
├ L401/2 Écrire le panneau et son rafraîchissement ▶ en cours
|
|
69
69
|
└ L401/3 Test du rendu (claude plugin test) et doc ○ à faire
|
|
70
70
|
L402 Réordonner par glisser-déposer after: L400 L401 ⊘ bloqué
|
|
@@ -75,6 +75,12 @@ Lots lecture seule lu à 15:52
|
|
|
75
75
|
|
|
76
76
|
Each project name is a button (L409): a click, or Enter once it has the focus (click in the panel, or ctrl+x then tab), folds the project to one line (`▸ maritime 159 lots non clos · 3 en cours`, the name, the open lots and those in progress) or opens it again (`▾`). `/hud lots <project>` does the same from the keyboard (`projet inconnu : … (projets : …)` for a name that is not in the panel). A project with more than 20 open lots (native sub-tasks not counted) is folded until you choose; your choice wins over that default either way, and it is kept between two openings of the panel and between sessions (the plugin's `$.store`, key `lotsFolds`). A folded project that is shown is not counted in `… +N lots de plus`, and when the panel is short it keeps its line even after the cut (the open lots before it give way); if there are more folded projects than the panel has room for, the ones left out are counted there with their open lots. A project the tour could not read has no button.
|
|
77
77
|
|
|
78
|
+
Inside a project the lots are ordered (L410): those in progress first (`doing`, or in a wave being implemented, reviewed, corrected, in question, ready or handed back), then the free ones (no open `after:`, whatever the wave state of a queued, failed or suspended lot), then the rest (blocked by an open `after:`), the order of the plan kept within each group. A sub-lot stays under its parent, and a parent with a sub-lot in progress rises with it (that sub-lot before its siblings).
|
|
79
|
+
|
|
80
|
+
A project shows at most 5 lots (L411), the first ones in the order above (so those in progress first); a lot and its sub-lots count for one, and the project's count (`12 lots non clos`) stays whole. Past 5, a `+ N lots` button (`N`: the lots left out) opens the rest of the project, and turns into `moins`, which closes it again. A lot that has sub-lots (a sub-lot of a plan, or native sub-tasks) shows `▸` (closed) or `▾` (open) glued to its id, in the last cell of its indent and their count after its title (`2 sous-lots`); it is closed by default and a click opens or closes it, at any depth (where the panel is drawn by a `Client` region — terminal and desktop, see Reordering below — the click is on its `▸`/`▾` cell, or Enter on the selected row: the id itself selects the row for a drag). Both choices are kept between two openings of the panel and between sessions (the plugin's `$.store`, key `lotsViews`); a folded project (L409) shows neither lots nor button. `… +N lots de plus`, when the panel is short, counts lots only, never these buttons; when the panel is short, the button of the project being cut keeps its line (like a folded project) and the lots give way, so `moins` is always there to close the rest, and the lots hidden behind a button that is cut with its project are counted in `… +N`.
|
|
81
|
+
|
|
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
|
+
|
|
78
84
|
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.
|
|
79
85
|
|
|
80
86
|
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.
|
|
@@ -86,3 +92,7 @@ The source lives in [`plugins/cadence-hud`](../plugins/cadence-hud/README.md)
|
|
|
86
92
|
`claude plugin validate plugins/cadence-hud`, `claude plugin test plugins/cadence-hud`, and
|
|
87
93
|
`tsc -p plugins/cadence-hud` once Claude Code has loaded the plugin at least once (it generates
|
|
88
94
|
the `.claude-plugin/types` the tsconfig extends).
|
|
95
|
+
|
|
96
|
+
To load the mod from a local copy instead of the marketplace, put it in `CLAUDE_CODE_PLUGIN_DIRS` (for example `~/.claude/mods/cadence-hud`). On the cadence repository, `npm run service -- <sha>` keeps that copy up to date (L419): it synchronises `plugins/cadence-hud` of the version put in service into `~/.claude/mods/cadence-hud` (`--mod-dir` for another folder), leaving `.claude-plugin/types` alone. The folder must already exist; it is never created.
|
|
97
|
+
|
|
98
|
+
`node scripts/porte-mod.mjs` plays the two together (`claude plugin test`, then `tsc -p … --noEmit`, both even if the first is red). In cadence's own gate it is played by `test/porte-mod.test.ts`, since `vitest` excludes `plugins/**`: `orchestrate.test` stays the single `npx vitest run` (a composed gate would refuse `testReplay`), and a mod whose tests or typing are red turns the wave's gate red (L417). A worktree has no generated `.claude-plugin/types`: the script takes them from the copy Claude Code loaded (`~/.claude/mods/cadence-hud`); with none, the gate is red and says why — typing is never skipped. The test is skipped, with vitest's own « skipped » count, on a machine without the `claude` binary.
|
package/docs/fonctionnement.md
CHANGED
|
@@ -73,7 +73,7 @@ working repository's `dist/`: they read a separate worktree under `~/.local/shar
|
|
|
73
73
|
only by `npm run service -- <sha>`. The command refuses a sha that is not an ancestor of `HEAD` and a
|
|
74
74
|
sha whose cited lots are not `done`, so a wave cannot put unreviewed code in service. In the
|
|
75
75
|
working repository, `npm run build` is a typecheck only. A `dropped` lot whose every commit is undone
|
|
76
|
-
by a `git revert` under the sha no longer blocks. The command keeps the copy in service and the 3 put in service before it (rollback), removes the older ones unless a live process still uses one, and says what it purged (number of copies, space freed).
|
|
76
|
+
by a `git revert` under the sha no longer blocks. The command keeps the copy in service and the 3 put in service before it (rollback), removes the older ones unless a live process still uses one, and says what it purged (number of copies, space freed). It also synchronises `plugins/cadence-hud` of the new copy into the local `cadence-hud` mod (`~/.claude/mods/cadence-hud`, `.claude-plugin/types` left alone), so the mod Claude Code loads is never an older release.
|
|
77
77
|
|
|
78
78
|
## Budget of a lot
|
|
79
79
|
|
package/docs/lead.md
CHANGED
|
@@ -114,7 +114,7 @@ page.
|
|
|
114
114
|
|
|
115
115
|
## lead hold and release
|
|
116
116
|
|
|
117
|
-
`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. `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). 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`.
|
|
117
|
+
`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. `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`.
|
|
118
118
|
|
|
119
119
|
The held sessions are kept in the `holds/` folder of the cadence home (`CADENCE_HOME`, shared by every folder and every wave). While at least one is held, the line of the live waves of `cadence orchestrate --status` and of `cadence lead tour` gains a segment and subtracts them: `vagues en cours : 1 · sessions en cours : 0 · sessions du lead : 1 (correctif-L12) · créneaux libres : 0 sur 3`. A held session alone is enough to print the line (`vagues en cours : 0`). It is a display, like the rest of the slots line: a hold blocks no session and never raises the cap (a wave that is already running keeps its own slot counting), and the `lead` skill declares every hand-started session with it.
|
|
120
120
|
|
|
@@ -175,9 +175,9 @@ After the project lines the tour lists the **worktrees kept by waves** (L279, `o
|
|
|
175
175
|
|
|
176
176
|
Before the project lines the tour prints the **perf header** (L227): one line per `⚠` of `cadence perf` for the folder, in the order of `perf`, each ending with its decision and, for a trend, the value of its quality twin (speed and quality side by side); the net-bottleneck `⚠ goulot` line is relayed as is. Nothing is printed when no `⚠` stands, and the output is then the one of a tour without header. The header is read with `perf`'s own reading of the folder (projects up to two levels, whatever `--depth`) and only in the text output: `--json` is unchanged. When `perf` cannot read the folder (no `.cadence/runs`, malformed price grid, unreadable journals or plans) the tour prints no header, reports no perf error and exits 0. The [lead skill](skills.md) reads the header before proposing the three lots and opens an improvement lot only on a trend confirmed on two windows or a net bottleneck.
|
|
177
177
|
|
|
178
|
-
When an orchestrated wave is live, one more line closes the tour (not part of `--json`): `vagues en cours : 1 · sessions en cours : 0 · créneaux libres : 1 sur 2` — the lead reads the free slots there instead of computing them (L141). The `sur M` is the cap the human set (`CADENCE_MAX_SESSIONS`, 2 by default), not a fixed 2 (L208); an invalid value falls back to 2 in this display. The sessions held with [`lead hold`](#lead-hold-and-release) are subtracted from the free slots, and
|
|
178
|
+
When an orchestrated wave is live, one more line closes the tour (not part of `--json`): `vagues en cours : 1 · sessions en cours : 0 · créneaux libres : 1 sur 2` — the lead reads the free slots there instead of computing them (L141). The `sur M` is the cap the human set (`CADENCE_MAX_SESSIONS`, 2 by default), not a fixed 2 (L208); an invalid value falls back to 2 in this display. The sessions held with [`lead hold`](#lead-hold-and-release) are subtracted from the free slots. The line is always printed (L416), with no live wave and no held session too: `vagues en cours : 0 · sessions en cours : 0 · créneaux libres : 3 sur 3`, so that an empty slot is read, not deduced from a missing line.
|
|
179
179
|
|
|
180
|
-
Next to it, when at least one ready lot is free, a line `prêts, ressources libres : alpha:L3 <title> · beta:L2 <title> (banc)` (L252, GitHub issue #13, 2/3) lists, for every project of the tour, the ready lots (same rule as `next`, minus the ones marked « à décider ») that can start now: a lot is free when none of the resources in its `uses:` (shown in parentheses) is held by a lot `doing` in any project of the tour or in any project that shares its resources (the projects under the folder whose `cadence.yaml` declares `resources:`, walked with that file's `depth:`, one level by default, so `P/x` and `P/grp/alpha` share the `banc` of `P/cadence.yaml` with `depth: 2` wherever the tour is launched from, even from inside one project; with no declared resource, the projects of the parent folder), nor by an unfinished lot of a live wave (a wave's lot is recognised by its repository and its id, not by the project name: a nested project `grp/alpha` matches the wave launched from `grp/` that cites it as `alpha`, and a wave launched from another folder only hides the lots of its own repositories); a lot with no `uses:` holds none and is always free. The line shows at most 3 lots per project, then `alpha +K` for the others. Like the slots line it
|
|
180
|
+
Next to it, when at least one ready lot is free, a line `prêts, ressources libres : alpha:L3 <title> · beta:L2 <title> (banc)` (L252, GitHub issue #13, 2/3) lists, for every project of the tour, the ready lots (same rule as `next`, minus the ones marked « à décider ») that can start now: a lot is free when none of the resources in its `uses:` (shown in parentheses) is held by a lot `doing` in any project of the tour or in any project that shares its resources (the projects under the folder whose `cadence.yaml` declares `resources:`, walked with that file's `depth:`, one level by default, so `P/x` and `P/grp/alpha` share the `banc` of `P/cadence.yaml` with `depth: 2` wherever the tour is launched from, even from inside one project; with no declared resource, the projects of the parent folder), nor by an unfinished lot of a live wave (a wave's lot is recognised by its repository and its id, not by the project name: a nested project `grp/alpha` matches the wave launched from `grp/` that cites it as `alpha`, and a wave launched from another folder only hides the lots of its own repositories); a lot with no `uses:` holds none and is always free. The line shows at most 3 lots per project, then `alpha +K` for the others. Like the slots line it is always printed (L416), whether or not a wave is live or a session is held. It is a display: nothing locks, `raf start` and `orchestrate` still start a lot whose resource is taken. `--json` gives the same lots, always, as a `free` array on each project (`id`, `title`, and `uses` when the lot has any; absent when the project has none).
|
|
181
181
|
|
|
182
182
|
The `lead` skill (L253, GitHub issue #13, 3/3) tells the lead to read that line again each time an agent comes back and each time a lot is blocked, before waiting: a lot it lists can start at once, with the free slots line saying a slot is free, instead of waiting behind the blocked one. The line never raises the slot cap.
|
|
183
183
|
|
package/docs/orchestrate.md
CHANGED
|
@@ -514,7 +514,7 @@ an uncommitted `src/` file) touches it. `npm run build` only type-checks, which
|
|
|
514
514
|
The lead updates the copy after the review, once the lot is accepted (`raf done`):
|
|
515
515
|
|
|
516
516
|
```
|
|
517
|
-
npm run service -- <sha> # [--root dir] [--bin-dir dir] [--repo dir]
|
|
517
|
+
npm run service -- <sha> # [--root dir] [--bin-dir dir] [--repo dir] [--mod-dir dir]
|
|
518
518
|
```
|
|
519
519
|
|
|
520
520
|
The script refuses a sha that is not an ancestor of HEAD, and a sha whose commits (since the one in service; only the sha itself the first time)
|
|
@@ -523,7 +523,7 @@ cite a lot whose status is not `done` (`plan:` commits are ignored); a lot `drop
|
|
|
523
523
|
(a rollback is `npm run service -- <old sha>`; the order is the order of the puts in service, not the install date or the age), and removes the others (`git worktree remove`, then `prune`) —
|
|
524
524
|
except a copy that is still needed, which stays and is said: a copy that a live process uses (its working directory, its binary or an argument of its command line, read in `/proc` and resolved with `realpath`; and the copy a `node_modules` link points to, found on the folders above those paths), and a copy that the snapshot of a wave **not finished** points to (`tool/node_modules` of a suspended, interrupted or running wave, status other than `done`), even with no process alive. The output ends with the purge:
|
|
525
525
|
`purge : 37 copies retirées, 2,3 Go libérés` (`aucune copie retirée` when there was nothing to remove, `; 1 gardée, un processus ou une vague non terminée l'utilise encore` for a copy spared). The copies grew without bound before (41 copies, 2.5 GB on 2026-10-25).
|
|
526
|
-
The waves that count are those of the launch folders the script knows (`<folder>/.cadence/runs/<wave>/wave.json`): the repository itself and the working directory of every wave the orchestrator's registry still lists (`~/.cadence/orchestrate/waves/<pid>.json`; the registry drops a dead wave when another registers, so it does not know suspended waves by itself). There is no search over the whole disk: a suspended wave launched from another folder is not protected here, and `--resume` repairs its snapshot instead (above). A wave never marked `done` keeps its copy until it is, or until its folder is removed. A failure of the purge (unreadable copy, busy file) once `service` and the links have switched is a warning on the error output (`avertissement : purge des anciennes copies interrompue …`): the new version is in service, the fingerprint is printed, the exit code is 0, the next put in service tries again. The first run is also what installs the links in `~/.local/bin`. It prints the fingerprint of the copy
|
|
526
|
+
The waves that count are those of the launch folders the script knows (`<folder>/.cadence/runs/<wave>/wave.json`): the repository itself and the working directory of every wave the orchestrator's registry still lists (`~/.cadence/orchestrate/waves/<pid>.json`; the registry drops a dead wave when another registers, so it does not know suspended waves by itself). There is no search over the whole disk: a suspended wave launched from another folder is not protected here, and `--resume` repairs its snapshot instead (above). A wave never marked `done` keeps its copy until it is, or until its folder is removed. A failure of the purge (unreadable copy, busy file) once `service` and the links have switched is a warning on the error output (`avertissement : purge des anciennes copies interrompue …`): the new version is in service, the fingerprint is printed, the exit code is 0, the next put in service tries again. The same run updates the **local copy of the `cadence-hud` mod** (L419), the one `CLAUDE_CODE_PLUGIN_DIRS` makes Claude Code load (`~/.claude/mods/cadence-hud`, `--mod-dir` for another folder): `plugins/cadence-hud` of the copy just put in service (not the working repository) is synchronised into it with `rsync -a --delete`, which leaves out `.claude-plugin/types` (generated by Claude Code in the copy). The output says `mod cadence-hud : copie locale mise à jour (<dir>)`. A machine without that folder gets no copy created (`copie locale absente … non mise à jour`), and a failed `rsync` is a warning on the error output (`avertissement : mod cadence-hud non mis à jour …`), exit code 0 like the purge: the version is in service. Before L419 the copy was updated by hand at each release and a forgotten one left the old mod loaded. The first run is also what installs the links in `~/.local/bin`. It prints the fingerprint of the copy
|
|
527
527
|
(bin, dist, package.json).
|
|
528
528
|
|
|
529
529
|
## The subject of the plan commits (`plan.commit`, L343)
|
package/docs/whats-new.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## In two lines
|
|
6
6
|
|
|
7
|
+
- **1.23.0**: the panel of lots shows five lots per project, in progress first, and reorders a lot by dragging it inside its group; `cadence lead tour` always shows the free slots.
|
|
7
8
|
- **1.22.0**: the panel of lots folds a project with a click on its name; a project of more than 20 open lots starts folded.
|
|
8
9
|
- **1.21.1**: `/hud lots` opens on the desktop app whatever the width; a correction session that makes no commit must say why, or the lot is handed back.
|
|
9
10
|
- **1.21.0**: `/hud lots` opens a panel beside the conversation with the open lots of every project; the pre-push hook of a wave refuses a push that carries a lot not ready.
|
|
@@ -48,6 +49,8 @@ Each is a paragraph in [docs/whats-new.md](whats-new.md) and a full section in [
|
|
|
48
49
|
|
|
49
50
|
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).
|
|
50
51
|
|
|
52
|
+
**1.23.0**: the panel of lots (`/hud lots`, `docs/cadence-hud.md`) becomes a place to steer from. In each project the lots in progress come first, then the free ones, then the blocked ones, plan order kept in each group (L410); at most five lots per project are shown, with a `+ N lots` / `moins` button for the rest, and a lot that has sub-lots folds and unfolds with a click (L411); `… +N lots de plus` counts every lot the height cuts, sub-lots included. A lot is reordered by dragging it onto another lot of its group, or with Alt+↑ / Alt+↓, through `raf move` (L402): dropping it on a lot of another group (a free lot on a lot in progress) is refused with its reason, a drop that changes nothing says so, and the panel never commits the plan. For the lead, `cadence lead tour` prints `créneaux libres : N sur M` even when nothing runs, and `cadence lead release` prints the free slots and the ready lots, with a ⚠ when a slot stays free (L416, `docs/lead.md`); cadence's own test gate also plays the tests and the type-check of the `cadence-hud` mod (L417); and `npm run service` also updates the local copy of the mod (L419).
|
|
53
|
+
|
|
51
54
|
**1.22.0**: the panel of lots no longer lets one big project hide the others (L409, cadence-hud 0.7.0, `docs/cadence-hud.md`): each project folds and unfolds with a click on its name (Enter under the focus, or `/hud lots <project>`), a project of more than 20 open lots starts folded on a single line (name, open lots, in progress), and the folded state is kept between two openings.
|
|
52
55
|
|
|
53
56
|
**1.21.1**: `/hud lots` opened nothing on the desktop app, even on a very wide screen (« terminal trop étroit », L408): the mod checked 110 columns itself, while a panel the person asks for is placed at any width; the engine now decides, and says « non placé » with its reason otherwise (cadence-hud 0.6.1). In a wave, a correction session that makes no commit must give its reason under `choix`; without one the lot is handed back instead of replaying the same review (L406, `docs/orchestrate.md`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylad/cadence",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.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
|
@@ -21,7 +21,7 @@ commands (its CLAUDE.md names them), never with `raf start|done|note|ux|review`.
|
|
|
21
21
|
subagent, check `cadence orchestrate --status`, and while a wave runs, start none beyond the free slots. Read the cap and the
|
|
22
22
|
free slots, do not compute them: `cadence orchestrate --status` and `cadence lead tour` print
|
|
23
23
|
`créneaux libres : N sur M` (M is the configured cap; the registry subtracts, for each live wave, min(cap,
|
|
24
|
-
repositories it still holds) and one slot per lead session (below) from the M slots;
|
|
24
|
+
repositories it still holds) and one slot per lead session (below) from the M slots; `lead tour` always prints it, so `3 sur 3` means all M are free). `lead release` prints the line again after handing the session back, with the ready lots that can fill the slot and a `⚠ N créneau(x) libre(s)` while a slot is empty: start a lot then, do not leave the slot empty.
|
|
25
25
|
**A session you start by hand counts too** (a fix subagent, a review, a QA pass launched outside a wave): the registry does not
|
|
26
26
|
see it, so declare it. Right before starting it, `cadence lead hold <label> [--ttl minutes]` (60 minutes by default,
|
|
27
27
|
the label names the session: `correctif-L12`); as soon as it comes back, `cadence lead release <label>`. While it is held, both
|
|
@@ -111,7 +111,7 @@ ends `interrupted`, and `--resume --wave <wave>` takes it up again (the command
|
|
|
111
111
|
|
|
112
112
|
**Delivering while the wave runs.** A lot that turns **ready** in the journal is not left waiting: once the lead has re-verified it (section 3, point 2) and recorded its verdict (`raf done`), it delivers that lot at once, without waiting for the end of the wave. The re-check runs at that sha with `cadence verify --gate --sha <sha>` (a throwaway worktree whose untracked dependencies are linked from the main tree, removed afterwards), never in the shared tree, which holds the lots still running. Take the sha to deliver from `git log`: the last commit of the ready lot such that no commit of a lot that is not ready (running, queued, in question, handed back) sits beneath it — the same « livrable jusqu'à » the final table prints. Push that sha alone (`git push origin <sha>:main`, never HEAD, which holds the code of lots still running; the wave tolerates exactly this push — the journal says « push du lead (livraison de <lot>) toléré » — because the sha is an ancestor of HEAD holding no commit of a lot of the wave that is not ready (a commit that cites no lot passes), whereas any other push still stops the wave as an incident), then `cadence deliver --dry-run --sha <sha>` and `cadence deliver --sha <sha>`: `deliver` accepts a `--sha` during a wave of the same repository when none of its commits belongs to a lot that is not ready, and refuses otherwise, naming the lots. Without `--sha` it stays refused until every lot of the repository is ready or handed back. The wave's sessions keep committing in the working tree: `--sha` never reads it. What the log keeps is `<sha>`, unless the delivery script declares another deployed commit in `CADENCE_DEPLOYED_FILE`; if HEAD moved during the delivery without that declaration, `deliver` warns (« la tête a avancé pendant la livraison ») and the log keeps `<sha>`: it is not a failure. This is the measured gain: a ready lot used to wait for the slowest lot of its wave (1,554 lot-minutes between « ready » and the end of the wave over 28 waves).
|
|
113
113
|
|
|
114
|
-
**Free resources.**
|
|
114
|
+
**Free resources.** `cadence lead tour` (always, L416) and `cadence orchestrate --continue` print, after the free slots line, `prêts, ressources libres : <project>:<lot> <title> (<resources>) · …`: the ready lots whose shared resources (`uses:`) nothing holds. Read it again each time an agent comes back (a session ended, a lot handed back or ready) and each time a lot is blocked (a question, a refusal, a resource taken), **before waiting** for the next transition: a lot listed there can start now, so start it (with the free slots line saying a slot is free) instead of waiting behind the blocked one. The line is a display: nothing is locked, and it never raises the cap — no `créneaux libres`, no new session. No line means no ready lot is free: wait.
|
|
115
115
|
|
|
116
116
|
`cadence orchestrate --dry-run …` first when a precondition is in doubt. The program, not the lead, runs
|
|
117
117
|
for each lot a fresh short session per step — implementation (Sonnet), code review
|