@sylad/cadence 0.20.0 → 0.22.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 +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +146 -8
- package/bin/cadence.js +4 -1
- package/dist/articles.js +86 -0
- package/dist/audit.js +16 -2
- package/dist/cli.js +29 -8
- package/dist/config.js +92 -3
- package/dist/deliver.js +56 -9
- package/dist/docsync.js +57 -0
- package/dist/hud-context.js +117 -0
- package/dist/lead.js +34 -10
- package/dist/orchestrate/briefs.js +1 -1
- package/dist/orchestrate/command.js +200 -15
- package/dist/orchestrate/continue.js +96 -0
- package/dist/orchestrate/cycle.js +48 -13
- package/dist/orchestrate/launch.js +5 -2
- package/dist/orchestrate/registry.js +10 -0
- package/dist/orchestrate/state.js +9 -5
- package/dist/plan.js +11 -0
- package/package.json +1 -1
- package/skills/lead/SKILL.md +34 -3
- package/templates/orchestrate/review-small.md +1 -0
- package/templates/orchestrate/review.md +1 -0
|
@@ -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": "0.
|
|
11
|
+
"version": "0.22.0",
|
|
12
12
|
"source": "./",
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "Sylvain Ladoire"
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
"name": "cadence-hud",
|
|
19
|
-
"description": "A status band above the Claude Code prompt: context, 5 h / 7 d quota windows, cost per model, agents of the session and the running cadence orchestrate waves.
|
|
20
|
-
"version": "0.
|
|
19
|
+
"description": "A status band above the Claude Code prompt: context, 5 h / 7 d quota windows, cost per model, agents of the session and the running cadence orchestrate waves. Works without the cadence CLI, except the per-project plan progress, which needs `cadence` on the PATH.",
|
|
20
|
+
"version": "0.4.0",
|
|
21
21
|
"source": "./plugins/cadence-hud",
|
|
22
22
|
"author": {
|
|
23
23
|
"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": "0.
|
|
4
|
+
"version": "0.22.0",
|
|
5
5
|
"author": { "name": "Sylvain Ladoire" },
|
|
6
6
|
"homepage": "https://github.com/Sylad/cadence",
|
|
7
7
|
"repository": "https://github.com/Sylad/cadence",
|
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ Code prompt that shows the session's context, quota, cost and the orchestrate wa
|
|
|
24
24
|
|
|
25
25
|
## What's new
|
|
26
26
|
|
|
27
|
-
**0.20.0**: `cadence-hud` no longer misleads once the work is over: `/hud cmd` lists the background commands it counts (id, tool, origin, age, name), any other argument answers `argument inconnu` instead of hiding the band, commands with no end seen for an hour or no record go to a grey `N cmd sans fin vue`, a finished subagent takes its commands with it, two races that left a `1 cmd` counted forever are closed, and a finished wave shows grey without its budget then disappears after 30 minutes (L142); the `qa-reviewer` and `ux-reviewer` agents are read-only for real, their frontmatter listing Read, Grep, Glob, Bash and the Playwright tools (L140). **0.19.1**: `cadence-hud`'s `N cmd` counter goes back down when a Monitor expires, when the background commands a subagent started end with it, and at session start or plugin reload (L135); its `tool.call` and `prompt.submit` guard hooks carry a `.catch` on registration, which clears the `claude plugin validate` warning (L136). **0.19.0**: `cadence orchestrate` lifts a repository's lock and `pre-push` guard as soon as every lot of the wave that touches it is finished, so `cadence deliver` works there while the wave continues elsewhere (L132); a review that leaves a repository dirty hands back that lot only, suspends the lots still queued in that repository and keeps the other repositories running, its verdict reported instead of lost (L133); `cadence session close` re-run without a `session start` keeps the window since the last opening (L131); `cadence-hud` draws the lot rows of a wave in aligned columns on every surface (L134). **0.18.0**: `cadence session next` prints what it records (issue #6); `cadence session close` covers the period since the last `session start` (or the last close) and says so in its title, `--since` still wins (issue #7). **0.17.0**: `cadence-hud` shows an `N cmd` segment next to the agents, the background commands of the session (Bash run in the background and Monitor, subagents' included), tracked from the tool results and ended by the task's notification or `TaskStop`. **0.16.0**: a lot can declare **neighbouring repositories** (`repos:` key of the lot, `{ path, cite }` for a repository shared between projects): `cadence orchestrate` refuses, locks and guards them, gives them to every session as `--add-dir`, and `raf commits`, `raf show` and the review verdict read the commits that cite the lot there too. **0.15.0**: `hook.autostart` in `cadence.yaml` (`warn` by default, `refuse` or `start`): a pre-commit hook that refuses, or starts in the same commit, a lot still todo that the commit cites; `cadence session start|close --all [--depth n]`, one section per project under a folder. **0.14.0**: `cadence lead tour`, the lead's morning table of every project in a folder, printed by a program (one line per project: lots in progress, gaps, last notes, next ready lot, repository) in place of one subagent per project; the `lead` skill calls it. **0.13.1**: `cadence-hud` takes back the segments that fit once a big one has dropped (reset times, then the per-model consumption, now short: `opus 1.2M sonnet 800k`); `CADENCE_HUD_AMBIGUOUS=1` for a terminal that draws `▰ ▱ ⚙ │ ↻` in one cell. **0.13.0**: `cadence orchestrate --status --watch` (a wave's table redrawn in the terminal, which
|
|
27
|
+
**0.22.0**: `cadence orchestrate --continue` draws the next ready lot of the plan itself, in the declared priority, until the budget, the time window or a question stops it (L147); `cadence session context` prints the context of the session, which the `cadence-hud` band publishes, and the `lead` skill chains lots without the human while it stays under 60 % (L148); the band shows the progress of each project's plan, and `cadence lead tour --json` gains `progress` (L149, `cadence-hud` 0.4.0); the final review of a lot is always played, the lot's own budget bounding the writing passes only, with 65 k tokens reserved for it (L145); the cause « tests rouges après … » names the pass whose tests were red (L151). **0.21.0**: `cadence orchestrate --status` and `cadence lead tour` print the free session slots, which the `lead` skill reads (L141); `orchestrate.effort` sets the effort level of each pass of a wave (L137); `docs.sync` makes `raf check`, `lead tour` and the review brief report a document that does not follow the code (L143); `docs.articles` opens, after a green `cadence deliver`, a lot `Article <project> à rafraîchir` in the neighbouring repository (L144). **0.20.0**: `cadence-hud` no longer misleads once the work is over: `/hud cmd` lists the background commands it counts (id, tool, origin, age, name), any other argument answers `argument inconnu` instead of hiding the band, commands with no end seen for an hour or no record go to a grey `N cmd sans fin vue`, a finished subagent takes its commands with it, two races that left a `1 cmd` counted forever are closed, and a finished wave shows grey without its budget then disappears after 30 minutes (L142); the `qa-reviewer` and `ux-reviewer` agents are read-only for real, their frontmatter listing Read, Grep, Glob, Bash and the Playwright tools (L140). **0.19.1**: `cadence-hud`'s `N cmd` counter goes back down when a Monitor expires, when the background commands a subagent started end with it, and at session start or plugin reload (L135); its `tool.call` and `prompt.submit` guard hooks carry a `.catch` on registration, which clears the `claude plugin validate` warning (L136). **0.19.0**: `cadence orchestrate` lifts a repository's lock and `pre-push` guard as soon as every lot of the wave that touches it is finished, so `cadence deliver` works there while the wave continues elsewhere (L132); a review that leaves a repository dirty hands back that lot only, suspends the lots still queued in that repository and keeps the other repositories running, its verdict reported instead of lost (L133); `cadence session close` re-run without a `session start` keeps the window since the last opening (L131); `cadence-hud` draws the lot rows of a wave in aligned columns on every surface (L134). **0.18.0**: `cadence session next` prints what it records (issue #6); `cadence session close` covers the period since the last `session start` (or the last close) and says so in its title, `--since` still wins (issue #7). **0.17.0**: `cadence-hud` shows an `N cmd` segment next to the agents, the background commands of the session (Bash run in the background and Monitor, subagents' included), tracked from the tool results and ended by the task's notification or `TaskStop`. **0.16.0**: a lot can declare **neighbouring repositories** (`repos:` key of the lot, `{ path, cite }` for a repository shared between projects): `cadence orchestrate` refuses, locks and guards them, gives them to every session as `--add-dir`, and `raf commits`, `raf show` and the review verdict read the commits that cite the lot there too. **0.15.0**: `hook.autostart` in `cadence.yaml` (`warn` by default, `refuse` or `start`): a pre-commit hook that refuses, or starts in the same commit, a lot still todo that the commit cites; `cadence session start|close --all [--depth n]`, one section per project under a folder. **0.14.0**: `cadence lead tour`, the lead's morning table of every project in a folder, printed by a program (one line per project: lots in progress, gaps, last notes, next ready lot, repository) in place of one subagent per project; the `lead` skill calls it. **0.13.1**: `cadence-hud` takes back the segments that fit once a big one has dropped (reset times, then the per-model consumption, now short: `opus 1.2M sonnet 800k`); `CADENCE_HUD_AMBIGUOUS=1` for a terminal that draws `▰ ▱ ⚙ │ ↻` in one cell. **0.13.0**: `cadence orchestrate --status --watch` (a wave's table redrawn in the terminal, which
|
|
28
28
|
stops with the wave) and the lead skill that names the wave and follows its journal; lot ids with `/`
|
|
29
29
|
(sub-tasks of a read-only plan) accepted by `orchestrate`; `cadence-hud`'s first line measured in
|
|
30
30
|
terminal cells and kept to ASCII; `publish.yml` on the v5 actions. **0.12.0**: the `cadence-hud` plugin (a status band above the Claude Code prompt),
|
|
@@ -341,6 +341,64 @@ lot in each neighbour (the one `raf commits` lists last: it cites the lot, falls
|
|
|
341
341
|
neighbour has none), and refuses to record when a listed repository cannot be read. `raf check` does not audit the
|
|
342
342
|
neighbours: their staleness is not checked, only the project's own commits are.
|
|
343
343
|
|
|
344
|
+
### Documentation that follows the code
|
|
345
|
+
|
|
346
|
+
A stale README misleads. `cadence.yaml` can declare which documents must follow which code:
|
|
347
|
+
|
|
348
|
+
```yaml
|
|
349
|
+
docs:
|
|
350
|
+
sync:
|
|
351
|
+
- paths: [src/**, bin/*.js] # what changes…
|
|
352
|
+
docs: [README.md, docs/usage.md] # …must change one of these, in the same lot
|
|
353
|
+
- paths: [templates/]
|
|
354
|
+
docs: [CLAUDE.md]
|
|
355
|
+
- paths: [frontend/src/**, '!**/*.test.tsx'] # a pattern starting with ! leaves files out
|
|
356
|
+
docs: [README.md]
|
|
357
|
+
since: 2026-10-09 # optional: also audit lots finished on or after this day
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
A pattern is an exact path, with `*` (inside a folder), `**` (across folders), `?`, or a trailing `/` for
|
|
361
|
+
a whole folder. `raf check` takes the **work commits** of every lot in progress (plan-only commits do
|
|
362
|
+
not count; a lot finished on or after `docs.since` is audited too) and, for each pair, reports the lot when
|
|
363
|
+
those commits touch `paths` without any of them touching a file of `docs`:
|
|
364
|
+
|
|
365
|
+
```
|
|
366
|
+
✗ L7 : documentation en retard — src/a.ts, src/b.ts sans toucher README.md ou docs/usage.md (docs.sync)
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
It counts as drift: exit code 1, shown in the `dérive` column of `cadence lead tour`, and the review
|
|
370
|
+
brief of `cadence orchestrate` (`review` and `review-small`) carries the list the program computed,
|
|
371
|
+
to be reported as one **major** finding per line — a stale document is not left to the reviewer's
|
|
372
|
+
memory of a sentence. Touching a document is what the program checks; whether its text is true is
|
|
373
|
+
still the review's reading. The commits of neighbouring repositories (`repos:`) are not audited.
|
|
374
|
+
|
|
375
|
+
#### An article elsewhere that follows the delivery
|
|
376
|
+
|
|
377
|
+
A case study that lives in a neighbouring repository (the project's article on a showcase site) goes stale
|
|
378
|
+
the same way. `docs.articles` names it:
|
|
379
|
+
|
|
380
|
+
```yaml
|
|
381
|
+
docs:
|
|
382
|
+
articles:
|
|
383
|
+
- repo: ../claude-code-codex # neighbouring repository, relative to the project
|
|
384
|
+
file: src/pages/cas/demo.md # the article in it
|
|
385
|
+
name: demo # optional: the name in the lot title (default: the plan's project)
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
After a **green** `cadence deliver`, for the lots it lists as delivered **that have a public title** (`public:`, or
|
|
389
|
+
the title of their Nouveautés entry), cadence opens in this repository's plan the lot
|
|
390
|
+
`Article demo à rafraîchir` (todo, estimate 0.5, `repos: [{ path: ../claude-code-codex, cite: demo }]`), with a note
|
|
391
|
+
that quotes the delivery, the public titles delivered, the article and the **sections to read again**: the headings
|
|
392
|
+
(`#` lines, or `<h1>`…`<h6>` of an Astro page) that share a word of five letters or more with a delivered title,
|
|
393
|
+
else all the `##` headings. The lot is then played like any other, in an ordinary wave (`cadence orchestrate`: an
|
|
394
|
+
implementer rewrites the article in the neighbouring repository, the review checks it against the note). If a lot
|
|
395
|
+
of that title is still open, it receives a note instead of a duplicate. Two entries with the same name (the same
|
|
396
|
+
project told in two neighbouring repositories) share one lot, which declares both repositories in `repos:`. Internal lots (no public title) open nothing;
|
|
397
|
+
a red delivery opens nothing; `cadence deliver --dry-run` announces the articles. The plan is written but **not
|
|
398
|
+
committed** (`deliver` never commits): the line `article à rafraîchir : L9 …` says so. With a plan kept by another
|
|
399
|
+
tool (read-only), the same line carries the instruction to open the lot with that tool. This replaces the manual
|
|
400
|
+
`sync-site-docs` skill.
|
|
401
|
+
|
|
344
402
|
### QA review
|
|
345
403
|
|
|
346
404
|
No gate and no command here: the QA review comes **after** a delivery, and
|
|
@@ -423,6 +481,11 @@ cadence session close --all --depth 2 # sub-folder holding a plan (--depth n:
|
|
|
423
481
|
# exit = the worst code of the projects (2 error, 1 open, 0 closed)
|
|
424
482
|
cadence session next "finish L3" "review L4" # shown by the next session start; replaces the previous notes
|
|
425
483
|
cadence session next --clear # erase those notes, on purpose
|
|
484
|
+
cadence session context # `ctx 42 % (84000/200000)`: the context of YOUR session, as the cadence-hud band publishes
|
|
485
|
+
# it: the one named by $CLAUDE_CODE_SESSION_ID (set in Claude Code's Bash); --session <id>
|
|
486
|
+
# overrides it; with no variable, the folder decides only if a single session publishes for it
|
|
487
|
+
# (two sessions in one folder = exit 2, "pass --session"); exit 2 too when no fresh figure
|
|
488
|
+
# (band not loaded) = treat as at the threshold
|
|
426
489
|
```
|
|
427
490
|
|
|
428
491
|
`session start` records its time in the worktree's state (`session.json`, under the git dir); the next
|
|
@@ -525,13 +588,21 @@ beta · en cours rien · dérive aucune · notes : aucune · prochain T2 Second
|
|
|
525
588
|
| Column | Content |
|
|
526
589
|
|---|---|
|
|
527
590
|
| `en cours` | ids of the lots in progress; `(aucune activité)` when it has none at all, `(silencieux Nj)` when the lot's last activity (commit, note or start) is more than `--idle` days old (default 3) |
|
|
528
|
-
| `dérive` | the gaps `raf check` reports, counted and the first two shown |
|
|
591
|
+
| `dérive` | the gaps `raf check` reports (a lot whose commits leave a document behind, [`docs.sync`](#documentation-that-follows-the-code), included), counted and the first two shown |
|
|
529
592
|
| `notes` | the notes left by the last `session close`, joined with ` / ` and cut at 120 characters |
|
|
530
593
|
| `prochain` | the first ready lot (quick wins first) — id and title cut at 60 characters |
|
|
531
594
|
| `dépôt` | `non commité` (modified or untracked files), `non poussé` (commits ahead of the upstream), `livraison en cours` (live delivery lock); `propre` otherwise |
|
|
532
595
|
|
|
596
|
+
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).
|
|
597
|
+
|
|
533
598
|
`--json` prints the same content as an array of objects (`project`, `doing`, `drift`, `notes`,
|
|
534
|
-
`next`, `repo`, and `error` when the project could not be read)
|
|
599
|
+
`next`, `repo`, and `error` when the project could not be read), plus `progress` — `{ done, doing, todo, added7 }`,
|
|
600
|
+
the lots done / in progress / to do (dropped and recurring lots left out) and those created in the last 7 days (L149), which
|
|
601
|
+
[`cadence-hud`](plugins/cadence-hud/README.md) draws as one progress bar per project. Projects come in the order of the
|
|
602
|
+
`priority:` key of the folder's `cadence.yaml` (the one `orchestrate --continue` reads), the others after, alphabetically; an unreadable `cadence.yaml` or
|
|
603
|
+
a `priority:` that is not a list of names (`priority: ol`, `priority: []`) keeps the alphabetical order and adds a first row `cadence.yaml · ✗ erreur : <cause>` (`project: "cadence.yaml"` with `error` in `--json`).
|
|
604
|
+
"Created in the last 7 days" means today and the six days before. When the folder has no sub-project but is itself a project, the tour is that project's single line
|
|
605
|
+
(this is what `cadence-hud` asks from a session opened inside a project). The tour is read-only: it changes
|
|
535
606
|
no plan. It exits 0 even when a project is in error — that project's line reads
|
|
536
607
|
`beta · ✗ erreur : <cause>`. The `lead` skill runs it instead of one subagent per project.
|
|
537
608
|
|
|
@@ -634,6 +705,8 @@ cadence deliver # 0 delivered and verified · 1 a step failed · 2
|
|
|
634
705
|
reservation number that looks like one) is not a delivered lot. Plan upkeep
|
|
635
706
|
commits (see "Plan upkeep needs no lot") are skipped in that list: they cite
|
|
636
707
|
lots without delivering anything.
|
|
708
|
+
- With [`docs.articles`](#an-article-elsewhere-that-follows-the-delivery), a green delivery of lots with a
|
|
709
|
+
public title also opens the lot `Article <project> à rafraîchir` in the plan.
|
|
637
710
|
|
|
638
711
|
### A project with its own delivery script
|
|
639
712
|
|
|
@@ -751,11 +824,40 @@ cadence orchestrate maritime-atlas:Q4/accueil-4-ux12@haiku # a lot id may cont
|
|
|
751
824
|
cadence orchestrate … --budget 1.5M # 1500000, 1.5M, 800k; default 2M
|
|
752
825
|
cadence orchestrate … --max-sessions 3 # sessions running at the same moment, all waves together; default 2
|
|
753
826
|
cadence orchestrate … --dry-run # preconditions + the plan of the wave; nothing is started
|
|
827
|
+
cadence orchestrate --continue [--until 18:00] [--priority cadence,maritime] # the wave draws the next ready lot itself (below)
|
|
754
828
|
cadence orchestrate --status [<wave>] # the live waves and the repositories they hold, then the table (default: the last wave of this folder)
|
|
755
829
|
cadence orchestrate --status [<wave>] --watch [--interval 10] # the same, redrawn every 10 s (--interval in seconds); stops by itself when no wave is running (with an explicit `<wave>`: when that wave is no longer running)
|
|
756
830
|
cadence orchestrate --resume [<wave>] [--budget 1M] [--answer ol-companion:L22 "reply"]
|
|
757
831
|
```
|
|
758
832
|
|
|
833
|
+
**`--continue` — idle time (L147)**: lots given on the command line open the wave (none: the first ones are drawn from the
|
|
834
|
+
plan too); then, each time the lots in play are finished, the wave **draws the next ready lots of the plan** — up to the
|
|
835
|
+
session cap — and plays them in the same wave (same budget, same state folder, journal line `continue : tire …`). A lot
|
|
836
|
+
is drawn when it is `todo`, its `after` is lifted, it is not recurring, neither its title nor a note says « à décider »
|
|
837
|
+
(« à décider avec … », « (à décider) »: a lot waiting for a human decision), it is not already in the wave, and its own budget (derived from its estimate)
|
|
838
|
+
fits in the budget left — otherwise the next one that fits is taken (a lot that does not fit in what is left of the current
|
|
839
|
+
round is not dropped: it is tried again at the next round, once the budget allows it). A round takes at most **one lot per
|
|
840
|
+
repository** while other repositories have ready lots — the pool plays the lots of one repository one after the other, so a
|
|
841
|
+
second one would leave a session idle — and is completed with the same repository when nothing else is ready. Projects are visited in the declared **priority**:
|
|
842
|
+
`--priority cadence,maritime`, or a `priority: [cadence, maritime]` list in the `cadence.yaml` of the folder you launch from
|
|
843
|
+
(the parent of the projects); a name also covers `name-…` (`maritime` → `maritime-atlas`), the other projects come after, in
|
|
844
|
+
alphabetical order. Launched from inside a project, only that project is drawn. A `priority:` key that cannot be read is refused before anything
|
|
845
|
+
starts, lots given or not. A candidate that preflight refuses (dirty tree, Node missing…) is skipped with its cause in the
|
|
846
|
+
journal (`continue : lot sauté — <cause>`; for the first draw also on the error output). Drawing stops — `continue : arrêt — <reason>` — at: the budget, the
|
|
847
|
+
time window (`--until HH:MM`, today's clock: nothing new is drawn from then on, a running lot is not cut), the usage limit,
|
|
848
|
+
a question asked (the wave stays resumable with `--answer`), two lots handed back in a row, an interrupted wave, or no ready
|
|
849
|
+
lot whose estimate fits the budget left. `--resume … --continue` draws again after the resumed lots. `--dry-run --continue` replays the
|
|
850
|
+
draws round by round with the same function as the wave (one lot per repository per round, pre-check, budget counted on the estimates) and
|
|
851
|
+
names the candidates skipped with their cause: `tour 1 : a:L2, b:L2 · tour 2 : a:L3, a:L4`. Drawing happens between rounds: a round of up to `--max-sessions` lots must finish before the next draw.
|
|
852
|
+
|
|
853
|
+
**Chaining without the human (L148)**: the `lead` skill, once the human has chosen the first lots and the order, does not ask « next? »
|
|
854
|
+
after a lot that comes back ready: it re-verifies it, records `raf done`, pushes, delivers, has the `qa-reviewer` check the
|
|
855
|
+
delivered app, then starts the next wave with `--continue`, in the declared priority — one project at a time, within the two-session
|
|
856
|
+
limit. Before each new wave it reads the context of its own session with `cadence session context` (the figure of the `ctx` segment of the `cadence-hud` band, which publishes the context of each session to `~/.cadence/orchestrate/hud-context/<session id>.json`; the command picks the file named by `$CLAUDE_CODE_SESSION_ID`, set in the environment of the session's Bash, never "the latest file of the folder" — two sessions of one folder alternate; without the band loaded in that session the command exits with code 2, and a missing or stale figure counts as at the threshold); from
|
|
857
|
+
**60 %** upward it starts nothing: it runs `session-close` in each project touched, writes its memory and records three lines for next
|
|
858
|
+
time (`cadence session next`). Stays with the human: the first choice and the order, the questions a session raised, the UX
|
|
859
|
+
reservations; a lot that is not ready, a red delivery or a blocking QA finding is reported and not chained over.
|
|
860
|
+
|
|
759
861
|
**Lot ids containing `/` (L120)**: the first `:` separates the project from the lot, a final `@` the model, and the lot
|
|
760
862
|
keeps its `/` (`maritime-atlas:Q4/accueil-4-ux12@haiku`); this holds for `--dry-run`, `--status`, `--resume` and
|
|
761
863
|
`--answer` too. In `.cadence/runs/<wave>/` the `/` of the lot becomes `__` (`maritime-atlas--Q4__accueil-4-ux12.json`,
|
|
@@ -796,6 +898,22 @@ orchestrate:
|
|
|
796
898
|
|
|
797
899
|
`full` is also the model of the UX review and of the single pass of a small lot that is not light.
|
|
798
900
|
|
|
901
|
+
**Effort level per pass** (L137). Each session is launched with `claude -p --effort <level>` (Claude Code 2.1.284; an
|
|
902
|
+
older `claude` is refused at launch unless every pass is `default`). Defaults: `precheck` low (it only reads),
|
|
903
|
+
`implement` and `fix` medium, `review` high (`review-small` follows `review`), `ux` high. A project overrides any of them
|
|
904
|
+
in `cadence.yaml`; the levels are `low`, `medium`, `high`, `xhigh`, `max`, and `default` sends no `--effort` (the
|
|
905
|
+
level of the session, as before this key). The format-retry session (`--resume`) keeps the level of its pass, and
|
|
906
|
+
`--dry-run` shows the flag in each command line.
|
|
907
|
+
|
|
908
|
+
```yaml
|
|
909
|
+
orchestrate:
|
|
910
|
+
effort: { precheck: low, implement: medium, fix: medium, review: high, ux: high } # defaults
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
To measure what a level costs, run the same kind of wave before and after changing the key: each step of
|
|
914
|
+
`<wave>/<project>--<lot>.json` records its `effort`, its `tokens` and its `started` / `ended` times (a step from a wave
|
|
915
|
+
older than L137, or with `default`, has no `effort`); compare the totals per `kind` between the two waves.
|
|
916
|
+
|
|
799
917
|
**Pre-check « deliverable already present? »** (L77). Before the first implementation of a lot that has no commit yet, a
|
|
800
918
|
short read-only session (Sonnet, `precheck` step, brief `templates/orchestrate/precheck.md`) looks in the repository for
|
|
801
919
|
what the lot asks for (another lot, or a correction, may have done it already). It answers `oui` (everything is there,
|
|
@@ -919,7 +1037,7 @@ A slot or a registry entry is owned by a pid **and** its start time: a reused pi
|
|
|
919
1037
|
identifiers are reserved atomically (`-2`, `-3` suffix when two waves start in the same minute; an existing
|
|
920
1038
|
`--wave` is refused). The registry of live waves and the slots live under `~/.cadence/orchestrate/` (`CADENCE_HOME`
|
|
921
1039
|
to move it): `cadence orchestrate --status` lists, from any folder, the live waves, the repositories each
|
|
922
|
-
still holds (those not yet released), the cap of each wave and the slots
|
|
1040
|
+
still holds (those not yet released), the cap of each wave, the slots in use and the **free slots** (`créneaux libres : N sur 2`: 2 minus, for each live wave, the smaller of its cap and the repositories it still holds — the figure the `lead` skill reads before starting a subagent). Without an identifier it then prints the table of the wave launched most recently from this folder (by launch time, not by alphabetical order of the identifier).
|
|
923
1041
|
|
|
924
1042
|
**Guards, imposed by the code**: a global cap of simultaneous sessions, one wave per repository at
|
|
925
1043
|
a time (above); `Agent`, `git push`, `cadence deliver`, `raf done|review|ux` are denied to the sessions; a
|
|
@@ -932,8 +1050,14 @@ tree (screenshots left at the root, a commit) is an incident of that lot only: t
|
|
|
932
1050
|
|
|
933
1051
|
**Budget**: the wave counts input + cache writes + output tokens (default 2 M); cache reads are kept and
|
|
934
1052
|
shown apart. Each lot also has its own budget derived from its estimate (400 k tokens per day, floor 200 k, shown by
|
|
935
|
-
`--dry-run`):
|
|
936
|
-
|
|
1053
|
+
`--dry-run`): it bounds the *writing* passes, never the reviews. A lot that spent it gets no further implementation or
|
|
1054
|
+
fix and is handed back to the lead, but its review (and UX review) is played first; a fix pass starts only if
|
|
1055
|
+
the remaining budget can also pay the review that follows it (65 k tokens reserved: a fixed amount close to the 90th
|
|
1056
|
+
percentile of the full reviews, 62.7 k over 187 reviews; the short review after the minors pass has a higher p90, 67.6 k
|
|
1057
|
+
over 114; measured on 2026-10-09 in the wave journals `.cadence/runs`, `tokens.counted`, 67 waves from 10-04 to 10-09).
|
|
1058
|
+
Exception: when the tests are red after a write pass, the lot goes to a fix pass without a review; if that fix pass is
|
|
1059
|
+
refused for lack of budget the lot is handed back to the lead without a review, with its findings, and the cause names
|
|
1060
|
+
the red tests: the pass it names is the one whose tests were red, counted by its number (« la passe fix 2 », « l'implémentation »), and « la passe des mineurs » appears only when that pass was the one played. The wave budget stays for the others. When the budget (or the usage limit) is reached no new session starts, the running ones
|
|
937
1061
|
finish, the wave is *suspended* (exit code 3) and `--resume --budget …` continues. A session that returns
|
|
938
1062
|
nothing readable, times out (45 min for work, 25 for a review) or fails is not retried; the lot is handed
|
|
939
1063
|
back with the cause. Exit codes: 0 every lot ready · 1 at least one lot handed back (question, failure,
|
|
@@ -964,6 +1088,7 @@ orchestrate:
|
|
|
964
1088
|
test: npm test # run by the orchestrator after a work step (optional)
|
|
965
1089
|
build: npm run build # run after the tests; both results go to the reviewer, who does not redo them (optional)
|
|
966
1090
|
precheck: true # default: before the first implementation of a lot with no commit, a read-only Sonnet session checks whether the deliverable is already in the repository (see below); false skips it
|
|
1091
|
+
effort: { review: xhigh } # effort level per pass (see « Effort level per pass »): precheck, implement, fix, review, ux
|
|
967
1092
|
ux: http://localhost:4200 # a URL, a launch command, or { command, url, timeout? } — for the UX review (see below)
|
|
968
1093
|
permissionMode: auto # default
|
|
969
1094
|
addDirs: [/home/me/projects/tmp] # extra directories the sessions may use
|
|
@@ -1138,7 +1263,19 @@ after it ended, then disappears. A background command counted for more than an h
|
|
|
1138
1263
|
record of when it started, is no longer shown as running: it moves to a grey `N cmd sans fin
|
|
1139
1264
|
vue` ("end never seen") right after `N cmd`, because its end notification may never have reached
|
|
1140
1265
|
the session. Waves are read from disk (`~/.cadence/orchestrate/waves/`, then `.cadence/runs/`); no
|
|
1141
|
-
cadence command is run, and the CLI is not required.
|
|
1266
|
+
cadence command is run, and the CLI is not required for those. One more block needs it: the **progress of the plans**,
|
|
1267
|
+
one line per active project (a project with open lots), in the order of `priority:`:
|
|
1268
|
+
|
|
1269
|
+
```
|
|
1270
|
+
ol ▮▮▮▮▮▯▯▯▯▯ 39/78 · 0 en cours · +3 cette semaine
|
|
1271
|
+
cadence ▮▮▮▮▮▯▯▯▯▯ 10/20 · 2 en cours
|
|
1272
|
+
```
|
|
1273
|
+
|
|
1274
|
+
The bar is lots done over lots done + in progress + to do (dropped and recurring lots left out);
|
|
1275
|
+
`+N cette semaine` counts the lots created in the last 7 days (today and the six before) and is left out at zero. The source is
|
|
1276
|
+
`cadence lead tour <folder> --json` (the folder a wave was launched from, otherwise the session's: a folder of projects gives one line per project, a project with no sub-project gives its own single line),
|
|
1277
|
+
read again at every wave transition and otherwise every minute; without `cadence` on the PATH, the
|
|
1278
|
+
lines are simply not drawn.
|
|
1142
1279
|
|
|
1143
1280
|
Install it as a plugin from the same marketplace:
|
|
1144
1281
|
|
|
@@ -1149,7 +1286,8 @@ Install it as a plugin from the same marketplace:
|
|
|
1149
1286
|
|
|
1150
1287
|
`/hud` hides or shows the band. `/hud cmd` lists the background commands being counted (id, tool,
|
|
1151
1288
|
origin `session` or `agent <id>`, age, name; `(sans fin vue)` on the ones above). Any other argument
|
|
1152
|
-
|
|
1289
|
+
`/hud projets` folds the project list to one line, and opens it again. Any other argument
|
|
1290
|
+
answers `argument inconnu : … (attendu : cmd, projets)` and leaves the band as it is. The source lives in [`plugins/cadence-hud`](plugins/cadence-hud/README.md)
|
|
1153
1291
|
(its own README has the details of every cell); it is not part of the npm package. To work on it:
|
|
1154
1292
|
`claude plugin validate plugins/cadence-hud`, `claude plugin test plugins/cadence-hud`, and
|
|
1155
1293
|
`tsc -p plugins/cadence-hud` once Claude Code has loaded the plugin at least once (it generates
|
package/bin/cadence.js
CHANGED
|
@@ -29,6 +29,7 @@ if (tool === 'raf') {
|
|
|
29
29
|
cadence session start|close --all [--depth 1] depuis un dossier non dépôt : une section par projet des sous-dossiers
|
|
30
30
|
cadence session next "ligne" … notes pour la prochaine session (remplacent les précédentes)
|
|
31
31
|
cadence session next --clear efface ces notes ; sans ligne ni --clear, la commande refuse
|
|
32
|
+
cadence session context [--session <id>] contexte de la session (variable CLAUDE_CODE_SESSION_ID, sinon la seule du dossier) publié par la bande cadence-hud ; code 2 sans chiffre frais
|
|
32
33
|
cadence deliver [--dry-run] [--sha rév] [--config cadence.yaml] [-- arguments du script du projet]
|
|
33
34
|
CI du sha poussé → déploiement → vérifications de l'effet ;
|
|
34
35
|
ou le script de livraison du projet (deliver.script), sous verrou et journal
|
|
@@ -38,8 +39,10 @@ if (tool === 'raf') {
|
|
|
38
39
|
cadence orchestrate <projet>:<lot>[@modèle]… [--budget 2M] [--max-sessions 2] [--wave id] [--dry-run]
|
|
39
40
|
une session claude neuve par étape (implémentation, revues, corrections) ; --status, --resume ;
|
|
40
41
|
--max-sessions : sessions simultanées, toutes vagues confondues (CADENCE_MAX_SESSIONS)
|
|
42
|
+
--continue [--until HH:MM] [--priority a,b] : tire seul les lots prêts du plan, jusqu'à une borne
|
|
41
43
|
cadence lead tour [dossier] [--idle 3] [--json]
|
|
42
|
-
le tableau du lead, sans modèle : une ligne par sous-dossier qui a un plan
|
|
44
|
+
le tableau du lead, sans modèle : une ligne par sous-dossier qui a un plan, ou la ligne du dossier courant
|
|
45
|
+
s'il est lui-même un projet ; une première ligne « cadence.yaml · ✗ erreur » si priority: est illisible
|
|
43
46
|
cadence skills install [--dir .claude] [--force]
|
|
44
47
|
installe les skills Claude Code session-start, session-close, deliver et l'agent ux-reviewer`);
|
|
45
48
|
process.exitCode = !tool || ['help', '--help', '-h'].includes(tool) ? 0 : 2;
|
package/dist/articles.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { isOpen } from './plan.js';
|
|
4
|
+
/** Titre du lot ouvert à la livraison. */
|
|
5
|
+
export const articleTitle = (name) => `Article ${name} à rafraîchir`;
|
|
6
|
+
const SECTIONS_SHOWN = 12;
|
|
7
|
+
/** Titres d'un article : lignes `#` du Markdown (hors en-tête YAML) et balises `<h1>`…`<h6>` d'une page Astro ou MDX. */
|
|
8
|
+
export function articleHeadings(text) {
|
|
9
|
+
const body = text.replace(/^/, '').replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, '');
|
|
10
|
+
const found = [];
|
|
11
|
+
for (const m of body.matchAll(/^(#{1,6})[ \t]+(.+?)[ \t]*#*[ \t]*$/gm))
|
|
12
|
+
found.push({ at: m.index, heading: { level: m[1].length, text: m[2].trim() } });
|
|
13
|
+
for (const m of body.matchAll(/<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1>/gi)) {
|
|
14
|
+
const t = m[2].replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim();
|
|
15
|
+
if (t)
|
|
16
|
+
found.push({ at: m.index, heading: { level: Number(m[1]), text: t } });
|
|
17
|
+
}
|
|
18
|
+
return found.sort((a, b) => a.at - b.at).map((f) => f.heading);
|
|
19
|
+
}
|
|
20
|
+
const words = (s) => new Set(s
|
|
21
|
+
.normalize('NFD')
|
|
22
|
+
.replace(/[̀-ͯ]/g, '')
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.split(/[^a-z0-9]+/)
|
|
25
|
+
.filter((w) => w.length >= 5));
|
|
26
|
+
/**
|
|
27
|
+
* Sections à relire : celles dont le titre partage un mot (5 lettres au moins, sans accents ni casse) avec un titre public
|
|
28
|
+
* livré ; sans recoupement, toutes les sections de premier niveau (`##`), faute de mieux.
|
|
29
|
+
*/
|
|
30
|
+
export function articleSections(text, titles) {
|
|
31
|
+
const headings = articleHeadings(text);
|
|
32
|
+
const wanted = new Set(titles.flatMap((t) => [...words(t)]));
|
|
33
|
+
const matching = headings.filter((h) => [...words(h.text)].some((w) => wanted.has(w))).map((h) => h.text);
|
|
34
|
+
const picked = matching.length ? matching : headings.filter((h) => h.level === 2).map((h) => h.text);
|
|
35
|
+
return picked.slice(0, SECTIONS_SHOWN);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Après une livraison verte (L144) : pour chaque article déclaré, ouvre dans ce dépôt le lot « Article <projet> à rafraîchir »
|
|
39
|
+
* — ou, s'il en est déjà un d'ouvert, y ajoute une note. La note cite les titres publics livrés et les sections à relire ;
|
|
40
|
+
* le lot déclare le ou les dépôts voisins (`repos`, filtrés par le nom du projet : le dépôt de l'article est partagé entre projets).
|
|
41
|
+
* Les entrées de même nom partagent un seul lot, qui déclare tous leurs dépôts.
|
|
42
|
+
* Ne sauve pas le plan. Aucun titre public livré : rien à rafraîchir.
|
|
43
|
+
*/
|
|
44
|
+
export function openArticleLots(o) {
|
|
45
|
+
const result = { opened: [], noted: [], manual: [], warnings: [] };
|
|
46
|
+
const titles = [...new Set(o.delivered.map(o.publicTitle).filter((t) => !!t))];
|
|
47
|
+
if (!titles.length)
|
|
48
|
+
return result;
|
|
49
|
+
// Un lot par nom de projet : deux articles de même nom (deux dépôts voisins) se jouent ensemble dans le même lot.
|
|
50
|
+
const byName = new Map();
|
|
51
|
+
for (const rule of o.rules) {
|
|
52
|
+
const name = rule.name ?? o.plan.project;
|
|
53
|
+
byName.set(name, [...(byName.get(name) ?? []), rule]);
|
|
54
|
+
}
|
|
55
|
+
for (const [name, rules] of byName) {
|
|
56
|
+
const title = articleTitle(name);
|
|
57
|
+
const repos = [...new Set(rules.map((r) => r.repo))];
|
|
58
|
+
const articles = rules.map((rule) => {
|
|
59
|
+
const path = resolve(o.root, rule.repo, rule.file);
|
|
60
|
+
const sections = existsSync(path) ? articleSections(readFileSync(path, 'utf8'), titles) : null;
|
|
61
|
+
if (sections === null)
|
|
62
|
+
result.warnings.push(`article introuvable : ${path}`);
|
|
63
|
+
return (`Article : ${rule.repo}/${rule.file}. ` +
|
|
64
|
+
(sections === null ? 'Fichier introuvable à l\'ouverture du lot : vérifier le chemin (docs.articles). ' : `Sections à relire : ${sections.length ? sections.join(' ; ') : '(aucun titre trouvé : relire tout l\'article)'}. `));
|
|
65
|
+
});
|
|
66
|
+
const note = `Livraison ${o.sha.slice(0, 7)} : titres publics livrés ${titles.map((t) => `« ${t} »`).join(', ')}. ` +
|
|
67
|
+
articles.join('') +
|
|
68
|
+
`Réécrire ce que ces changements rendent faux ou incomplet, sans rien inventer ; les commits ${repos.length > 1 ? 'des dépôts' : 'du dépôt'} ${repos.join(', ')} citent le lot et « ${name} ».`;
|
|
69
|
+
if (o.plan.readonly) {
|
|
70
|
+
result.manual.push(`${title} — plan tenu par un autre outil, ouvrir le lot avec lui : ${note}`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const open = o.plan.lots().find((l) => l.title === title && isOpen(l.status));
|
|
74
|
+
if (open) {
|
|
75
|
+
o.plan.note(open.id, note, o.today);
|
|
76
|
+
result.noted.push(open.id);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const id = o.plan.add(title, o.today, { estimate: 0.5 });
|
|
80
|
+
o.plan.setRepos(id, repos.map((path) => ({ path, cite: name })));
|
|
81
|
+
o.plan.note(id, note, o.today);
|
|
82
|
+
result.opened.push(id);
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
export const describeArticle = (rule) => join(rule.repo, rule.file);
|
package/dist/audit.js
CHANGED
|
@@ -4,7 +4,8 @@ import { check } from './check.js';
|
|
|
4
4
|
import { parse } from 'yaml';
|
|
5
5
|
import { changedFiles, fileAt, readCommits } from './git.js';
|
|
6
6
|
import { linkCommits } from './link.js';
|
|
7
|
-
import { readNewsConfig } from './config.js';
|
|
7
|
+
import { readDocsConfig, readNewsConfig } from './config.js';
|
|
8
|
+
import { docSyncGaps, filesOf, gapMessage } from './docsync.js';
|
|
8
9
|
import { loadEntries, newsIssues, PUBLIC_TITLE_DEFAULT, publicTitleTooLong, reusedNewsTitle } from './news.js';
|
|
9
10
|
import { isRecurring } from './recurring.js';
|
|
10
11
|
import { isOpen } from './plan.js';
|
|
@@ -109,11 +110,24 @@ export function audit(plan, root, newsDir, today, opts = {}) {
|
|
|
109
110
|
const m = l.public ? publicTitleTooLong(l.public, max) : reused ? publicTitleTooLong(reused, max) : null;
|
|
110
111
|
return m ? [{ message: `${l.id} : ${reused && !l.public ? m.replace('titre public', 'titre public repris de la Nouveauté') : m}` }] : [];
|
|
111
112
|
});
|
|
112
|
-
const issues = [...check(lots, { ...linked, byLot: all.byLot }, today, opts.idle ?? 7), ...newsIssues(lots, entries, newsDir), ...titles, ...gates, ...(plan.hasPublicField ? missingPublicTitles(lots, entries) : []),
|
|
113
|
+
const issues = [...check(lots, { ...linked, byLot: all.byLot }, today, opts.idle ?? 7), ...newsIssues(lots, entries, newsDir), ...titles, ...gates, ...docSyncIssues(plan, root, all.byLot), ...(plan.hasPublicField ? missingPublicTitles(lots, entries) : []),
|
|
113
114
|
...plan.ignore.invalid.map((src) => ({ message: `ignore : motif invalide « ${src} »` }))];
|
|
114
115
|
// Un plan en lecture seule se corrige avec l'outil du projet : ne pas conseiller une commande raf qui refuserait.
|
|
115
116
|
return plan.readonly ? issues.map((i) => ({ ...i, message: i.message.replace(/ — raf (start|public) .*$/, '') })) : issues;
|
|
116
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Documentation en retard (L143, clé `docs.sync`) : un lot en cours — ou terminé à partir de `docs.since` — dont les commits de
|
|
120
|
+
* travail touchent des chemins sans toucher le document que la règle désigne. Les commits de plan ne comptent pas.
|
|
121
|
+
*/
|
|
122
|
+
export function docSyncIssues(plan, root, byLot) {
|
|
123
|
+
const { sync, since } = readDocsConfig(plan.configFile ?? join(root, 'cadence.yaml'));
|
|
124
|
+
if (!sync.length)
|
|
125
|
+
return [];
|
|
126
|
+
return plan
|
|
127
|
+
.lots()
|
|
128
|
+
.filter((l) => l.status === 'doing' || (l.status === 'done' && !!since && !!l.finished && l.finished >= since))
|
|
129
|
+
.flatMap((l) => docSyncGaps(sync, filesOf(root, workCommits(plan, root, byLot.get(l.id) ?? []))).map((g) => ({ message: gapMessage(l.id, g) })));
|
|
130
|
+
}
|
|
117
131
|
/** Commits qui portent du travail sur un lot : ni antérieurs à l'adoption du plan, ni réduits au plan. */
|
|
118
132
|
function workCommits(plan, root, commits) {
|
|
119
133
|
const adopted = plan.since;
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
3
3
|
import { parseArgs } from 'node:util';
|
|
4
|
-
import { readHookConfig, readNewsConfig, readPlanConfig, readSessionConfig } from './config.js';
|
|
4
|
+
import { readDocsConfig, readHookConfig, readNewsConfig, readPlanConfig, readSessionConfig } from './config.js';
|
|
5
5
|
import { repoSections, repoShas, resolveLotRepos } from './repos.js';
|
|
6
6
|
import { audit, exemptPlanOnly, ownFiles, isPlanOnly, lotWork, nextUp, planCommits, unreviewedWork } from './audit.js';
|
|
7
7
|
import { short } from './check.js';
|
|
@@ -18,8 +18,9 @@ import { dueLine, isRecurring, recurringByDue } from './recurring.js';
|
|
|
18
18
|
import { schedule } from './schedule.js';
|
|
19
19
|
import { AGENTS_DIR, installAgents, installSkills, SKILLS_DIR } from './skills.js';
|
|
20
20
|
import { findProjects, leadTour, tourLine } from './lead.js';
|
|
21
|
-
import { orchestrate, realOrchestrateDeps } from './orchestrate/command.js';
|
|
21
|
+
import { liveLines, orchestrate, realOrchestrateDeps } from './orchestrate/command.js';
|
|
22
22
|
import { activeLock, REPO_LOCK } from './orchestrate/lock.js';
|
|
23
|
+
import { readHudContext } from './hud-context.js';
|
|
23
24
|
import { sessionClose, sessionStart } from './session.js';
|
|
24
25
|
import { clearNext, readNext, sharedStateDir, stateDir, writeNext } from './state.js';
|
|
25
26
|
const HELP = `raf — plan « reste à faire » versionné dans le dépôt, relié aux commits
|
|
@@ -42,8 +43,8 @@ const HELP = `raf — plan « reste à faire » versionné dans le dépôt, reli
|
|
|
42
43
|
raf ignore <sha> | "<sujet exact>" [--reason texte] acquitte un commit sans lot (ou citant un id inconnu) sans réécrire l'historique ; raf check --ignored les liste
|
|
43
44
|
raf gantt [-o docs/plan/gantt.html]
|
|
44
45
|
raf hook install (pre-commit et post-commit ; cadence.yaml hook.autostart: warn|refuse|start)
|
|
45
|
-
cadence orchestrate <projet>:<lot>[@modèle]… [--budget 2M] [--dry-run] [--wave nom] une session claude neuve par étape ; --status [vague] [--watch [--interval s]] ; --resume [vague] [--budget 1M] [--answer projet:lot "réponse"]
|
|
46
|
-
cadence lead tour [dossier] [--idle 3] [--json] le tableau du lead, sans modèle : une ligne par sous-dossier qui a un plan (en cours, dérive, notes de clôture, prochain lot prêt, dépôt)
|
|
46
|
+
cadence orchestrate <projet>:<lot>[@modèle]… [--budget 2M] [--dry-run] [--wave nom] [--continue [--until HH:MM] [--priority a,b]] une session claude neuve par étape ; --status [vague] [--watch [--interval s]] ; --resume [vague] [--budget 1M] [--answer projet:lot "réponse"]
|
|
47
|
+
cadence lead tour [dossier] [--idle 3] [--json] le tableau du lead, sans modèle : une ligne par sous-dossier qui a un plan, ou la ligne du dossier seul s'il est lui-même un projet (en cours, dérive, notes de clôture, prochain lot prêt, dépôt)
|
|
47
48
|
cadence verify [--retry s] [--sha rév] rejoue deliver.verify hors livraison : 0 vert, 1 effet rouge, 2 rien à vérifier
|
|
48
49
|
raf news new <lot…> [--title t] | list | check | stamp | build [-o dossier] (aussi « cadence news … »)
|
|
49
50
|
|
|
@@ -115,6 +116,7 @@ function dispatch(argv, io) {
|
|
|
115
116
|
status: { type: 'string' },
|
|
116
117
|
since: { type: 'string' },
|
|
117
118
|
idle: { type: 'string' },
|
|
119
|
+
session: { type: 'string' },
|
|
118
120
|
all: { type: 'boolean' },
|
|
119
121
|
depth: { type: 'string' },
|
|
120
122
|
output: { type: 'string', short: 'o' },
|
|
@@ -140,7 +142,7 @@ function dispatch(argv, io) {
|
|
|
140
142
|
if (!isDay(today))
|
|
141
143
|
throw new RafError(`RAF_TODAY invalide : ${today} (attendu AAAA-MM-JJ)`);
|
|
142
144
|
const root = gitRoot(io.cwd) ?? io.cwd;
|
|
143
|
-
// Le tour parcourt les sous-dossiers : il ne lit
|
|
145
|
+
// 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.
|
|
144
146
|
if (command === 'lead') {
|
|
145
147
|
if (rest[0] !== 'tour' || rest.length > 2)
|
|
146
148
|
throw new RafError('usage : cadence lead tour [dossier] [--idle 3] [--json]');
|
|
@@ -155,9 +157,14 @@ function dispatch(argv, io) {
|
|
|
155
157
|
io.out(JSON.stringify(rows, null, 2));
|
|
156
158
|
else if (rows.length === 0)
|
|
157
159
|
io.out(`aucun projet sous ${parent} (docs/plan/raf.yaml ou cadence.yaml avec plan:)`);
|
|
158
|
-
else
|
|
160
|
+
else {
|
|
159
161
|
for (const r of rows)
|
|
160
162
|
io.out(tourLine(r));
|
|
163
|
+
// les créneaux libres viennent du registre, le lead les lit au lieu de les calculer (L141)
|
|
164
|
+
const [slots] = liveLines();
|
|
165
|
+
if (slots)
|
|
166
|
+
io.out(slots);
|
|
167
|
+
}
|
|
161
168
|
return 0;
|
|
162
169
|
}
|
|
163
170
|
// --all balaie les sous-dossiers : il ne lit ni le plan ni le cadence.yaml du dossier courant.
|
|
@@ -401,6 +408,9 @@ function dispatch(argv, io) {
|
|
|
401
408
|
throw new RafError('usage : raf hook install|pre-commit|post-commit');
|
|
402
409
|
}
|
|
403
410
|
case 'session': {
|
|
411
|
+
// Le contexte de la session ne dépend d'aucun dépôt : le lead le lit depuis le dossier parent.
|
|
412
|
+
if (rest[0] === 'context')
|
|
413
|
+
return sessionContext(io, values.session);
|
|
404
414
|
if (!gitRoot(io.cwd))
|
|
405
415
|
throw new RafError('session : à lancer dans un dépôt git');
|
|
406
416
|
// « next » n'écrit que les notes : la commande du projet ne se joue qu'à la reprise et à la clôture.
|
|
@@ -426,7 +436,10 @@ function dispatch(argv, io) {
|
|
|
426
436
|
throw new RafError(`pas de configuration de livraison : ${configPath} (voir « cadence.yaml » dans le README)`);
|
|
427
437
|
const config = parseDeliverConfig(readFileSync(configPath, 'utf8'), configPath);
|
|
428
438
|
const plan = existsSync(planPath) ? loadPlan() : null;
|
|
429
|
-
const
|
|
439
|
+
const articles = readDocsConfig(configPath).articles;
|
|
440
|
+
// Sans public: d'un lot visible, le site reprend le titre de sa Nouveauté la plus récente.
|
|
441
|
+
const publicTitle = (lot) => lot.public ?? (lot.visible ? loadEntries(newsDir).find((e) => e.lots.includes(lot.id))?.title : undefined);
|
|
442
|
+
const ctx = { root, state: sharedStateDir(root), plan, config, today, dryRun: !!values['dry-run'], sha: values.sha, args: rest, articles, publicTitle, out: io.out, err: io.err };
|
|
430
443
|
return deliver(ctx, realDeps(root));
|
|
431
444
|
}
|
|
432
445
|
case 'verify': {
|
|
@@ -764,6 +777,14 @@ async function sessionAll(rest, values, io) {
|
|
|
764
777
|
}
|
|
765
778
|
return worst;
|
|
766
779
|
}
|
|
780
|
+
/** Le contexte de la session de `--session <id>`, sinon de `CLAUDE_CODE_SESSION_ID`, sinon (variable absente) de la seule session du dossier courant, tel que la bande cadence-hud le publie ; illisible = code 2, à lire comme « au seuil ». */
|
|
781
|
+
function sessionContext(io, session) {
|
|
782
|
+
const r = readHudContext(io.env, io.now().getTime(), { cwd: io.cwd, session });
|
|
783
|
+
if (!r.ok)
|
|
784
|
+
throw new RafError(`session context : contexte illisible — ${r.reason} ; à traiter comme au seuil`);
|
|
785
|
+
io.out(`ctx ${Math.round(r.ctx.percent)} % (${r.ctx.tokens}/${r.ctx.window})`);
|
|
786
|
+
return 0;
|
|
787
|
+
}
|
|
767
788
|
function session([sub, ...args], ctx, values) {
|
|
768
789
|
switch (sub) {
|
|
769
790
|
case 'start': {
|
|
@@ -798,6 +819,6 @@ function session([sub, ...args], ctx, values) {
|
|
|
798
819
|
return 0;
|
|
799
820
|
}
|
|
800
821
|
default:
|
|
801
|
-
throw new RafError('usage : cadence session start [--since …] [--idle 2] | close [--since …] | start|close --all [--depth n] | next "ligne" … | next --clear');
|
|
822
|
+
throw new RafError('usage : cadence session start [--since …] [--idle 2] | close [--since …] | start|close --all [--depth n] | next "ligne" … | next --clear | context [--session id]');
|
|
802
823
|
}
|
|
803
824
|
}
|