@sylad/cadence 1.30.0 → 1.31.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.
@@ -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.30.0",
11
+ "version": "1.31.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.30.0",
4
+ "version": "1.31.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/audit.js CHANGED
@@ -2,7 +2,7 @@ import { existsSync } from 'node:fs';
2
2
  import { dirname, join, relative } from 'node:path';
3
3
  import { check } from './check.js';
4
4
  import { parse } from 'yaml';
5
- import { changedFiles, fileAt, preloadChangedFiles, readCommits } from './git.js';
5
+ import { changedFiles, fileAt, isAncestor, preloadChangedFiles, readCommits } from './git.js';
6
6
  import { citedRefs, linkCommits, scopeOf } from './link.js';
7
7
  import { PLAN_DEFAULT, PLAN_LEGACY, readDocsConfig, readNewsConfig, readOrchestrateConfig } from './config.js';
8
8
  import { ficheHypotheses } from './spec.js';
@@ -303,6 +303,42 @@ function unreviewed(plan, root, review, linked) {
303
303
  // Seuls les commits postérieurs sont examinés : un lot relu à son dernier commit ne coûte aucun appel git.
304
304
  return workCommits(plan, root, at < 0 ? linked : linked.slice(0, at));
305
305
  }
306
+ /** Un texte de verdict de revue dit « non conforme » / « not compliant » (le mot « conforme » dedans ne compte pas) ; « conforme après 1 correction » reste conforme. */
307
+ export function isNonConformingVerdict(text) {
308
+ return /\bnon[\s-]*conforme|\bnot\s+compliant|\bnon[\s-]*compliant/i.test(text);
309
+ }
310
+ /**
311
+ * Les lots dont les commits de travail sont dans l'histoire de `sha` après `base` (toute l'histoire sans `base`), avec leur état de livraison (L474, hors vague) :
312
+ * `done` et abandonné ne bloquent pas ; un lot « prêt » (son verdict de revue, conforme, couvre tous ses commits) non plus ; un lot à faire ou en cours, non relu
313
+ * ou relu avant des commits postérieurs, bloque. Un commit qui cite un lot que le plan ne connaît pas (clos et archivé, ou inconnu) n'en fait pas un lot.
314
+ */
315
+ export function deliveryLots(plan, root, sha, base) {
316
+ const inRange = planCommits(plan, root, { range: base ? `${base}..${sha}` : sha });
317
+ const { byLot } = linkCommits(plan.lots(), inRange, plan.refs);
318
+ const lots = [];
319
+ for (const l of plan.lots()) {
320
+ if (!workCommits(plan, root, byLot.get(l.id) ?? []).length)
321
+ continue;
322
+ const entry = (blocking, state) => lots.push({ id: l.id, title: l.title, blocking, state });
323
+ if (l.status === 'done')
324
+ entry(false, 'terminé');
325
+ else if (l.status === 'dropped')
326
+ entry(false, 'abandonné');
327
+ else {
328
+ // Couverture jugée sur les commits du lot jusqu'à `sha` seulement : un `fix` posé au-dessus du sha livré n'est pas livré.
329
+ const late = l.review ? unreviewed(plan, root, l.review, lotCommits(plan, root, l.id)).filter((c) => isAncestor(root, c.sha, sha)).length : 0;
330
+ const state = l.status === 'todo' ? 'à faire' : 'en cours';
331
+ // Prêt = revue CONFORME couvrant les commits (côté vague, un lot n'est ready qu'après une revue conforme).
332
+ if (l.review && late === 0 && !isNonConformingVerdict(l.review.verdict))
333
+ entry(false, 'prêt');
334
+ else if (l.review && late === 0)
335
+ entry(true, `${state}, relu non conforme`);
336
+ else
337
+ entry(true, `${state}, ${l.review ? `relu avant ${late} commit(s)` : 'non relu'}`);
338
+ }
339
+ }
340
+ return lots;
341
+ }
306
342
  /** Nombre de commits d'un lot que sa revue de code ne couvre pas — ce que `raf done` refuse quand la porte est active. */
