@sylad/cadence 1.19.1 → 1.20.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.19.1",
11
+ "version": "1.20.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.19.1",
4
+ "version": "1.20.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
@@ -46,6 +46,7 @@ const HELP = `raf — plan « reste à faire » versionné dans le dépôt, reli
46
46
  raf screen <id> page|component|journey|none pose, remplace ou retire screen: d'un lot visible existant (seule la valeur page adopte l'étape spec d'orchestrate)
47
47
  raf visible <id> on|off pose ou retire visible: d'un lot existant (changement visible : entrée Nouveautés et étape UX d'orchestrate) ; off est refusé tant que le lot a un screen:
48
48
  raf spec <id> on|off pose ou retire spec: d'un lot existant (le lot change un comportement : étape spec back d'orchestrate)
49
+ raf move <id> --before|--after <id> réordonne un lot (ou une sous-tâche) dans le plan, parmi ceux du même parent ; refusé pendant une vague du dépôt, sur un lot archivé et sur un plan en lecture seule
49
50
  raf uses <lot> [<ressource>…] remplace les ressources partagées du lot (uses:, déclarées par resources: dans le cadence.yaml du dossier parent ; raf check refuse un nom non déclaré) ; sans nom, retire la clé
50
51
  raf start <id> raf done <id> [--force] raf drop <id> [--reason texte]
51
52
  raf todo <id> [--reason texte] remet en attente un lot démarré sans commit de travail (doing → todo, started retiré, note datée) ; refusé s'il a des commits ; rouvre un lot done ou dropped (→ todo, finished retiré, note « rouvert ») — la seule façon de rouvrir, archivé compris (il revient dans le fichier du plan)
@@ -532,6 +533,20 @@ function dispatch(argv, io) {
532
533
  plan.save();
533
534
  return 0;
534
535
  }
536
+ case 'move': {
537
+ // Une seule position : --before ou --after, avec une cible (« after » est aussi le drapeau de « add », « before » celui de « archive »).
538
+ const place = values.before !== undefined && values.after === undefined ? 'before' : values.after !== undefined && values.before === undefined ? 'after' : null;
539
+ if (rest.length !== 1 || !place)
540
+ throw new RafError('usage : raf move <id> --before <id> | raf move <id> --after <id>');
541
+ const plan = loadPlan();
542
+ plan.move(rest[0], place, values[place]); // en mémoire : les refus de la commande (plan en lecture seule, parent, archive) passent avant celui de la vague
543
+ // Un lot de la vague s'appuie sur l'ordre du plan qu'il a lu : le réordonner sous ses pieds est refusé tant que la vague tient le dépôt.
544
+ const holding = waveHolding(root);
545
+ if (holding)
546
+ throw new RafError(`move : orchestration en cours (vague ${holding.wave}, pid ${holding.pid}) — attendre que les lots de ce dépôt soient finis (le verrou tombe dès que tous sont prêts ou rendus), puis relancer`);
547
+ plan.save();
548
+ return 0;
549
+ }
535
550
  case 'public': {
536
551
  need(values.clear ? 1 : 2, 'public <id> "titre public" | public <id> --clear');
537
552
  if (values.clear && rest.length > 1)
package/dist/plan.js CHANGED
@@ -625,6 +625,43 @@ export class Plan {
625
625
  const after = keys.indexOf('after');
626
626
  node.items.splice(created >= 0 ? created : after >= 0 ? after + 1 : keys.length, 0, this.doc.createPair('uses', list));
627
627
  }
628
+ /**
629
+ * Place un lot (ou une sous-tâche) juste avant ou juste après un autre du même parent (L400) : les lots entre eux, les sous-tâches d'un même lot entre elles.
630
+ * Seul l'ordre du fichier change — le nœud voyage avec ses clés et ses commentaires. Un lot archivé n'a pas de place dans le fichier du plan
631
+ * (l'archive se présente dans l'ordre d'avant l'archivage, jamais déplacée) : refusé. Déjà à sa place : rien à écrire.
632
+ */
633
+ move(ref, place, target) {
634
+ this.writable();
635
+ if (ref === target)
636
+ throw new RafError(`move : ${ref} ne peut pas être placé par rapport à lui-même`);
637
+ const parent = (r) => (r.includes('/') ? r.slice(0, r.indexOf('/')) : null);
638
+ const siblings = (r) => {
639
+ const owner = parent(r);
640
+ const top = this.doc.get('lots');
641
+ const lotId = owner ?? r;
642
+ if (!top.items.some((n) => isMap(n) && String(n.get('id')) === lotId)) {
643
+ this.lotNode(lotId); // inconnu : « lot inconnu », même dans l'archive
644
+ throw new RafError(`move : ${lotId} est archivé — l'ordre du plan ne compte que les lots du fichier du plan`);
645
+ }
646
+ if (!owner)
647
+ return top;
648
+ this.taskNode(owner, r.slice(owner.length + 1)); // sous-tâche inconnue
649
+ return this.lotNode(owner).get('tasks');
650
+ };
651
+ const from = siblings(ref);
652
+ const to = siblings(target);
653
+ if (parent(ref) !== parent(target))
654
+ throw new RafError(`move : ${ref} et ${target} n'ont pas le même parent — un lot se place parmi les lots, une sous-tâche parmi celles de son lot`);
655
+ const idOf = (r) => (parent(r) ? r.slice(r.indexOf('/') + 1) : r);
656
+ const at = (seq, r) => seq.items.findIndex((n) => isMap(n) && String(n.get('id')) === idOf(r));
657
+ const node = from.items[at(from, ref)];
658
+ const without = from.items.filter((n) => n !== node);
659
+ const wanted = without.findIndex((n) => n === to.items[at(to, target)]) + (place === 'after' ? 1 : 0);
660
+ if (at(from, ref) === wanted)
661
+ return;
662
+ without.splice(wanted, 0, node);
663
+ from.items = without;
664
+ }
628
665
  addTask(lotId, title) {
629
666
  this.writable();
630
667
  const lot = this.lotNode(lotId);
package/docs/raf.md CHANGED
@@ -58,6 +58,7 @@ raf gantt # .cadence/gantt.html (next to the plan)
58
58
  | `raf visible <id> on\|off` | set or remove `visible:` on an existing lot (a user-facing change: a news entry is expected, and `cadence orchestrate` adds its UX step), written right after `quickwin:`, `estimate:` or `status:` (whichever comes last); `on` on a visible lot changes nothing, `off` on a lot without the key neither; refused on a sub-task, and `off` is refused while the lot has a `screen:` (`raf screen <id> none` first: a screen is a visible change). It is the fix `raf check` points to for an open « Revue UX — » lot without `visible:` (L273) |
59
59
  | `raf spec <id> on\|off` | set or remove `spec:` on an existing lot (a lot that changes a behaviour, for the back-end spec step of `cadence orchestrate`, see [The spec step of a lot with `spec: true`](orchestrate.md#the-spec-step-of-a-lot-with-spec-true)); refused on a sub-task; `off` is refused on a lot that has `screen: page` (the page implies the spec: remove `screen:` first) and `on` there only says so and changes nothing; on `component` and `journey` (ordinary path since L209) both work and set or remove the back-end `spec:`. `raf spec` only takes `on`/`off`: `cadence spec check\|qa <fiche.yaml>` keeps its own subcommands. `raf check` reports a `spec:` that is neither `true` nor `false` (`spec : true ou false attendu`) |
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
+ | `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) |
61
62
  | `raf public <id> "title"` · `raf public <id> --clear` | set, replace or remove the public title of a lot |
62
63
  | `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) |
63
64
  | `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.20.0**: `raf move <id> --before|--after <id>` reorders a lot or a sub-task in the plan, refused during a wave of the repository and on a plan kept by another tool.
7
8
  - **1.19.1**: `cadence verify --gate` in the main tree reads `HEAD` and `git status` again after the steps, says what moved and prints no green or red verdict that is not the sha's.
8
9
  - **1.19.0**: `raf archive` moves closed lots out of the plan file (565 KB → 5 KB, same output everywhere); live waves share one session cap; wave sessions skip the user's plugins.
9
10
  - **1.18.0**: `cadence verify --gate` reuses the green gate of the wave instead of replaying it; `orchestrate.continue` keeps `--continue` from drawing a big lot; `raf check` and `raf now` run about 5 times faster.
@@ -44,6 +45,8 @@ Each is a paragraph in [docs/whats-new.md](whats-new.md) and a full section in [
44
45
 
45
46
  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).
46
47
 
48
+ **1.20.0**: the order of the plan is no longer edited by hand. `raf move <id> --before|--after <id>` (L400, `docs/raf.md`) moves a lot among the lots, or a sub-task among the sub-tasks of its lot; it is refused across different parents, on an archived lot, during a live wave of the repository (the same detection as `cadence deliver`) and on a plan kept by another tool (with that tool's command); a lot already in place leaves the file untouched, and like every plan write it does not commit. A test that read the real plan file directly, red since `raf archive`, now goes through the plan loader, and a guard test refuses any test that reads the real plan without it (L404).
49
+
47
50
  **1.19.1**: a verdict of `cadence verify --gate` run in the main tree is always the verdict of the sha it names (L393, `docs/deliver.md`): after the steps, `HEAD` and `git status` are read again; if the tree moved during the gate (a lot integrated, the plan of a wave committed), the output says what moved, the ✓/✗ lines are said not to be those of the sha, no « vert » / « rouge » line is written and the exit code is 2.
48
51
 
49
52
  **1.19.0**: a plan that only grows no longer weighs on every session. `raf archive [--before date] [--dry-run]` moves the `done` and `dropped` lots into `.cadence/archive/<year>.yaml` (L396, `docs/raf.md`): measured on the cadence plan, 565 KB → 5 KB, and `raf show|check|commits|now|gantt`, `cadence news`, `perf`, `session start`, `lead tour`, `deliver` and `orchestrate` print the same thing before and after (one transverse test); the archiving is lossless whatever fails, the command to commit plan and archive together is printed, a wave's plan commit takes the year files too, and the briefs tell the sessions never to read the archive. `raf todo <id>` reopens a closed lot, archived or not. All live waves now apply the same session cap, the lowest one declared, and the test gate is a visible step of a lot (L395, `docs/orchestrate.md`). Wave sessions start without the user's plugins (`--settings` with `enabledPlugins` false, L398): the first turn of an `implement` session went from 42,541 to 38,330 tokens.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sylad/cadence",
3
- "version": "1.19.1",
3
+ "version": "1.20.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",