@sylad/cadence 1.35.0 → 1.36.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/deliver-ci.js +105 -0
- package/dist/deliver.js +57 -75
- package/dist/orchestrate/ajout.js +186 -0
- package/dist/orchestrate/command.js +118 -140
- package/dist/orchestrate/cycle.js +2 -2
- package/dist/orchestrate/depot.js +33 -0
- package/dist/orchestrate/lot-budget.js +50 -0
- package/dist/orchestrate/pool.js +1 -1
- package/dist/orchestrate/retrait.js +37 -0
- package/dist/orchestrate/snapshot.js +13 -0
- package/dist/orchestrate/state.js +47 -9
- package/dist/orchestrate/tiers.js +9 -0
- package/dist/remote-gate.js +2 -2
- package/dist/state.js +4 -0
- package/docs/deliver.md +18 -0
- package/docs/lead.md +3 -1
- package/docs/orchestrate.md +3 -0
- package/docs/releasing.md +1 -1
- package/docs/whats-new.md +3 -0
- package/package.json +1 -1
|
@@ -168,11 +168,11 @@ export class RunStore {
|
|
|
168
168
|
writeFileSync(f, kept.map((l) => `${l}\n`).join(''));
|
|
169
169
|
}
|
|
170
170
|
/**
|
|
171
|
-
*
|
|
172
|
-
*
|
|
171
|
+
* Lit une file en ajout seul de la vague (`answers.log`, `adds.log`) : une ligne JSON par demande (`{id, …}`) ou par demande prise (`{taken: id}`) ; une demande prise n'y figure plus.
|
|
172
|
+
* Une ligne vide ou illisible est ignorée, et `parse` écarte une ligne dont la forme n'est pas celle de la file. Pas de `.json` : `lots()` le prendrait pour un lot.
|
|
173
173
|
*/
|
|
174
|
-
|
|
175
|
-
const f = join(this.dir,
|
|
174
|
+
queue(file, parse) {
|
|
175
|
+
const f = join(this.dir, file);
|
|
176
176
|
if (!existsSync(f))
|
|
177
177
|
return [];
|
|
178
178
|
const entries = [];
|
|
@@ -182,8 +182,11 @@ export class RunStore {
|
|
|
182
182
|
const o = JSON.parse(line);
|
|
183
183
|
if (typeof o.taken === 'number')
|
|
184
184
|
taken.add(o.taken);
|
|
185
|
-
else if (typeof o.id === 'number'
|
|
186
|
-
|
|
185
|
+
else if (typeof o.id === 'number') {
|
|
186
|
+
const rest = parse(o);
|
|
187
|
+
if (rest)
|
|
188
|
+
entries.push({ id: o.id, ...rest });
|
|
189
|
+
}
|
|
187
190
|
}
|
|
188
191
|
catch {
|
|
189
192
|
// ligne vide ou illisible : ignorée
|
|
@@ -191,11 +194,22 @@ export class RunStore {
|
|
|
191
194
|
}
|
|
192
195
|
return entries.filter((e) => !taken.has(e.id));
|
|
193
196
|
}
|
|
197
|
+
/** Numéro suivant d'une file : un de plus que le plus grand déjà écrit. */
|
|
198
|
+
nextId(file) {
|
|
199
|
+
const f = join(this.dir, file);
|
|
200
|
+
const last = existsSync(f) ? [...readFileSync(f, 'utf8').matchAll(/^\{"id":(\d+),/gm)].map((m) => Number(m[1])) : [];
|
|
201
|
+
return Math.max(0, ...last) + 1;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* File des réponses du lead à une vague vivante (L407) : `answers.log`, une ligne JSON par réponse déposée (`{id, lot, text}`).
|
|
205
|
+
* La vague la relit entre deux étapes.
|
|
206
|
+
*/
|
|
207
|
+
answerQueue() {
|
|
208
|
+
return this.queue('answers.log', (o) => (typeof o.lot === 'string' && typeof o.text === 'string' ? { lot: o.lot, text: o.text } : null));
|
|
209
|
+
}
|
|
194
210
|
/** Dépose une réponse dans la file ; rend son numéro. */
|
|
195
211
|
requestAnswer(lot, text) {
|
|
196
|
-
const
|
|
197
|
-
const last = existsSync(f) ? [...readFileSync(f, 'utf8').matchAll(/^\{"id":(\d+),/gm)].map((m) => Number(m[1])) : [];
|
|
198
|
-
const id = Math.max(0, ...last) + 1;
|
|
212
|
+
const id = this.nextId('answers.log');
|
|
199
213
|
appendFileSync(join(this.dir, 'answers.log'), `${JSON.stringify({ id, lot, text })}\n`);
|
|
200
214
|
return id;
|
|
201
215
|
}
|
|
@@ -203,6 +217,30 @@ export class RunStore {
|
|
|
203
217
|
markAnswerTaken(id) {
|
|
204
218
|
appendFileSync(join(this.dir, 'answers.log'), `${JSON.stringify({ taken: id })}\n`);
|
|
205
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* File des lots à ajouter à une vague vivante (L421, `--add`) : `adds.log`, une ligne JSON par demande (`{id, lot, project?, model?}`), le projet étant écrit comme le lead l'a donné
|
|
222
|
+
* (nom de dossier depuis le dossier de lancement). La vague la relit entre deux étapes, comme celle des réponses.
|
|
223
|
+
*/
|
|
224
|
+
addQueue() {
|
|
225
|
+
return this.queue('adds.log', (o) => (typeof o.lot === 'string' ? { lot: o.lot, ...(typeof o.project === 'string' ? { project: o.project } : {}), ...(typeof o.model === 'string' ? { model: o.model } : {}) } : null));
|
|
226
|
+
}
|
|
227
|
+
/** Dépose un lot à ajouter ; rend son numéro. */
|
|
228
|
+
requestAdd(t) {
|
|
229
|
+
const id = this.nextId('adds.log');
|
|
230
|
+
this.appendAddLine({ id, lot: t.lot, ...(t.project ? { project: t.project } : {}), ...(t.model ? { model: t.model } : {}) });
|
|
231
|
+
return id;
|
|
232
|
+
}
|
|
233
|
+
/** La demande `id` est prise (lot ajouté ou écarté) : elle ne sera plus relue. */
|
|
234
|
+
markAddTaken(id) {
|
|
235
|
+
this.appendAddLine({ taken: id });
|
|
236
|
+
}
|
|
237
|
+
/** Ajoute une ligne à `adds.log`, après un saut de ligne si une écriture interrompue a laissé la dernière sans le sien (sinon les deux lignes se collent et sont perdues). */
|
|
238
|
+
appendAddLine(o) {
|
|
239
|
+
const f = join(this.dir, 'adds.log');
|
|
240
|
+
const before = existsSync(f) ? readFileSync(f, 'utf8') : '';
|
|
241
|
+
const cut = before !== '' && !before.endsWith('\n');
|
|
242
|
+
appendFileSync(f, `${cut ? '\n' : ''}${JSON.stringify(o)}\n`);
|
|
243
|
+
}
|
|
206
244
|
journal(line) {
|
|
207
245
|
appendFileSync(join(this.dir, 'journal.log'), `${new Date().toISOString()} ${line}\n`);
|
|
208
246
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { lstatSync, readdirSync, realpathSync } from 'node:fs';
|
|
2
2
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
3
3
|
import { git, gitFailure, tryGit } from './worktree.js';
|
|
4
|
+
import { lotKey, lotRepoPaths } from './state.js';
|
|
4
5
|
/** Voie prévue pour un travail qui doit toucher un dépôt tiers (L456) : une branche à part, jamais la branche principale. */
|
|
5
6
|
export const wipBranch = (project, lot) => `wip/${project}-${lot}`;
|
|
6
7
|
const real = (p) => {
|
|
@@ -119,3 +120,11 @@ export function tierMoved(repo, before, after, lots) {
|
|
|
119
120
|
}
|
|
120
121
|
return null;
|
|
121
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Les dépôts tiers de la session qui le sont encore à sa fin (L421) : un lot ajouté (`--add`) pendant la session a pu prendre l'un d'eux, dont la branche principale bouge alors
|
|
125
|
+
* légitimement, comme celle des autres lots de la vague (R14).
|
|
126
|
+
*/
|
|
127
|
+
export function stillThird(before, wave, mine) {
|
|
128
|
+
const ofOthers = new Set(wave.filter((x) => lotKey(x.project, x.lot) !== mine).flatMap(lotRepoPaths));
|
|
129
|
+
return [...before].filter(([repo]) => !ofOthers.has(repo));
|
|
130
|
+
}
|
package/dist/remote-gate.js
CHANGED
|
@@ -134,14 +134,14 @@ async function probeHost(o) {
|
|
|
134
134
|
const nproc = Number.parseInt(cores, 10);
|
|
135
135
|
return { platform: `${os.toLowerCase()}-${archOf(arch)}`, cores: Number.isFinite(nproc) && nproc >= 1 ? nproc : 1 };
|
|
136
136
|
}
|
|
137
|
-
/** Crée le dossier de travail de l'hôte (dépôt nu, orphelins retirés, R17, R19) et y pousse le sha (R5) ; rend la cause d'un échec. */
|
|
137
|
+
/** Crée le dossier de travail de l'hôte (dépôt nu créé à part puis renommé : deux portes simultanées ne s'y disputent pas, orphelins retirés même si le renommage échoue, R17, R19) et y pousse le sha (R5) ; rend la cause d'un échec. */
|
|
138
138
|
async function pushSha(o, w) {
|
|
139
139
|
const { host, env } = o;
|
|
140
140
|
const { head, bare, root } = w;
|
|
141
141
|
const init = await ssh({
|
|
142
142
|
host,
|
|
143
143
|
env,
|
|
144
|
-
script: `${head}set -e; mkdir -p "$R/deps" "$R/wt"; [ -d ${bare} ] || git init --bare -q ${bare}; ` +
|
|
144
|
+
script: `${head}set -e; mkdir -p "$R/deps" "$R/wt"; [ -d ${bare} ] || { git init --bare -q ${bare}.init-$$ && mv ${bare}.init-$$ ${bare} 2>/dev/null || true; }; rm -rf ${bare}.init-$$ ${bare}/repo.git.init-$$; [ -d ${bare} ]; ` +
|
|
145
145
|
`find "$R/wt" -mindepth 1 -maxdepth 1 -mmin +${ORPHAN_MIN} -exec rm -rf {} + 2>/dev/null || true; ` +
|
|
146
146
|
`find "$R/deps" -mindepth 1 -maxdepth 1 -name '.tmp-*' -mmin +${ORPHAN_MIN} -exec rm -rf {} + 2>/dev/null || true; git -C ${bare} worktree prune 2>/dev/null || true`,
|
|
147
147
|
});
|
package/dist/state.js
CHANGED
|
@@ -156,6 +156,10 @@ export function pidAlive(pid) {
|
|
|
156
156
|
export function appendDelivery(dir, day, sha) {
|
|
157
157
|
appendFileSync(join(dir, 'deliveries.log'), `${day} ${sha}\n`);
|
|
158
158
|
}
|
|
159
|
+
/** Ajoute une étape à `deliveries-steps.log` (ajout seul, une ligne JSON par étape) ; `deliveries.log` n'est pas touché. */
|
|
160
|
+
export function appendStep(dir, step) {
|
|
161
|
+
appendFileSync(join(dir, 'deliveries-steps.log'), `${JSON.stringify(step)}\n`);
|
|
162
|
+
}
|
|
159
163
|
/** Classe d'une livraison rouge : où elle a cassé. `refus` : le script a refusé avant d'agir (code 2). */
|
|
160
164
|
export const FAIL_CLASSES = ['refus', 'ci', 'deploiement', 'verification'];
|
|
161
165
|
const TAIL_SEP = ' ⏎ ';
|
package/docs/deliver.md
CHANGED
|
@@ -133,6 +133,24 @@ cadence deliver # 0 delivered and verified · 1 a step failed · 2
|
|
|
133
133
|
5 minutes is a failure (you probably pushed another commit than the one you
|
|
134
134
|
deliver); every run must end `success`, `skipped` or `neutral`. `gh` errors
|
|
135
135
|
are retried, and reported with their cause after 5 minutes.
|
|
136
|
+
- **CI not waited for on a plan-only sha** (L516): with `ci: github` or a CI command (no `deliver.script`, which
|
|
137
|
+
carries its own CI), when the previous delivery (`deliveries.log`) is an ancestor of the sha and **every** commit of
|
|
138
|
+
`previous..sha` only touches the plan (the same test as `raf check`, `isPlanOnly`), nothing is waited for: no
|
|
139
|
+
`gh run list`, no CI command. The output says `→ CI : non attendue (aucun fichier hors plan depuis <previous sha>)`
|
|
140
|
+
and the deploy and the checks run as usual. One commit that touches anything else (source, tests, docs,
|
|
141
|
+
`cadence.yaml` outside its `plan` key) is enough to wait for the CI as before; so is any case where this cannot be
|
|
142
|
+
proved: no previous delivery, a previous delivery outside the history of the sha or equal to it, no plan loaded, an
|
|
143
|
+
unreadable or empty range. `--dry-run` announces it with the same words on its CI line (`CI : non attendue (…)`).
|
|
144
|
+
- **Timestamps and step journal** (L516): every step line of the output (`→ script`, `→ CI`, `→ déploiement i/n`,
|
|
145
|
+
`→ vérification i/n`, `✓ livré`) starts with the UTC time `HH:MM:SS`, and `✓ livré` ends with `en N s`, the time
|
|
146
|
+
since the lock was taken. The failure messages, the exit codes, `deliveries.log` (`day sha`) and
|
|
147
|
+
`deliveries-failed.log` are unchanged. Each step played (the CI wait, each deploy command, each check) also appends
|
|
148
|
+
one JSON line to `<git common dir>/cadence/deliveries-steps.log`, append-only, with the keys `lots` (lots cited since
|
|
149
|
+
the previous delivery, `[]` if none), `sha`, `projet`, `etape` (`ci`, `deploiement i/n`, `verification i/n`), `debut`
|
|
150
|
+
and `fin` (ISO 8601 UTC), `duree_s` (seconds, rounded) and `resultat` (`ok` or `echec`). A failing step writes its
|
|
151
|
+
`echec` line before `deliver` exits 1; steps not played write nothing (no `ci` line when the CI is not waited for or
|
|
152
|
+
`ci: none`). The project script (`deliver.script`) is not a step of this journal. `--dry-run` writes nothing. A write
|
|
153
|
+
error only prints a warning (`deliver : journal des étapes non écrit : <cause>`): the delivery and its code stand.
|
|
136
154
|
- Commands get `CADENCE_SHA`, `CADENCE_SHORT` (7 characters) and `CADENCE_BRANCH` (a `deliver.script` also gets `CADENCE_DEPLOYED_FILE`, see below);
|
|
137
155
|
`${SHA}` and `${SHORT}` are replaced in `url` and `contains`.
|
|
138
156
|
- On success the lots cited by the commits since the previous delivery are
|
package/docs/lead.md
CHANGED
|
@@ -58,7 +58,9 @@ picture, not its source.
|
|
|
58
58
|
soon as the folder keeps several unfinished waves). While the wave is still running the same command (or
|
|
59
59
|
`cadence orchestrate --answer <project>:<lot> "…"`) queues the answer and the live wave replays the lot at
|
|
60
60
|
once, whatever the other lots do (L407). The lead never answers in the
|
|
61
|
-
human's place. `--drop` and `--stop-after-current` steer a wave that is running
|
|
61
|
+
human's place. `--drop` and `--stop-after-current` steer a wave that is running, and
|
|
62
|
+
`cadence orchestrate --add <project>:<lot> [--wave <wave>]` adds a lot to it instead of launching a second
|
|
63
|
+
wave, which is refused in a repository the first one holds; the refusal names this command (L421).
|
|
62
64
|
4. **Review.** The final table of the wave lists the lots ready, handed back, and the line
|
|
63
65
|
« livrable jusqu'à <sha> » for a ready lot stacked under a handed-back one. Minor findings come back in
|
|
64
66
|
the table: one becomes a lot of its own only if it describes an observable bug.
|
package/docs/orchestrate.md
CHANGED
|
@@ -36,6 +36,7 @@ cadence orchestrate --resume … --dry-run # refused: a
|
|
|
36
36
|
cadence orchestrate --drop ccc:L29 [--wave <wave>] # from another terminal: take a lot out of the running wave
|
|
37
37
|
cadence orchestrate --stop-after-current [--wave <wave>] # from another terminal: let the running sessions finish, start nothing else
|
|
38
38
|
cadence orchestrate --answer ol-companion:L22 "reply" [--wave <wave>] # from another terminal: answer a lot of the running wave that is waiting for it (below)
|
|
39
|
+
cadence orchestrate --add ccc:L30 [--add ol-companion:L23] [--wave <wave>] # from another terminal: add lots to the running wave instead of launching a second one (below)
|
|
39
40
|
```
|
|
40
41
|
|
|
41
42
|
**Claude Code must be installed and logged in** (L378). Before starting a lot, `cadence orchestrate` runs `claude auth status` (no model, no cost); when it says `loggedIn: false` the command is refused (exit 2) before anything is written: no `raf start`, no plan commit, the lot stays `todo`. The message says what to do: « claude n'est pas connecté : lancer `claude` puis /login (état : `claude auth status`) ». `--dry-run` starts no session, so it is not refused: it prints the same message after its first line, as a `⚠`. `claude auth status` is read once per launch, so with `--continue` the rounds of a wave do not run it again for every candidate lot. An unreadable answer (a `claude` without `auth status`) is not a refusal.
|
|
@@ -63,6 +64,8 @@ refuse the resume, and a refusal writes nothing to `control.log`.
|
|
|
63
64
|
|
|
64
65
|
**Answering on a live wave (L407).** `--answer <project:lot> "…"` (from the folder that launched the wave, `--wave` when several are live) no longer waits for the wave to end: on 28-10 a lot waited about 15 minutes for its answer because another lot of the wave was still running, and `--resume` refuses a wave that is running. The answer is appended to the wave's queue (`answers.log` in `.cadence/runs/<wave>/`, one JSON line per answer and per answer taken), the command prints `<lot> : réponse déposée` and returns at once. The live wave reads the queue every 2 seconds and each time a lot finishes, applies the answer to the lot (the same state as `--resume --answer`: the answer joins the brief of its next session) and replays it as one more line of the pool, as soon as a session slot is free; a lot with no worktree never plays together with another lot of its repository. The documented form `--resume --wave <wave> --answer …` does the same when that wave is still running (it used to be refused with `tourne encore`); combined with another resume option (`--budget`, `--drop`, `--integrate`, `--continue`) it is still refused. Refused (exit 2) before anything is written: a lot that is unknown, that several projects carry, or that is not waiting for an answer (`question`); no live wave in that folder (the message gives the `--resume --wave <wave> --answer` form for a stopped wave); a wave started before L407, whose snapshot does not read the queue. `--answer` alone does not combine with `--drop`, `--stop-after-current`, `--budget`, `--dry-run`, `--continue` or lots to launch. An answer whose lot is no longer waiting when the wave reads it (dropped, or already answered by an earlier line) is discarded and the journal says so (`réponse écartée`), it is never played twice; if the wave finishes between the check and the write, the answer is withdrawn and the command says to use `--resume`. Not covered: a lot that depends (`after`) on the answered lot and was left waiting in the pool is not started by this wave when the answer makes the lot ready; `--resume` plays it.
|
|
65
66
|
|
|
67
|
+
**Adding a lot to a live wave (L421).** A second `cadence orchestrate <project:lot>` in a repository that a live wave holds is refused (`une orchestration y est déjà en cours (<wave>, pid …)`), and the lead used to wait for the whole wave to end. The refusal now names the way out when that wave was launched from the same folder: `… ; pour y ajouter ce lot : cadence orchestrate --add <project>:<lot> --wave <wave>`. `--add <project:lot> [--wave <wave>]` (repeatable; from the folder that launched the wave, `--wave` when several are live) checks each lot like a lot given at launch (known, open, dependencies, Node, `repos:`), with three differences: a repository held **by that wave** is accepted (its dirty tree and its push guard are the wave's own), a dependency counts when it is a lot of the wave or an earlier `--add` of the same call, and the remaining budget of the wave (`budget − consumed`) must hold the budget of the lot. Then it appends the lot to the wave's queue (`adds.log` in `.cadence/runs/<wave>/`, one JSON line per request and per request taken), prints `<lot> : ajout déposé` and returns at once. The live wave reads the queue with the answers (every 2 seconds and each time a lot finishes), checks the lot again at that moment, and adds it: the lot is written in the wave state and in `wave.json`, the repository lock and push guard are taken if the wave does not hold the repository (any more), and the journal says `<lot> : ajouté à la vague <wave>`. The lot then plays as one more line of the pool as soon as a session slot is free; a lot of a repository where another lot of the wave is playing waits for it (never two sessions in one repository), and plays in its own worktree only when every lot still to play in that repository does. Refused (exit 2) before anything is written, nothing queued for any lot as soon as one is refused: a lot unknown or already finished, a lot already in the wave or already queued, a dependency neither done nor in the wave, a repository held by another wave, a budget that does not fit, no live wave in that folder, several live waves without `--wave`, a wave started before L421 (its snapshot does not read the queue). `--add` combines with nothing but `--wave`. A request that no longer holds when the wave reads it (lot done meanwhile, repository taken by another wave, budget spent, `--stop-after-current` requested, usage limit, incident) is discarded and the journal says so (`ajout écarté (<cause>)`) — a request kept waiting for its repository is checked again at the moment it is taken, so a stop requested or a budget spent while it waited discards it too, and the lot never enters `wave.json`; the same goes for a request still queued when no session is left to play (`la vague a fini de jouer`) — if the wave ends between the check and the write, the command withdraws the request and says to launch the lot in a new wave. A legitimate commit of an added lot on the main branch of its repository is not an incident for a session already running in a sibling repository (L456). With `--continue`, the lots added by hand count as lots of the wave and are not drawn a second time. The case of `--resume --integrate` refused while another wave holds the repository is not part of `--add`: the resume waits for that wave (L443).
|
|
68
|
+
|
|
66
69
|
**`--goal "…"` and `goal:` — the objective of the briefs when the title is stale (L258)**: the first line of the `{{objectif}}` of every
|
|
67
70
|
brief of a lot (and the title quoted in their first sentence) is the lot's title; when a change of scope made it stale, the lead no longer
|
|
68
71
|
has to delegate by hand. Three ways to give the goal, in this order of precedence (`--goal`, then the plan's key, then the title):
|
package/docs/releasing.md
CHANGED
|
@@ -51,7 +51,7 @@ Between the version commit and the gate it also prints a **provisional local tag
|
|
|
51
51
|
while an existing one is left alone. If the package is on npm but the release is still missing, re-run the job;
|
|
52
52
|
the by-hand fallback is `gh release create v<version> --title v<version> --notes-file <the section of CHANGELOG.md> --verify-tag`.
|
|
53
53
|
|
|
54
|
-
`.github/workflows/tests.yml` runs the type-check and the test suite on the same runner image and `npm ci` as the publish job, in a matrix of Node 22 and 24 (the minimum `engines.node` allows, and the Node the publish job uses), and never publishes: it starts on every push to `main` (L446: each run is a fresh checkout, which no local tree is), with `gh workflow run tests.yml --ref <branch>` or by pushing a `diag/**` branch, to see on the runner what a red publish run saw.
|
|
54
|
+
`.github/workflows/tests.yml` runs the type-check and the test suite on the same runner image and `npm ci` as the publish job, in a matrix of Node 22 and 24 (the minimum `engines.node` allows, and the Node the publish job uses), and never publishes: it starts on every push to `main` (L446: each run is a fresh checkout, which no local tree is), with `gh workflow run tests.yml --ref <branch>` or by pushing a `diag/**` branch, to see on the runner what a red publish run saw. Its last step plays the suite inside `npm publish --dry-run`; on a commit that is not a release, `package.json` carries an already published version and npm ends that dry-run with « You cannot publish over the previously published versions » once the suite has passed (L515). That refusal alone, read on an npm error line as `publish.yml` reads it (`EPUBLISHCONFLICT` / « previously published »), is tolerated; any other failure, a red suite included, keeps the step red.
|
|
55
55
|
|
|
56
56
|
The suite runs the same way on your machine as on that runner, **without `raf` or `cadence` installed** (L384): `test/global-setup.ts` replaces, in the `PATH` of the tests, every folder outside the repository that carries a `raf` or a `cadence` (`~/.local/bin`, the global npm folder, the plugin's `bin/`) by a mirror of that folder without those two names — the other tools stay, `node` included. A test that relies on the installed tool turns red on the lead's machine and in the gate, not only in the publish run: the 1.16.0 publication failed because the post-commit hook of `test/raf-init-commit.test.ts` (R2) found `raf` in the developer's `PATH` and not on the runner. A test that needs `raf` (a hook, a script) builds its own from the sources (`CADENCE_TEST_PACKAGE`).
|
|
57
57
|
|
package/docs/whats-new.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## In two lines
|
|
6
6
|
|
|
7
|
+
- **1.36.0**: `cadence deliver` no longer waits for the CI of a sha whose commits only touch the plan, with timed steps and a `deliveries-steps.log` journal; `orchestrate --add` puts a lot in a live wave.
|
|
7
8
|
- **1.35.0**: a test already red before a lot, or that only needs more time, no longer costs correction passes, including on a gate declared in parts; QA passes run in browsers of their own; design agents check contrast and AI tics.
|
|
8
9
|
- **1.34.1**: republishes 1.34.0, whose npm publication failed after the tag; same features (edge-case probe, steadier `mise-en-service` tests).
|
|
9
10
|
- **1.34.0**: an optional edge-case probe (`orchestrate.probe: true`) tries to break a lot's diff with tests at the edges before the code review; flaky tests of `mise-en-service` made deterministic.
|
|
@@ -63,6 +64,8 @@ Each is a paragraph in [docs/whats-new.md](whats-new.md) and a full section in [
|
|
|
63
64
|
|
|
64
65
|
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).
|
|
65
66
|
|
|
67
|
+
**1.36.0**: `cadence deliver` stops waiting for a CI that proves nothing. When every commit since the previous delivery touches only the plan, it says `→ CI : non attendue (aucun fichier hors plan depuis <sha>)`, then runs the deploy and the checks; as soon as one commit touches anything else, or the proof is missing (no previous delivery, rewritten history, no plan, empty range), it waits as before (L516, `docs/deliver.md`). 42 % of the app deliveries measured on 2026-11-01 were such shas. Every step line of the output now starts with its UTC time and `✓ livré` ends with its duration, and each step played appends a JSON line to `deliveries-steps.log`, so a slow delivery can be read step by step. `cadence orchestrate --add <project:lot> [--wave <wave>]` adds a lot to a live wave instead of launching a second one: the lot is checked like a launched one, queued in the wave's `adds.log` and played as soon as a session slot is free; the refusal of a second wave in the same repository now names this command, with the lot and the wave (L421, `docs/orchestrate.md`). A session running in a sibling repository is no longer handed back for a commit on another repository's main branch once a lot was added to the wave.
|
|
68
|
+
|
|
66
69
|
**1.35.0**: the wave's gate stops paying for reds that need no fix. With `orchestrate.testReplay`, a red that stays red alone in the lot is replayed alone at the start sha, and one already failing there is « déjà rouge »: no correction pass, no escalation; a test that expired in the whole suite and passes alone at 80 % or more of its delay is a « délai trop juste », a warning naming the test with its duration and its delay (L510, `docs/orchestrate.md`). On a gate played in several folders, `orchestrate.test` takes a list of parts `[{ dir, run, replay? }]` and each part plays, replays and attributes its own reds, so a composed gate gets the same two verdicts (L512, `docs/orchestrate.md`, « A gate declared in parts »; evatosorus `node --test` is not covered yet). `cadence corrections` now counts the passes launched on tests that ended without a code change (L510, `docs/corrections.md`). The gate can also run on another machine through `orchestrate.gate.host`, by ssh at the pushed sha, with a local fallback and a lost connection replayed locally, never counted red (L403, `docs/remote-gate.md`). The `qa-reviewer` opens a browser context and a profile of its own, so two QA passes started together no longer share a tab (L511, `docs/skills.md`). The `spec-writer` joins a `contraste.md` (WCAG 2.2 ratios, 4.5:1 at least) to each sheet, and the `ux-reviewer` gets a delivery grid and a static check of AI tics (L513, `docs/orchestrate.md`). Small fixes: `cadence release` prints its local tag before the gate and removes it when red (L508, `docs/releasing.md`), `raf ux` and `raf review` print the commit command for the regenerated plan (L509), and `cadence deliver` outside a wave commits the plan it wrote for the « article à rafraîchir » lot (L420, `docs/news.md`).
|
|
67
70
|
|
|
68
71
|
**1.34.1**: 1.34.0 was tagged but its publication to npm failed (a CHANGELOG entry cited a lot with no commit in the tag); 1.34.1 publishes the same content, with that entry reworded. Install 1.34.1 to get the edge-case probe and the steadier tests of 1.34.0.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylad/cadence",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.36.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",
|