307
343
  export function unreviewedWork(plan, root, lotId) {
308
344
  const lot = plan.lots().find((l) => l.id === lotId);
package/dist/cli.js CHANGED
@@ -28,6 +28,7 @@ import { configuredCap, expiredHoldLines, slotsLine, orchestrate, realOrchestrat
28
28
  import { addHold, releaseHold } from './orchestrate/registry.js';
29
29
  import { activeLock, REPO_LOCK } from './orchestrate/lock.js';
30
30
  import { INTEGRATION_LOCK } from './orchestrate/integrate.js';
31
+ import { mainTreeWorkers } from './orchestrate/main-tree.js';
31
32
  import { cadenceHome, liveWaves, openLotsIn, openWavesIn, waveLotsIn } from './orchestrate/registry.js';
32
33
  import { blockingLots, LABEL, resumeCommand } from './orchestrate/table.js';
33
34
  import { bugCodeOf, bugCodePasses, classifyAll, classifySous, measure, ollamaClassifier, ollamaSousClassifier, readCache, readJournals, readRuns, readSousCache, renderReport, sampleKeys, serializeCache, serializeSousCache } from './corrections.js';
@@ -423,6 +424,7 @@ function dispatch(argv, io) {
423
424
  plan.save();
424
425
  io.out(id);
425
426
  warnIntegrating(root, io.err);
427
+ warnWaveWorking(root, io.env, io.err);
426
428
  return 0;
427
429
  }
428
430
  case 'start':
@@ -464,8 +466,10 @@ function dispatch(argv, io) {
464
466
  plan.note(rest[0], `abandonné : ${values.reason}`, today);
465
467
  plan.save();
466
468
  io.out(`${rest[0]} → ${status}`);
467
- if (status === 'done')
469
+ if (status === 'done') {
468
470
  warnIntegrating(root, io.err);
471
+ warnWaveWorking(root, io.env, io.err);
472
+ }
469
473
  touchedLot(plan, rest[0], values.verbose, io);
470
474
  if (status === 'done' && !rest[0].includes('/')) {
471
475
  const lot = plan.lot(rest[0]);
@@ -522,6 +526,7 @@ function dispatch(argv, io) {
522
526
  plan.note(rest[0], rest.slice(1).join(' '), today);
523
527
  plan.save();
524
528
  warnIntegrating(root, io.err);
529
+ warnWaveWorking(root, io.env, io.err);
525
530
  touchedLot(plan, rest[0], values.verbose, io);
526
531
  return 0;
527
532
  }
@@ -1219,6 +1224,23 @@ function warnIntegrating(root, err) {
1219
1224
  if (holder)
1220
1225
  err(`avertissement : intégration en cours (vague ${holder.wave}, pid ${holder.pid}) — ce plan est un fichier suivi modifié de l'arbre principal, l'intégration en cours peut refuser son lot (arbre du dépôt modifié) : committer le plan sans attendre`);
1221
1226
  }
1227
+ /**
1228
+ * Avertit (stderr, sans refuser) quand une session de vague écrit dans l'arbre principal du dépôt (L475) : un lot en implémentation, revue ou correction sans worktree.
1229
+ * Un commit de plan du lead à ce moment-là rend le lot (cas du 30-10 : cadence L458 rendu par le commit du plan). Sans effet dans le worktree d'un lot, ou depuis une session de la vague (c'est elle qui tient l'arbre).
1230
+ */
1231
+ function warnWaveWorking(root, env, err) {
1232
+ if (env.CADENCE_ORCHESTRATED)
1233
+ return;
1234
+ const top = gitRoot(root);
1235
+ if (!top || !statSync(join(top, '.git')).isDirectory())
1236
+ return;
1237
+ const holder = activeLock(join(sharedStateDir(root), REPO_LOCK));
1238
+ if (!holder)
1239
+ return;
1240
+ const working = mainTreeWorkers(cadenceHome(), holder.pid, top);
1241
+ if (working?.length)
1242
+ err(`avertissement : la vague ${holder.wave} (pid ${holder.pid}) travaille dans l'arbre principal de ce dépôt (${working.join(', ')}) — un commit de plan maintenant rendrait ces lots à la revue ; attendre la fin de leur étape, ou écrire le plan sans le committer`);
1243
+ }
1222
1244
  /** La vague qui tient le verrou du dépôt tant qu'un de ses lots y travaille encore (état illisible : on la tient pour active) ; null si le dépôt est libre. Partagé par `deliver` et `spec reseal`. */
1223
1245
  function waveHolding(root) {
1224
1246
  const orchestrating = activeLock(join(sharedStateDir(root), REPO_LOCK));
package/dist/deliver.js CHANGED
@@ -4,7 +4,7 @@ import { constants } from 'node:os';
4
4
  import { join, relative } from 'node:path';
5
5
  import { parse } from 'yaml';
6
6
  import { onTermination, readProcs, SIGNAL_GRACE_MS, SIGNAL_PENDING_MS, TreeTracker } from './proc.js';
7
- import { isPlanOnly } from './audit.js';
7
+ import { deliveryLots, isPlanOnly } from './audit.js';
8
8
  import { commitPaths, dirtyPaths, headSha, isAncestor, onRemote, readCommits, repoStatus, resolveCommit } from './git.js';
9
9
  import { PLAN_ACTIONS, planCommitMessage } from './plan-commit.js';
10
10
  import { citedRefs } from './link.js';
@@ -235,6 +235,7 @@ export function describeCheck(c, sha) {
235
235
  return c.command;
236
236
  return `GET ${substitute(c.url, sha)} → ${c.status ?? 200}${c.contains === undefined ? '' : `, contient « ${substitute(c.contains, sha)} »`}`;
237
237
  }
238
+ const guardMessage = (sha, lots) => `${sha.slice(0, 7)} contient des commits de lots ni terminés ni prêts : ${lots.map((l) => `${l.id} (${l.state})`).join(', ')} ; livrer un sha antérieur, ou relire ces lots (raf review) avant`;
238
239
  /** Livre HEAD. 0 livré, 1 étape en échec, 2 refus avant d'agir. */
239
240
  export async function deliver(ctx, deps) {
240
241
  const { config, out, err } = ctx;
@@ -259,6 +260,24 @@ export async function deliver(ctx, deps) {
259
260
  err(`deliver : ${dirty} fichier(s) suivi(s) modifié(s) : non livré(s), seul ${sha.slice(0, 7)} l'est (allowDirty)`);
260
261
  if (!onRemote(ctx.root, sha))
261
262
  return refuse(`${sha.slice(0, 7)} non poussé : la CI n'a rien construit (git push)`);
263
+ // Hors vague, un sha choisi emporte les commits de TOUS les lots sous lui (« L49 seul » a livré L50 et L51 non relus, 30-10) : on les nomme, avec leur état.
264
+ // Pendant une vague, la garde de la vague (cli, `blockingLots`) en tient lieu.
265
+ let undelivered = [];
266
+ if (ctx.sha !== undefined && !ctx.duringWave && ctx.plan) {
267
+ const last = lastDelivery(ctx.state);
268
+ const from = last && isAncestor(ctx.root, last, sha) ? last : null;
269
+ const lots = deliveryLots(ctx.plan, ctx.root, sha, from);
270
+ if (ctx.dryRun) {
271
+ out(`Lots de l'intervalle ${from ? `${from.slice(0, 7)}..` : 'jusqu\'à '}${sha.slice(0, 7)} :`);
272
+ if (lots.length === 0)
273
+ out(' (aucun)');
274
+ for (const l of lots)
275
+ out(` ${l.id} « ${l.title} » — ${l.state}`);
276
+ }
277
+ undelivered = lots.filter((l) => l.blocking);
278
+ if (undelivered.length && !ctx.dryRun)
279
+ return refuse(guardMessage(sha, undelivered));
280
+ }
262
281
  const lock = readLock(ctx.state);
263
282
  if (lock && lockAlive(lock)) {
264
283
  return refuse(`livraison déjà en cours (${lock.sha.slice(0, 7)}, pid ${lock.pid}, depuis ${lock.started}) — jamais deux à la fois ; ` +
@@ -292,6 +311,8 @@ export async function deliver(ctx, deps) {
292
311
  out(` Vérifications (réessayées pendant ${config.verifyTimeout} s) :`);
293
312
  config.verify.forEach((c, i) => out(` ${i + 1}. ${describeCheck(c, sha)}`));
294
313
  out(` Variables : ${Object.entries(env).map(([k, v]) => `${k}=${v}`).join(' ')}`);
314
+ if (undelivered.length)
315
+ return refuse(guardMessage(sha, undelivered));
295
316
  return 0;
296
317
  }
297
318
  if (lock && removeStaleLock(ctx.state, lock)) {
@@ -0,0 +1,34 @@
1
+ import { existsSync, realpathSync } from 'node:fs';
2
+ import { liveWaves } from './registry.js';
3
+ import { lotKey, lotRepoPaths, RunStore } from './state.js';
4
+ import { lotTreeDir } from './worktree.js';
5
+ /** Les étapes d'un lot où une session de la vague écrit dans le dépôt (L475) : le lot en file, en question, prêt, rendu, en échec ou suspendu n'y touche pas. */
6
+ const WRITING = new Set(['implementing', 'reviewing', 'fixing']);
7
+ const real = (p) => {
8
+ try {
9
+ return realpathSync(p);
10
+ }
11
+ catch {
12
+ return p;
13
+ }
14
+ };
15
+ /**
16
+ * Lots de la vague vivante `pid` dont une session travaille dans l'ARBRE PRINCIPAL de `repo` (L475) : un lot en implémentation, revue ou correction qui ne joue pas dans un worktree
17
+ * (vague sans worktree, ou lot dont le dossier de worktree n'existe pas). Un commit de plan du lead y est un commit de plus dans l'historique que la revue ou l'intégration compare
18
+ * à celui du lot : le lot est rendu (cas du 30-10). Clés `projet:lot` ; liste vide si aucun ; null si l'état de la vague est illisible.
19
+ */
20
+ export function mainTreeWorkers(home, pid, repo) {
21
+ try {
22
+ const w = liveWaves(home).find((x) => x.pid === pid);
23
+ const store = w && RunStore.find(w.cwd, w.wave);
24
+ if (!store)
25
+ return null;
26
+ return store
27
+ .lots()
28
+ .filter((l) => WRITING.has(l.status) && lotRepoPaths(l).some((r) => real(r) === real(repo)) && !existsSync(lotTreeDir(store.dir, l.project, l.lot, l.repo)))
29
+ .map((l) => lotKey(l.project, l.lot));
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
package/docs/deliver.md CHANGED
@@ -172,6 +172,15 @@ itself never reads the working tree: a tracked file modified outside the deliver
172
172
  sha (the plan after `raf done`, say) neither refuses nor is reported, with or
173
173
  without `allowDirty`.
174
174
 
175
+ **Outside a wave: the lots under the sha (L474).** `--sha` also refuses, with no wave running, a sha whose history carries work commits of a lot of the
176
+ plan that is neither done nor ready — a commit cited « L49 » that sits above commits of L50 and L51 delivers them too (30-10: `09217b0` « L49 alone »
177
+ shipped two lots nobody had reviewed). The interval checked runs from the last delivery of the journal (when it is in the history of `<sha>`) up to
178
+ `<sha>`; with no such delivery, the whole history, the plan's adoption day and the plan-only, acknowledged and release commits left out as everywhere. A lot passes
179
+ when it is `done` or dropped, or **ready**: its code review verdict is compliant and covers all its commits (a verdict reading `non conforme` / `not compliant` keeps the lot back, state `relu non conforme`). A lot to do or in progress, not reviewed or reviewed before later commits, holds the sha back; the
180
+ refusal (exit 2, before anything runs) names each such lot with its state. `--dry-run` prints every lot of the interval with its state (`terminé`, `prêt`,
181
+ `abandonné`, `en cours, non relu`, `à faire, non relu`, `en cours, relu avant N commit(s)`, `en cours, relu non conforme`) after the same refusal checks, and refuses the same way when one blocks. A plain `cadence deliver` (HEAD) and a delivery during a wave (the
182
+ wave's own guard, below) do not run this check; a commit that cites a lot the plan does not know (archived, unknown) makes no lot.
183
+
175
184
  **While a wave runs.** `cadence orchestrate` holds the repository: a plain `cadence deliver` is refused until every lot of the
176
185
  repository in the wave is ready or handed back. `--sha` is the exception (L277): it is accepted while the wave runs when no
177
186
  commit in the history of `<sha>` belongs to a lot of the wave that is not ready (running, queued, in question, suspended,
package/docs/overview.md CHANGED
@@ -80,7 +80,7 @@ Also: [news](news.md) (changelog, UX, code and QA reviews, documentation that fo
80
80
  - **raf** (French *reste à faire*, "what is left to do"): the plan, linked to your commits by the lot id in the message, computed from `git log` and never stored.
81
81
  - **news**: a user-facing changelog with screenshots, tied to the plan; a lot is not done until its entry is written.
82
82
  - **session**: `session start` and `session close` gather the facts of a work session (each lot in progress shows its commit count, `0 commit` included, next to its last activity).
83
- - **deliver**: wait for the CI of the pushed commit, run the deploy commands, then **verify the effect**; a delivery is done only when its checks pass. A failed delivery is logged as a *red delivery* with its class (refusal, CI, deploy, check) and the last lines of the failing command's output. While a wave runs, `cadence deliver --sha <sha>` is accepted when no commit of the sha belongs to a lot that is not ready, so a ready lot ships while the wave runs.
83
+ - **deliver**: wait for the CI of the pushed commit, run the deploy commands, then **verify the effect**; a delivery is done only when its checks pass. A failed delivery is logged as a *red delivery* with its class (refusal, CI, deploy, check) and the last lines of the failing command's output. While a wave runs, `cadence deliver --sha <sha>` is accepted when no commit of the sha belongs to a lot that is not ready, so a ready lot ships while the wave runs. Outside a wave, `--sha` refuses a range that holds commits of a lot that is neither done nor ready (its code review covering its commits up to the sha, not above), and `--dry-run` lists the lots of the range with their state (L474).
84
84
  - **auto-verification** (L236): before it reports, an `implement` or `fix` session re-reads the complete diff of its lot (of its fix commits, in `fix`) against the grid of the `code-reviewer` (code bug, architecture drift, architecture documentation, edge cases, one test per changed behaviour), fixes what it finds and writes an « auto-vérification » paragraph in its report; the measure is `cadence corrections --classify` and the share right the first time of `cadence perf`.
85
85
  - **real walkthrough and decisions** (L344): once the tests are green an `implement` session plays what it delivered as a user (the command in a throwaway repository following its own documentation, the screen in each state with the failure in view) and reports it under « parcours réel »; the notes of the lot and of its parent that record a decision of the human reach the `implement` and `fix` briefs under « Decisions to respect », a departure being a question, never a choice (`docs/orchestrate.md`).
86
86
  - **perf**: `cadence perf` is the dashboard of the workflow (speed next to quality, `⚠` on a trend), including the red deliveries and the blocking QA findings; `cadence lead tour` opens with its *perf header*, one line per `⚠`, nothing when stable; a rise of the tokens per lot names the step that carries it, and when the share right the first time rose too it reads as quality paid for, not a wrong estimate ([docs/perf.md](perf.md)).
package/docs/raf.md CHANGED
@@ -60,6 +60,7 @@ raf gantt # .cadence/gantt.html (next to the plan)
60
60
  | `raf uses <lot> [<resource>…]` | replace the `uses:` list of a lot of any status (`done` and `dropped` included) by the names given (duplicates removed, order kept); with no name, remove the key. Prints nothing. A name is a word of lowercase letters, digits, `-` and `_` (else `ressource invalide : <name> (lettres minuscules, chiffres, - et _)`, plan unchanged); a sub-task is refused (`uses se pose sur un lot, pas sur une sous-tâche : L1/t1`); an unknown lot is `lot inconnu : <id>`. A name not yet declared is accepted: `raf check` refuses it (L251) |
61
61
  | `raf move <id> --before\|--after <id>` | reorder a lot in the plan file (L400): `<id>` is placed right before or right after the other lot, the others keep their order, and the lot travels with all its keys, its sub-tasks and its comments. Only among siblings: a lot among lots, a sub-task (`L1/t1`) among the sub-tasks of its own lot (`move : L2 et L1/t1 n'ont pas le même parent …`, plan unchanged). Exactly one of `--before` / `--after`, one id (else `usage : raf move <id> --before <id> \| raf move <id> --after <id>`); a lot placed where it already is, or an unknown id (`lot inconnu : <id>`), changes nothing. **Refused** on a read-only plan, plan kept by another tool (`plan en lecture seule : … — le modifier avec l'outil du projet`: reorder it with the project's own tool), on an archived lot (`move : L1 est archivé …`: the archive is presented in its former order and the order of the plan file never moves, see `raf archive`) and while a wave holds the repository (`move : orchestration en cours (vague …, pid …) — attendre …`: the lots of the wave rely on the order they read; the lock falls as soon as all of them are ready or handed back). The order is the order of the file, so it is the order `raf list`, `raf now`, the Gantt page and the draw of `cadence orchestrate` follow; the command only writes the plan, it does not commit it (`plan.commit` prints the command, as for every plan write) |
62
62
  | `raf add`, `raf note`, `raf done` during an integration (L425) | while a wave integrates a lot of the repository (the integration lock is held by a live process), these three commands write the plan as usual and print on stderr `avertissement : intégration en cours (vague …, pid …) — …`: the plan is a tracked file of the main tree, and an integration that sees it modified refuses its lot (`arbre du dépôt modifié`, 2026-10-29: a `raf add` of the lead during the integration of L416). Commit the plan right away. Exit code 0; nothing is printed in a lot's worktree (its plan is not the tree the integration checks) nor when the lock belongs to a dead process |
63
+ | `raf add`, `raf note`, `raf done` while a wave works in the main tree (L475) | when the repository's orchestration lock is held by a live wave and one of its lots is in `implementing`, `reviewing` or `fixing` **without a worktree** (a wave without worktrees, or a lot whose worktree folder does not exist), these commands write the plan as usual and print on stderr `avertissement : la vague … travaille dans l'arbre principal de ce dépôt (projet:lot) — …`: a plan commit of the lead then hands the lot back at review (2026-10-30: cadence L458 handed back by a plan commit during its review). Write the plan without committing it, or commit once the step ends. Exit code 0; nothing is printed for lots that are queued, in question, ready, handed back, failed or suspended, for lots playing in their worktree, from a worktree of the repository, from a session of the wave itself (`CADENCE_ORCHESTRATED` set), when the lock belongs to a dead process, or when the wave's state is unreadable |
63
64
  | `raf public <id> "title"` · `raf public <id> --clear` | set, replace or remove the public title of a lot |
64
65
  | `raf start <id>` · `raf done <id> [--force]` · `raf drop <id> [--reason text]` | dated transitions (`done` refuses open sub-tasks unless `--force`, and a lot whose `acceptation-<lot>.json` is still `vert: false`, L366: closed like that it would lock every later wave of the repository, see `cadence spec green`; with `--force` it only warns on stderr. Like every `raf` command it only writes the plan file and **never commits** it: when the plan or a file `plan.onWrite` produces is left with changes, `done` prints the `git add` / `git commit` command that takes them, the subject from the `plan.commit` template, L371) |
65
66
  | `cadence spec green <lot>` | L366: run the dedicated acceptance command (`orchestrate.accept`) on a lot driven or closed by hand; green, set `vert: true` in its `acceptation-<lot>.json` and commit that file alone (subject `chore(<lot>): acceptation verte, vert: true posé par cadence spec green` or the `plan.commit` template; the resealed tests are listed in the commit body, never in the subject), resealing the fingerprints of the listed tests refined since the red commit (journal `resceaux`) so the lock goes quiet; a listed test changed but not committed is refused. Red: exit 1, nothing changed. Refused for a `todo` lot, during a wave, and while an unfinished wave keeps a lot of the repository open; see [orchestrate](orchestrate.md) |
package/docs/whats-new.md CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  ## In two lines
6
6
 
7
+ - **1.31.0**: `deliver --sha` refuses, outside a wave too, a sha that carries commits of a lot neither done nor ready (a `non conforme` verdict is not ready); `raf add/note/done` warn when a wave session writes in the main tree.
7
8
  - **1.30.0**: a question can be answered while its wave still runs; the sibling-repository check of 1.29.0 only flags commits that cite a lot of the wave; `session context` reads the lead's threshold from the lead's folder.
8
9
  - **1.29.0**: a wave session can no longer commit on the main branch of another repository than its lot's (the lot is handed back with the finding); `verify --gate` no longer links data folders into its throwaway copy.
9
10
  - **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.
@@ -57,6 +58,8 @@ Each is a paragraph in [docs/whats-new.md](whats-new.md) and a full section in [
57
58
 
58
59
  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).
59
60
 
61
+ **1.31.0**: `cadence deliver --sha <sha>` refuses, outside a wave too, a sha whose history since the last delivery carries work commits of a lot that is neither done nor ready — ready meaning a compliant code review that covers the lot's commits up to that sha, so a fix stacked above a reviewed sha does not block it and a `non conforme` verdict does — and `--dry-run` lists the lots of the interval with their state (L474, `docs/deliver.md`). Before, the check only ran during a wave of the repository: on 30-10 a sha delivered as « L49 alone » took two unreviewed lots whose commits sat beneath it. `raf add`, `raf note` and `raf done` warn on stderr when a session of a live wave writes in the main tree of the repository (L475, `docs/raf.md`): a plan commit of the lead at that moment hands the lot back, as it did on 30-10.
62
+
60
63
  **1.30.0**: `cadence orchestrate --answer <project>:<lot> "…"` (or `--resume --wave <wave> --answer`) is accepted while the wave is still running (L407, `docs/orchestrate.md`): the answer goes into a queue the live wave reads, the lot takes a slot again under the wave's cap, plays in its own worktree when the mode is on, and never runs beside another session of the same repository; the registry counts it at once, so « créneaux libres » stays right. Before, the lead had to wait for the end of the wave. The check of 1.29.0 against commits on the main branch of a sibling repository (L456) now flags only the commits that cite a lot of the wave (L458): a delivery's tag bump in a GitOps repository, the lead's plan commits and the lots of another live wave launched later no longer hand a lot back — three false positives on 30-10. `cadence session context` reads `lead.contextThreshold` from the lead's folder, the first parent holding `lead:` or `priority:`, instead of the current folder (L457).
61
64
 
62
65
  **1.29.0**: two incidents of 30-10 turned into mechanisms. A writing session of a wave no longer commits on the main branch of a repository its lot does not declare (L456, `docs/orchestrate.md`): the brief forbids it and names the way, a branch `wip/<project>-<lot>`, and at the end of each session the program compares the main branches of the sibling repositories of the folder; a commit found there hands the lot back with the repository, the branch and the shas, and never moves or reverts anything — the lead moves the commits. Commits of the lead (subject `plan:`, plan file only) and commits of lots of another live wave of the folder are tolerated; repositories declared by the lot (`repos:`) keep their behaviour. The case was a session that committed a chart on the production branch of a GitOps repository. `cadence verify --gate` no longer links the ignored folders of the main tree that are not dependencies (`data/`, `data-local/`, `.env`) into its throwaway copy (L455, `docs/deliver.md`): a test comparing paths by `realpath` went red in the copy and green in the main tree at the same sha, and stopped a delivery.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sylad/cadence",
3
- "version": "1.30.0",
3
+ "version": "1.31.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",