@sylad/cadence 0.11.0 → 0.12.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 +16 -3
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +183 -17
- package/agents/code-reviewer.md +8 -3
- package/agents/precheck-reader.md +14 -0
- package/agents/qa-reviewer.md +19 -2
- package/agents/ux-reviewer.md +1 -1
- package/dist/audit.js +34 -7
- package/dist/cli.js +113 -13
- package/dist/config.js +104 -15
- package/dist/deliver.js +10 -3
- package/dist/news.js +17 -1
- package/dist/orchestrate/app.js +189 -0
- package/dist/orchestrate/briefs.js +16 -0
- package/dist/orchestrate/command.js +110 -17
- package/dist/orchestrate/cycle.js +208 -26
- package/dist/orchestrate/guard.js +148 -3
- package/dist/orchestrate/launch.js +43 -5
- package/dist/orchestrate/node-env.js +77 -0
- package/dist/orchestrate/registry.js +5 -4
- package/dist/orchestrate/schemas.js +7 -1
- package/dist/orchestrate/snapshot.js +99 -0
- package/dist/orchestrate/table.js +11 -1
- package/dist/plan.js +29 -0
- package/dist/session.js +6 -3
- package/dist/state.js +34 -14
- package/package.json +1 -1
- package/skills/lead/SKILL.md +17 -8
- package/templates/orchestrate/fix-minors.md +3 -0
- package/templates/orchestrate/fix.md +4 -0
- package/templates/orchestrate/implement.md +5 -1
- package/templates/orchestrate/precheck.md +10 -0
- package/templates/orchestrate/review-recheck.md +2 -0
- package/templates/orchestrate/review-small.md +3 -0
- package/templates/orchestrate/review.md +2 -0
- package/templates/orchestrate/ux.md +2 -0
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cadence",
|
|
3
3
|
"description": "cadence — a repo-native working method for Claude Code sessions",
|
|
4
|
-
"owner": {
|
|
4
|
+
"owner": {
|
|
5
|
+
"name": "Sylvain Ladoire"
|
|
6
|
+
},
|
|
5
7
|
"plugins": [
|
|
6
8
|
{
|
|
7
9
|
"name": "cadence",
|
|
8
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).",
|
|
9
|
-
"version": "0.
|
|
11
|
+
"version": "0.12.0",
|
|
10
12
|
"source": "./",
|
|
11
|
-
"author": {
|
|
13
|
+
"author": {
|
|
14
|
+
"name": "Sylvain Ladoire"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
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. No CLI needed.",
|
|
20
|
+
"version": "0.2.0",
|
|
21
|
+
"source": "./plugins/cadence-hud",
|
|
22
|
+
"author": {
|
|
23
|
+
"name": "Sylvain Ladoire"
|
|
24
|
+
}
|
|
12
25
|
}
|
|
13
26
|
]
|
|
14
27
|
}
|
|
@@ -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.12.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
|
@@ -19,9 +19,15 @@ several projects through subagents — plus three reviewer agents: `ux-reviewer`
|
|
|
19
19
|
gate, and `qa-reviewer`, which walks the delivered app in a real browser and
|
|
20
20
|
reports a page left empty or in error.
|
|
21
21
|
|
|
22
|
+
And a second plugin, [`cadence-hud`](#cadence-hud): a status band above the Claude
|
|
23
|
+
Code prompt that shows the session's context, quota, cost and the orchestrate waves running.
|
|
24
|
+
|
|
22
25
|
## What's new
|
|
23
26
|
|
|
24
|
-
**0.
|
|
27
|
+
**0.12.0**: the `cadence-hud` plugin (a status band above the Claude Code prompt),
|
|
28
|
+
`raf ignore` to acknowledge a commit without a lot, waves that size the review to the lot,
|
|
29
|
+
hand back a lot whose deliverable is already there and start the app themselves for the UX
|
|
30
|
+
review (`orchestrate.ux`), and briefs that require the README to follow the change. **0.11.0**: waves on different repositories run side by side under a shared cap of
|
|
25
31
|
simultaneous sessions (`--max-sessions`, 2 by default), and every release now has its
|
|
26
32
|
changelog section and GitHub release. **0.10.1**: the QA expectations file (`docs/qa/expectations.md`) is kept with the plan —
|
|
27
33
|
a commit that only touches it no longer has to cite a lot. **0.10.0**: `cadence orchestrate`
|
|
@@ -59,7 +65,7 @@ Every version, with what it brings and since when: [CHANGELOG.md](CHANGELOG.md).
|
|
|
59
65
|
```sh
|
|
60
66
|
npm install -g @sylad/cadence # or: npx -p @sylad/cadence raf …
|
|
61
67
|
|
|
62
|
-
raf init # docs/plan/raf.yaml + post-commit hook
|
|
68
|
+
raf init # docs/plan/raf.yaml + post-commit hook + .playwright-mcp/ in .gitignore
|
|
63
69
|
raf add "Monthly dedup on merge" --estimate 2
|
|
64
70
|
raf add "Typo in footer" --quickwin
|
|
65
71
|
raf add "Loan cache" --after L1
|
|
@@ -76,16 +82,19 @@ raf gantt # docs/plan/gantt.html
|
|
|
76
82
|
|
|
77
83
|
| Command | Effect |
|
|
78
84
|
|---|---|
|
|
79
|
-
| `raf init [--project name] [--prefix L] [--no-hook]` | create the plan
|
|
85
|
+
| `raf init [--project name] [--prefix L] [--no-hook]` | create the plan, install the hook, add `.playwright-mcp/` to `.gitignore` |
|
|
80
86
|
| `raf add "title" [--estimate d] [--quickwin] [--visible] [--public "title"] [--after L2,L4] [--parent L3]` | add a lot or a sub-task, print its id (`--public`: the lot's title in the public's words, written as `public:` right after `title:`) |
|
|
81
87
|
| `raf add "title" --every <days>` · `raf did <id> ["text"]` | recurring lot: `every` (days) and `last` (last time done) fields; the lot stays `todo`, `raf now` and `session start` list it under « Récurrent » with « dû depuis N j » / « prochain dans N j »; `raf did` resets the count (spec: `docs/superpowers/specs/2026-10-04-L11-tache-recurrente.md`) |
|
|
82
88
|
| `raf public <id> "title"` · `raf public <id> --clear` | set, replace or remove the public title of a lot |
|
|
83
89
|
| `raf start <id>` · `raf done <id> [--force]` · `raf drop <id> [--reason text]` | dated transitions (`done` refuses open sub-tasks unless `--force`) |
|
|
84
90
|
| `raf note <id> "text"` | dated note — keep decisions next to the work |
|
|
91
|
+
| `raf show <id> [--notes]` | one lot: status, dates, `after`, public title, dated notes in order, then the counted commits (as `raf commits`); `--notes` prints the notes alone |
|
|
85
92
|
| `raf commits <id>` | the commits counted for a lot (the set the code review gate uses), one `<sha> <subject>` per line, oldest first |
|
|
86
93
|
| `raf now` | what to do next |
|
|
87
94
|
| `raf list [--status s]` | flat list |
|
|
88
|
-
| `raf
|
|
95
|
+
| `raf ignore <sha> \| "exact subject" [--reason text]` | acknowledge a commit without a lot (tooling chore, a plan commit citing an unknown id) without rewriting history: a dated, reasoned line in the plan's `acknowledged:` section; a sha is exact, a subject covers every commit carrying it |
|
|
96
|
+
| `raf check --ignored` | list the acknowledged commits with their date and reason |
|
|
97
|
+
| `raf check [--since date] [--idle 7]` | since the plan's adoption date by default (a visible lot without a public title is only a `⚠` warning: it never changes the exit code): commits without a lot (commits touching only plan files are exempt), unknown ids, `todo` lots that already have commits, idle lots, `done` lots with open sub-tasks, bad or circular dependencies |
|
|
89
98
|
| `raf gantt [-o file]` | standalone Gantt page |
|
|
90
99
|
| `raf hook install` | add the (non-blocking, read-only) post-commit hook |
|
|
91
100
|
|
|
@@ -201,7 +210,7 @@ What changed *for the user*, one entry per visible lot, each with a screenshot.
|
|
|
201
210
|
```sh
|
|
202
211
|
raf add "Amounts like 3.000 read as three thousand" --visible # → L8
|
|
203
212
|
raf start L8 && git commit -m "fix(L8): thousands separator" && raf done L8
|
|
204
|
-
cadence news new L8 # docs/nouveautes/2026-09-29-amounts-like-3-000-….md
|
|
213
|
+
cadence news new L8 # docs/nouveautes/2026-09-29-amounts-like-3-000-….md (title: the lot's public title if any)
|
|
205
214
|
# add docs/nouveautes/captures/l8.png, list it under `captures:`, write the text
|
|
206
215
|
cadence news check # exit 1 on drift (also part of raf check)
|
|
207
216
|
cadence news build -o frontend/public/nouveautes
|
|
@@ -230,7 +239,7 @@ captures:
|
|
|
230
239
|
|
|
231
240
|
| Command | Effect |
|
|
232
241
|
|---|---|
|
|
233
|
-
| `cadence news new <lot…> [--title t]` | entry skeleton, dated and timed now (`date`, `created`), titled after the lot |
|
|
242
|
+
| `cadence news new <lot…> [--title t]` | entry skeleton, dated and timed now (`date`, `created`), titled after the lot's public title (`public`), else its title; `--title` wins |
|
|
234
243
|
| `cadence news list` | entries, newest first (see *Order* below) |
|
|
235
244
|
| `cadence news check` | visible lots done without entry, unknown lots, missing or undeclared screenshots, entries without creation time, bad headers |
|
|
236
245
|
| `cadence news stamp` | migration: writes `created:` into entries without one (or with an empty one), from the author date of the commit that added the file under its current name (now if not committed yet) |
|
|
@@ -464,6 +473,19 @@ deliver:
|
|
|
464
473
|
deployTimeout: 1800 # seconds, per deploy command
|
|
465
474
|
```
|
|
466
475
|
|
|
476
|
+
A public title (`public:`) longer than the site's Plan page accepts fails its build. Declare the limit
|
|
477
|
+
in `cadence.yaml` and it is caught before the push:
|
|
478
|
+
|
|
479
|
+
```yaml
|
|
480
|
+
news:
|
|
481
|
+
publicTitleMax: 80 # characters; without the key, `raf check` warns at 80 in a project that has a news directory
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
`raf check` also warns (`⚠`, not an error: exit code 0 if nothing else is wrong) about every visible lot, dropped ones excepted, that has no public title: the site would show its technical title (a done lot cited by a News entry is spared: the site reuses that entry's title). Fix it with `raf public <id> "…"`.
|
|
485
|
+
|
|
486
|
+
With the key, `raf public`, `raf add --public`, `raf done` (on a lot whose public title is too long) and
|
|
487
|
+
`cadence news new` (its title) refuse above it; without it, only `raf check` warns.
|
|
488
|
+
|
|
467
489
|
```sh
|
|
468
490
|
cadence deliver --dry-run # preconditions, then the resolved steps; nothing runs
|
|
469
491
|
cadence deliver # 0 delivered and verified · 1 a step failed · 2 refused before acting
|
|
@@ -651,7 +673,7 @@ cadence orchestrate --resume [<wave>] [--budget 1M] [--answer ol-companion:L22 "
|
|
|
651
673
|
|
|
652
674
|
You choose the lots; the order is the order given (one queue per repository, as many repositories at the
|
|
653
675
|
same time as the session cap allows — 2 by default). `@haiku|@sonnet|@opus` sets the model of the implementation and corrections of that
|
|
654
|
-
lot (default Sonnet; reviews are
|
|
676
|
+
lot (default Sonnet; reviews are Opus, except the light review below; Haiku only when you write it, for a mechanical lot). Run it
|
|
655
677
|
in the background and read `--status`: it prints one line per transition and the final table.
|
|
656
678
|
|
|
657
679
|
**Cycle of a lot**: preconditions (clean tracked files, lot `todo` or `doing`, dependencies met) →
|
|
@@ -664,6 +686,27 @@ you with the findings. A small lot (`estimate` ≤ 0.5 or `quickwin`) gets one s
|
|
|
664
686
|
usability (code only when the lot is not `visible`). Failing tests (reported red, or red when
|
|
665
687
|
`orchestrate.test` is run) go straight to a correction.
|
|
666
688
|
|
|
689
|
+
**Review sized to the lot (L108)**: a lot whose `estimate` is ≤ `orchestrate.review.threshold` (0.25 day by default)
|
|
690
|
+
is *light*: one single review (Sonnet by default, `code-reviewer` agent, same criteria), no minors pass — the minors are
|
|
691
|
+
returned to the lead as notes (proposals). A blocking or major finding on a light lot still triggers a correction, and
|
|
692
|
+
the review that follows is the full one (Opus); the cap of two correction passes is unchanged. A bigger lot keeps the
|
|
693
|
+
chain described above (Opus, two corrections, minors pass). `--dry-run` shows « revue légère » on a light lot.
|
|
694
|
+
|
|
695
|
+
```yaml
|
|
696
|
+
orchestrate:
|
|
697
|
+
review: { threshold: 0.25, light: sonnet, full: opus } # defaults; threshold in days (0 = no light lot), models haiku|sonnet|opus
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
`full` is also the model of the UX review and of the single pass of a small lot that is not light.
|
|
701
|
+
|
|
702
|
+
**Pre-check « deliverable already present? »** (L77). Before the first implementation of a lot that has no commit yet, a
|
|
703
|
+
short read-only session (Sonnet, `precheck` step, brief `templates/orchestrate/precheck.md`) looks in the repository for
|
|
704
|
+
what the lot asks for (another lot, or a correction, may have done it already). It answers `oui` (everything is there,
|
|
705
|
+
with proofs), `partiel` or `non`: on `oui` no implementation session is opened and the lot is handed back
|
|
706
|
+
(`livrable déjà présent : <résumé> (<preuves>)`) for the lead to drop or close it; on `partiel` the finding goes into the
|
|
707
|
+
implementation brief and a warning; on `non` — or an unreadable report, which only adds a warning — the wave goes on. A
|
|
708
|
+
lot that already has commits is never pre-checked (resuming it is legitimate). `orchestrate.precheck: false` turns it off.
|
|
709
|
+
|
|
667
710
|
**A lot that already has commits** (a spec commit, an interrupted wave, a lot committed by hand, a lot sent back after a
|
|
668
711
|
review) still starts with the implementation session: its brief tells it to read the lot, its notes and its open
|
|
669
712
|
sub-tasks, which carry the findings of any earlier review. If that session ends without a new commit on a lot that
|
|
@@ -686,6 +729,18 @@ review with minors: it concludes on that review instead of staying suspended.
|
|
|
686
729
|
|
|
687
730
|
**Choices, not questions**: the author brief tells the session to decide minor interpretation questions itself and to list them under `choix` in its report; the reviewer receives that list to re-read, and the final table prints each one (`choix fait : …`). A session stops with a question only on a real blocker: a decision that changes the scope or the architecture or is costly to undo, AND that the plan, its notes and CLAUDE.md do not settle; everything else is a choice.
|
|
688
731
|
|
|
732
|
+
**Frozen copy of the tool (L61)**: at the real start of a wave (not with `--dry-run` or `--status`), cadence copies the
|
|
733
|
+
parts of its package that run the wave (`bin/`, `dist/`, `templates/`, `agents/`, `skills/`, `package.json`; `node_modules`
|
|
734
|
+
is linked, not copied) into `.cadence/runs/<wave>/tool/` and relaunches the orchestrate process from that copy (the
|
|
735
|
+
parent process waits, relays the signals and exits with the same code or signal). Code, brief templates and agents
|
|
736
|
+
therefore come from the snapshot: a wave can orchestrate cadence itself, and a session of the wave may edit
|
|
737
|
+
`templates/` or rebuild `dist/` without touching the wave running. `--resume` relaunches from the snapshot of the
|
|
738
|
+
resumed wave, never from the current `dist/`; a wave without a snapshot (started before L61) resumes with the current
|
|
739
|
+
package and a warning. The copy is removed with the wave folder. The wave's pid in `--status` is the relaunched process.
|
|
740
|
+
The sessions of the wave also get `.cadence/runs/<wave>/tool/bin` (the `raf` and `cadence` entries of the snapshot) **first in their
|
|
741
|
+
`PATH`**, ahead of the per-project Node below and of the usual `PATH`: the `raf` a reviewer runs (`raf commits`…) is the
|
|
742
|
+
snapshot's, not the installed one. A wave without a snapshot leaves the `PATH` as it is.
|
|
743
|
+
|
|
689
744
|
**What stays with you**: choosing the lots, the questions raised (`--resume --answer`), re-verifying
|
|
690
745
|
after the wave (`git log`, tests, `raf check`), `raf done`, **`raf ux`** (the orchestrator reports the UX
|
|
691
746
|
verdict and screenshots, it does not record it), the push and the deliveries, one project at a time.
|
|
@@ -716,7 +771,9 @@ tree is an incident that stops the wave. `raf done|ux|review` and `cadence deliv
|
|
|
716
771
|
`CADENCE_ORCHESTRATED` is set.
|
|
717
772
|
|
|
718
773
|
**Budget**: the wave counts input + cache writes + output tokens (default 2 M); cache reads are kept and
|
|
719
|
-
shown apart.
|
|
774
|
+
shown apart. Each lot also has its own budget derived from its estimate (400 k tokens per day, floor 200 k, shown by
|
|
775
|
+
`--dry-run`): a lot that spent it gets no further session and is handed back to the lead, the wave budget stays for
|
|
776
|
+
the others. When the budget (or the usage limit) is reached no new session starts, the running ones
|
|
720
777
|
finish, the wave is *suspended* (exit code 3) and `--resume --budget …` continues. A session that returns
|
|
721
778
|
nothing readable, times out (45 min for work, 25 for a review) or fails is not retried; the lot is handed
|
|
722
779
|
back with the cause. Exit codes: 0 every lot ready · 1 at least one lot handed back (question, failure,
|
|
@@ -731,18 +788,23 @@ retry count in the wave budget and in the step (`formatRetry: true`); if the out
|
|
|
731
788
|
the step fails as usual — never a second retry.
|
|
732
789
|
|
|
733
790
|
State is in `.cadence/runs/<wave>/` of the folder where the command is run (added to `.git/info/exclude`
|
|
734
|
-
when that folder is in a repository
|
|
791
|
+
when that folder is in a repository; `.cadence/` and `.playwright-mcp/`, where the Playwright MCP writes its
|
|
792
|
+
output, never count as a dirty repository, even when `.gitignore` does not list them): `wave.json`, one `<project>--<lot>.json` per lot (steps, tokens
|
|
735
793
|
kept apart, session ids, commits, verdicts), the JSON output of every session and a `journal.log`. After a
|
|
736
794
|
cut (Ctrl-C, WSL closed) `--resume` replays an interrupted step entirely in a new session whose brief
|
|
737
795
|
lists the commits already present; finished steps are never replayed.
|
|
738
796
|
|
|
739
797
|
Briefs are the templates of `templates/orchestrate/` (`implement.md` is the `lead` skill's standard
|
|
740
|
-
brief; `--dry-run` writes the rendered ones).
|
|
798
|
+
brief; `--dry-run` writes the rendered ones). The `implement` brief of a `visible` lot also carries the
|
|
799
|
+
News instruction (`cadence news new <lot>`, factual user-side text, a screenshot in `docs/nouveautes/captures/` or
|
|
800
|
+
`nocapture:`, `cadence news check` green); the brief gives the absolute path of the wave's Playwright output directory and the `implement` session gets `--add-dir` on it, to copy the capture into the repository; a lot that is not `visible` gets nothing. A project can declare, in `cadence.yaml`:
|
|
741
801
|
|
|
742
802
|
```yaml
|
|
743
803
|
orchestrate:
|
|
744
804
|
test: npm test # run by the orchestrator after a work step (optional)
|
|
745
|
-
|
|
805
|
+
build: npm run build # run after the tests; both results go to the reviewer, who does not redo them (optional)
|
|
806
|
+
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
|
|
807
|
+
ux: http://localhost:4200 # a URL, a launch command, or { command, url, timeout? } — for the UX review (see below)
|
|
746
808
|
permissionMode: auto # default
|
|
747
809
|
addDirs: [/home/me/projects/tmp] # extra directories the sessions may use
|
|
748
810
|
timeouts: { implement: 45, review: 25 } # minutes
|
|
@@ -751,9 +813,80 @@ orchestrate:
|
|
|
751
813
|
verdict: python3 scripts/raf.py note {lot} "revue de code : {verdict}"
|
|
752
814
|
```
|
|
753
815
|
|
|
816
|
+
**`orchestrate.ux`, who starts the app**: a string is a URL if it starts with `http://` or `https://`, a launch
|
|
817
|
+
command otherwise; both forms behave as before (the `ux` brief gives the URL, or tells the session to start the app
|
|
818
|
+
with the command and stop it). The object form `{ command, url, timeout? }` makes **the program** start the app, not
|
|
819
|
+
the UX session. Before the `ux` step (and before the `review-small` single pass of a small `visible` lot, which follows
|
|
820
|
+
the same rule) the orchestrator:
|
|
821
|
+
|
|
822
|
+
0. takes a lock on the `host:port` of `url` (a file `cadence-ux-<host>-<port>.lock` in the OS temp folder, created
|
|
823
|
+
exclusively, containing the pid of the orchestrator; a lock whose pid is dead is taken over). Another wave — in
|
|
824
|
+
this process or another — that declares the same URL waits for it, and the wait counts in `timeout`; past it the
|
|
825
|
+
note is « UX non vérifiée : url tenue par une autre vague » and nothing is started. The lock is given back after
|
|
826
|
+
the app is stopped. Declaring **distinct ports per project** is still recommended: the lock serialises two waves
|
|
827
|
+
on one URL, it does not make them fast;
|
|
828
|
+
1. probes `url`; if it already answers it does **not** start anything, notes « port occupé » (`uxNote`: the UX is not
|
|
829
|
+
verified) and the lot goes on;
|
|
830
|
+
2. starts `command` with `sh -c` from the repository root, in its own detached process group, its output in
|
|
831
|
+
`<wave>/<project>--<lot>/ux-app.log` (the command carries its own prefixes, e.g. `cd web && PORT=4300 npm start`;
|
|
832
|
+
there is no `env`, `cwd` or account/PIN key);
|
|
833
|
+
3. probes `url` until an HTTP status below 500, for `timeout` seconds (default 300); the answer only counts while
|
|
834
|
+
the process group it started is still alive. A command that exits early, or
|
|
835
|
+
no answer in time, means « UX not verified » with the end of the log in the note: the UX session is skipped,
|
|
836
|
+
the lot goes on to the code review — it is never a failure of the wave (the single pass of a small lot still runs,
|
|
837
|
+
on the code alone, its brief saying the app could not be verified);
|
|
838
|
+
4. gives the session « The running app is at `<url>` », with no instruction to start anything;
|
|
839
|
+
5. at the end of the step — success, error or SIGTERM of the wave — kills the group: SIGTERM, then SIGKILL after 10 s.
|
|
840
|
+
|
|
841
|
+
```yaml
|
|
842
|
+
orchestrate:
|
|
843
|
+
ux: { command: 'cd web && PORT=4300 npm start', url: 'http://localhost:4300', timeout: 120 }
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
In the object form `url` must start with `http://` or `https://`, and `timeout` is only accepted together with both
|
|
847
|
+
`command` and `url` (the program only waits when it starts the app). A wave stopped (incident, quota, budget) before
|
|
848
|
+
the step suspends the lot without starting the app.
|
|
849
|
+
|
|
850
|
+
**Node per project (`.nvmrc`)**: when a project has a `.nvmrc` at its root, every session the
|
|
851
|
+
orchestrator launches for it (implementation, UX and code reviews, corrections) runs with the matching Node
|
|
852
|
+
first in its `PATH`, so `node`, `npm` and `npx` resolve to it (Astro needs 22 while the default may be 20) — and so does
|
|
853
|
+
the app the program starts for the UX review (`ux.command`): same links directory first in its `PATH`, no
|
|
854
|
+
`. ~/.nvm/nvm.sh && nvm use` prefix needed (it exits with code 3 under `sh`). Only
|
|
855
|
+
those four names (`node`, `npm`, `npx`, `corepack`) are exposed, through symlinks in
|
|
856
|
+
`.cadence/runs/<wave>/node-bin/<version>/` (recreated at start and at every `--resume`): the rest of that Node's
|
|
857
|
+
`bin` (globally installed `raf`, `cadence`, `claude`…) is never exposed, so it does not shadow the tools in the usual `PATH`;
|
|
858
|
+
only the snapshot's `tool/bin` (above) comes first, ahead of this Node directory. `--resume` resolves
|
|
859
|
+
the `.nvmrc` again for every live lot and refuses it like at start if it can no longer be resolved.
|
|
860
|
+
The file is read trimmed (`22`, `v22`, `22.22`, `v22.22.3`); the highest matching version installed under
|
|
861
|
+
`$NVM_DIR/versions/node` (default `~/.nvm/versions/node`) **that has an executable `bin/node`** is used: a higher
|
|
862
|
+
version with an empty or broken `bin` is skipped for a lower valid one (and `--dry-run` says so); if none is valid
|
|
863
|
+
the lot is refused. Only the sessions' environment changes,
|
|
864
|
+
never the orchestrator's own. No `.nvmrc` → nothing changes. A `.nvmrc` that cannot be resolved (`lts/*`, an
|
|
865
|
+
alias, a version not installed) refuses the lot before anything is started (exit 2), naming the requested
|
|
866
|
+
version and the folder searched — there is no silent fallback to the default Node. `--dry-run` prints
|
|
867
|
+
`node : v22.22.3 (.nvmrc 22)` under each lot that has one, with the skipped versions when it happens
|
|
868
|
+
(`node : v22.9.0 (.nvmrc 22 ; v22.22.3 écartée : pas de node exécutable)`).
|
|
869
|
+
|
|
870
|
+
**Minimal MCP servers per step**: every session the orchestrator launches gets `--strict-mcp-config --mcp-config
|
|
871
|
+
<file>`, with the file written by the orchestrator in the lot's folder of the wave
|
|
872
|
+
(`.cadence/runs/<wave>/<project>--<lot>/mcp-<step>.json`). `--strict-mcp-config` makes `claude` ignore every other
|
|
873
|
+
source (user, project, plugin servers: Serena, context7, Cloudflare…), so a session no longer starts a handful of
|
|
874
|
+
`npx`/`uvx` servers it does not use. `review` and `review-recheck` always get an empty set
|
|
875
|
+
(`{"mcpServers":{}}`); so do `implement`, `fix` and `fix-minors` on a lot without a screen. Playwright, launched as
|
|
876
|
+
`npx -y @playwright/mcp@latest --output-dir <wave>/<project>--<lot>/playwright`, is loaded for the `ux` step and, on a
|
|
877
|
+
`visible` lot, for `implement` and `fix` (`fix-minors` included) and, on a small `visible` lot, for `review-small` (its only
|
|
878
|
+
usability review; `review-small` of a small lot without a screen loads nothing), so the screenshots and snapshots of the work and of
|
|
879
|
+
the review stay with the wave, outside the repository, where the lead can look at them; the final table prints that
|
|
880
|
+
folder whenever Playwright was loaded for the lot, even if no `ux` step ran
|
|
881
|
+
(`<project>:<lot> — captures Playwright : <dir>`; `uxCaptures` in the wave state). The `implement`, `fix`,
|
|
882
|
+
`fix-minors`, `review-small` and `ux` briefs tell the session to give relative file names if a Playwright server is available, and
|
|
883
|
+
otherwise to keep any Playwright CLI output outside the repository.
|
|
884
|
+
`--dry-run` prints, under each step, `mcp : aucun` or `mcp : playwright (…)`. There is no per-project override in
|
|
885
|
+
`cadence.yaml` yet.
|
|
886
|
+
|
|
754
887
|
Without `start`, a read-only plan's `todo` lot is refused (start it with the project's tool); without
|
|
755
888
|
`verdict`, the review verdict stays in the wave's state and you report it. Only the plan's files
|
|
756
|
-
(`plan.path`, `plan.files`, the QA expectations file) are committed from those commands; anything else dirty stops the lot.
|
|
889
|
+
(`plan.path`, `plan.files`, the QA expectations file) are committed from those commands; anything else dirty stops the lot (`.cadence/` and `.playwright-mcp/` excepted, as above).
|
|
757
890
|
|
|
758
891
|
## Claude Code skills
|
|
759
892
|
|
|
@@ -790,7 +923,7 @@ repository with `cadence skills install` (to `.claude/skills/cadence-*` and
|
|
|
790
923
|
- **lead**: from a folder holding several projects, one subagent per project
|
|
791
924
|
gathers the facts, you choose the priorities, the lots are delegated with
|
|
792
925
|
`cadence orchestrate` (fresh short sessions with a standard brief — test first,
|
|
793
|
-
commits citing the lot, no push — reviewed by the `code-reviewer` agent, see
|
|
926
|
+
README and usage documentation updated with the change, commits citing the lot, no push — reviewed by the `code-reviewer` agent, see
|
|
794
927
|
[orchestrate](#orchestrate)), re-verified by the lead, then delivered
|
|
795
928
|
one project at a time; a delivery that changes what a page shows or what it is
|
|
796
929
|
served is then checked in the running app by the `qa-reviewer` agent, whose
|
|
@@ -807,7 +940,8 @@ repository with `cadence skills install` (to `.claude/skills/cadence-*` and
|
|
|
807
940
|
block, dead code) or a named rule, each with `file:line` and a concrete
|
|
808
941
|
scenario; real defects only, ranked, what it could not verify, and a one-line
|
|
809
942
|
verdict for `raf review`. It takes the lot's commits from `raf commits`, never
|
|
810
|
-
runs a build whose output is used live, and never edits code.
|
|
943
|
+
runs a build whose output is used live, and never edits code. A README or usage
|
|
944
|
+
documentation that does not follow the lot's change is a *major* finding.
|
|
811
945
|
- **qa-reviewer** (agent): any web app; given a repository and a base URL (and
|
|
812
946
|
optionally a lot id, to start with the pages it touched — for a backend-only
|
|
813
947
|
lot, those that call the changed endpoints), it opens each page of
|
|
@@ -823,6 +957,37 @@ repository with `cadence skills install` (to `.claude/skills/cadence-*` and
|
|
|
823
957
|
N/N, follow-ups as `raf add` lines, what it could not verify, a one-line
|
|
824
958
|
verdict. Read-only: GET only, no login, nothing submitted; it stops at a PIN.
|
|
825
959
|
|
|
960
|
+
## cadence-hud
|
|
961
|
+
|
|
962
|
+
A band above the Claude Code prompt (terminal and desktop app), refreshed every 5 s:
|
|
963
|
+
|
|
964
|
+
```
|
|
965
|
+
ctx ▰▰▰▰▱▱▱▱▱▱ 42 % 84k/200k │ 5h 23 % ↻ 2 h 10 │ 7j 61 % ↻ 6 j 15 h │ $1.23 │ fable 410k $0.95 · sonnet 85k $0.12 │ ⚙ 2 agents
|
|
966
|
+
⟳ cadence · 2026-10-07-2131 en cours │ budget ▱▱▱▱▱▱▱▱▱▱ 1 % 14k/2M │ 1 session/2
|
|
967
|
+
L112 implémente implement@sonnet 34 s global-setup coupe le cache de compilation de Node…
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
Context of the session (green / orange / red at 50 and 75 %), the 5-hour and 7-day quota
|
|
971
|
+
windows with the time to their reset, the cost of the session and its split per model, the
|
|
972
|
+
subagents of this session, and every live `cadence orchestrate` wave: budget, sessions, one
|
|
973
|
+
aligned line per active lot (status, step, model, elapsed, start of the title), the lots still
|
|
974
|
+
waiting. When no wave is running, the last finished one stays on a grey line until the next
|
|
975
|
+
starts. Waves are read from disk (`~/.cadence/orchestrate/waves/`, then `.cadence/runs/`); no
|
|
976
|
+
cadence command is run, and the CLI is not required.
|
|
977
|
+
|
|
978
|
+
Install it as a plugin from the same marketplace:
|
|
979
|
+
|
|
980
|
+
```
|
|
981
|
+
/plugin marketplace add Sylad/cadence
|
|
982
|
+
/plugin install cadence-hud@cadence
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
`/hud` hides or shows the band. The source lives in [`plugins/cadence-hud`](plugins/cadence-hud/README.md)
|
|
986
|
+
(its own README has the details of every cell); it is not part of the npm package. To work on it:
|
|
987
|
+
`claude plugin validate plugins/cadence-hud`, `claude plugin test plugins/cadence-hud`, and
|
|
988
|
+
`tsc -p plugins/cadence-hud` once Claude Code has loaded the plugin at least once (it generates
|
|
989
|
+
the `.claude-plugin/types` the tsconfig extends).
|
|
990
|
+
|
|
826
991
|
## Releasing
|
|
827
992
|
|
|
828
993
|
A version exists in three places and is published in two; a release does all of it, in this order:
|
|
@@ -838,9 +1003,10 @@ A version exists in three places and is published in two; a release does all of
|
|
|
838
1003
|
once on npmjs.com (package settings → Trusted Publisher → GitHub Actions, `Sylad/cadence`, `publish.yml`).
|
|
839
1004
|
3. Watch the run: `gh run watch` (or `gh run list --workflow publish.yml`).
|
|
840
1005
|
4. Check the effect: `npm view @sylad/cadence version` answers the new version.
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
1006
|
+
A run is safe to re-run, and two runs for one tag queue instead of racing (`concurrency` per ref, never cancelling the one that publishes):
|
|
1007
|
+
a version already on npm skips `npm publish`, and a GitHub release that is missing is created (`--verify-tag`)
|
|
1008
|
+
while an existing one is left alone. If the package is on npm but the release is still missing, re-run the job;
|
|
1009
|
+
the by-hand fallback is `gh release create v<version> --title v<version> --notes-file <the section of CHANGELOG.md> --verify-tag`.
|
|
844
1010
|
|
|
845
1011
|
The Claude Code plugin is read from the repository, so pushing `main` is what updates it; npm is what
|
|
846
1012
|
`npx @sylad/cadence` and a global install read, and only the tag publishes there. A missing tag, or a red
|
package/agents/code-reviewer.md
CHANGED
|
@@ -35,7 +35,8 @@ fact. If the path or the id is missing, or the lot has no commit to review, say
|
|
|
35
35
|
and a finding must point at a line that exists today. Read what the changed code calls and what
|
|
36
36
|
calls it, far enough to know whether a caller is broken.
|
|
37
37
|
5. **Run what verifies**: the project's tests, type check and linter, with the commands found in
|
|
38
|
-
step 1.
|
|
38
|
+
step 1. When the brief says the program already ran the tests and the build, take those results as given: do not rerun them
|
|
39
|
+
(nor rebuild), and run only a check they do not cover. A linter or coverage tool the project does not have goes under "not verified": it is
|
|
39
40
|
not a finding. Do not run a command that deploys, publishes, pushes, migrates data or reaches a
|
|
40
41
|
remote system, and never run a build whose output directory is used live — the hint is an
|
|
41
42
|
output directory that a `bin` entry or a symlink on the PATH points to; list what you did not
|
|
@@ -56,14 +57,17 @@ fact. If the path or the id is missing, or the lot has no commit to review, say
|
|
|
56
57
|
- errors swallowed, inputs trusted, resources not released, secrets or personal data written
|
|
57
58
|
to a log or to the repository;
|
|
58
59
|
- a written convention of the project not followed: quote the line of CLAUDE.md.
|
|
60
|
+
- documentation that does not follow the change: the lot changes a behaviour, a command, an option or a default and the README (or the project's usage documentation) still describes the old one or says nothing: name the file and the stale or missing passage.
|
|
59
61
|
7. **Rank** each finding: *blocking* (wrong result, lost data, security hole, crash, a command that
|
|
60
62
|
fails), *major* (breaks in a plausible scenario, behaviour changed without a test, a written
|
|
61
|
-
convention broken), *minor* (costs maintenance: duplication, dead code). Untested code that is
|
|
63
|
+
convention broken, a README or usage documentation that does not follow the change), *minor* (costs maintenance: duplication, dead code). Untested code that is
|
|
62
64
|
practically unreachable, and a rule that holds as written while an edge defeats its purpose, are
|
|
63
65
|
*minor* — unless they can lose or corrupt data.
|
|
64
66
|
|
|
65
67
|
## Output
|
|
66
68
|
|
|
69
|
+
A proposed sub-task describes an observable bug (a wrong output, a crash, a measured regression); any other minor finding stays a note of this lot, not a sub-task.
|
|
70
|
+
|
|
67
71
|
A short report:
|
|
68
72
|
|
|
69
73
|
- **Commits reviewed**: sha and subject, and the commands you ran with their result (counts).
|
|
@@ -85,5 +89,6 @@ A short report:
|
|
|
85
89
|
- Judge on taste: naming, formatting or structure you would have written differently is not a
|
|
86
90
|
finding unless a written convention or a named practice says so.
|
|
87
91
|
- Report a finding you have not read in the code or measured, or pad the list: real defects only.
|
|
88
|
-
- Take the author's summary, or a green run you did not launch, as proof
|
|
92
|
+
- Take the author's summary, or a green run you did not launch, as proof — except the results the
|
|
93
|
+
brief says the program ran itself.
|
|
89
94
|
- Edit code, commit, or record `raf review` yourself: the session that owns the lot does it.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: precheck-reader
|
|
3
|
+
description: Read-only pre-check reader. Before a lot that has no commit is implemented, looks in the repository for a deliverable that another lot or a correction already made, and reports oui, partiel or non with a proof (file:line or commit sha) for each part. Quick look only, it implements nothing, runs no test suite and does not review. Used by the orchestrator, not by hand.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You check, quickly, whether the deliverable of one lot of a plan is already in the repository. You report; you never edit code.
|
|
8
|
+
|
|
9
|
+
Follow the brief you are given: read the lot in the plan, then look for each part of its deliverable in the code, the tests and `git log`. A lot with no commit of its own is the normal case here — do not stop for that, it is the reason for the check.
|
|
10
|
+
|
|
11
|
+
- Do not run the test suite, the type-check, the linter or the build: this is a quick look.
|
|
12
|
+
- Do not modify, commit, push, or run `raf start|done|note|review|ux`. Do not launch subagents.
|
|
13
|
+
- Answer `oui` only when every part is present, each with a proof; `partiel` when some are (say which); `non` otherwise. When in doubt, answer `non`.
|
|
14
|
+
- Give the report in the structured output the brief asks for.
|
package/agents/qa-reviewer.md
CHANGED
|
@@ -42,7 +42,7 @@ missing, or the URL does not answer, say so and stop.
|
|
|
42
42
|
write it into the repository. Say plainly that without expectations an empty state cannot be told
|
|
43
43
|
from a normal one.
|
|
44
44
|
4. **Open each page in a real browser** (Playwright, or the browser tool available), at **1440 px**
|
|
45
|
-
and **390 px** wide. Let it settle: after `load`, wait a fixed few seconds, scroll through the
|
|
45
|
+
and **390 px** wide. Walk within the bounded pass below. Let it settle: after `load`, wait a fixed few seconds, scroll through the
|
|
46
46
|
page (lazy images), wait again — never for network idle, which streams and polling never reach.
|
|
47
47
|
Then measure:
|
|
48
48
|
- browser state, once before the first page, in a profile already used (a persistent context, not a fresh one — a `userDataDir` reserved for QA and kept between passes, never the user's own browser profile): compare the bundle the page loaded (its script URL) with the one `index.html` references, re-read without cache — a returning visitor still holds the old one, so a difference is stated in the report — then clear the cache and measure;
|
|
@@ -95,6 +95,23 @@ missing, or the URL does not answer, say so and stop.
|
|
|
95
95
|
false, or an error is shown to the user), *major* (secondary content missing or wrong, a section
|
|
96
96
|
silently dropped after a failed or empty API call, a broken content image), *minor* (noise). A broken line of the expectations with no visible loss on the page (the content is on screen by another path) is *minor* too.
|
|
97
97
|
|
|
98
|
+
## Bounded pass
|
|
99
|
+
|
|
100
|
+
The pass has a time budget: the one the caller names, otherwise 15 minutes. You keep the count
|
|
101
|
+
from the first page.
|
|
102
|
+
|
|
103
|
+
- Never wait in silence on your own background work (a scripted walk, a long browser task, a
|
|
104
|
+
listener): every wait has a timeout and is announced in one line (what you wait for, until
|
|
105
|
+
when). A task still running at its deadline is stopped and its pages counted as partial or not
|
|
106
|
+
reached — you do not wait for it again.
|
|
107
|
+
- Write as you go. After each width measured, append its measurements to a results file in the
|
|
108
|
+
temporary directory — one line per page and width, the page and the width named — before
|
|
109
|
+
measuring the next. Never one single file written at the end of the pass: a pass that is
|
|
110
|
+
stopped loses it all, and a page stopped between its two widths keeps the first.
|
|
111
|
+
- When the budget is spent, or the caller asks you to stop, stop walking and write the report from
|
|
112
|
+
the results file: pages measured at both widths are checked, a page measured at one width is
|
|
113
|
+
partial, the pages not reached are named, never dropped.
|
|
114
|
+
|
|
98
115
|
## Output
|
|
99
116
|
|
|
100
117
|
A short report:
|
|
@@ -102,7 +119,7 @@ A short report:
|
|
|
102
119
|
- **Pages checked N/N**, with the base URL and the date and time of the run, and the two widths. The
|
|
103
120
|
second N is every page of the expectations (or every route discovered): a page you could not open
|
|
104
121
|
is counted and named, never dropped. A page counts as checked when both widths were measured; a
|
|
105
|
-
page checked partially (one width, tabs not opened) is counted and named as partial.
|
|
122
|
+
page checked partially (one width, tabs not opened) is counted and named as partial. If the pass stopped before the end (budget spent, stop requested), say so in the first line, with the pages measured so far.
|
|
106
123
|
- **Findings**, most severe first, each with: the route, its kind and rank, what was expected —
|
|
107
124
|
quote the line of the expectations, or name the universal check, or, for a suspect, give the
|
|
108
125
|
expectation line you propose —, what was measured, and the evidence — status code, response
|
package/agents/ux-reviewer.md
CHANGED
|
@@ -38,7 +38,7 @@ A short report:
|
|
|
38
38
|
"not compliant: 1 blocking".
|
|
39
39
|
- **Findings**, most severe first, each with: what (with the capture), the rule or the measure, the
|
|
40
40
|
proposed change, the effort (S ≤ a session, M ≈ a day).
|
|
41
|
-
- **Proposed sub-tasks**: one `raf add --parent <lot> "…"` line per finding worth doing.
|
|
41
|
+
- **Proposed sub-tasks**: one `raf add --parent <lot> "…"` line per finding worth doing. A proposed sub-task describes an observable bug (a wrong output, a crash, a measured regression); any other minor finding stays a note of this lot, not a sub-task.
|
|
42
42
|
- For any redesign, a **described mockup** (layout, hierarchy, what moves where) to be approved before
|
|
43
43
|
anyone codes it.
|
|
44
44
|
|
package/dist/audit.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
1
2
|
import { dirname, join, relative } from 'node:path';
|
|
2
3
|
import { check } from './check.js';
|
|
3
4
|
import { parse } from 'yaml';
|
|
4
5
|
import { changedFiles, fileAt, readCommits } from './git.js';
|
|
5
6
|
import { linkCommits } from './link.js';
|
|
6
|
-
import {
|
|
7
|
+
import { readNewsConfig } from './config.js';
|
|
8
|
+
import { loadEntries, newsIssues, PUBLIC_TITLE_DEFAULT, publicTitleTooLong, reusedNewsTitle } from './news.js';
|
|
7
9
|
import { isRecurring } from './recurring.js';
|
|
8
10
|
import { isOpen } from './plan.js';
|
|
9
11
|
/** Chemin de la configuration lue, relatif à la racine : celui de --config, sinon cadence.yaml. */
|
|
@@ -49,14 +51,20 @@ export function isPlanOnly(sha, plan, root) {
|
|
|
49
51
|
const files = changedFiles(root, sha);
|
|
50
52
|
return files.length > 0 && files.every((f) => own.has(f) && (f !== config || onlyPlanKeyChanged(root, sha, f)));
|
|
51
53
|
}
|
|
54
|
+
/** Le commit a été acquitté par « raf ignore » : par son sha, ou par son sujet exact. */
|
|
55
|
+
export function isAcknowledged(plan, c) {
|
|
56
|
+
return plan.acknowledged.some((a) => (a.sha !== undefined && a.sha === c.sha) || (a.sha === undefined && a.subject === c.subject));
|
|
57
|
+
}
|
|
52
58
|
/**
|
|
53
|
-
* N'ont pas besoin de citer un lot : un commit d'entretien du plan (cf. isPlanOnly)
|
|
54
|
-
* automatique dont le sujet correspond à un motif `ignore:` du plan
|
|
59
|
+
* N'ont pas besoin de citer un lot : un commit d'entretien du plan (cf. isPlanOnly), un commit
|
|
60
|
+
* automatique dont le sujet correspond à un motif `ignore:` du plan et un commit acquitté (`raf ignore`) ;
|
|
61
|
+
* un commit acquitté n'est pas non plus signalé pour un identifiant cité inconnu.
|
|
55
62
|
*/
|
|
56
63
|
export function exemptPlanOnly(linked, plan, root) {
|
|
57
64
|
const { patterns } = plan.ignore;
|
|
58
|
-
const
|
|
59
|
-
|
|
65
|
+
const acked = (c) => isAcknowledged(plan, c);
|
|
66
|
+
const orphans = linked.orphans.filter((c) => !patterns.some((re) => re.test(c.subject)) && !acked(c) && !isPlanOnly(c.sha, plan, root));
|
|
67
|
+
return { ...linked, orphans, unknown: linked.unknown.filter((u) => !acked(u.commit)) };
|
|
60
68
|
}
|
|
61
69
|
/** Fenêtre de l'audit : --since, sinon la date d'adoption du plan, sinon 30 jours. */
|
|
62
70
|
export function auditSince(plan, explicit) {
|
|
@@ -65,6 +73,15 @@ export function auditSince(plan, explicit) {
|
|
|
65
73
|
// Une date seule vaudrait « ce jour-là à l'heure actuelle » pour git : minuit explicite.
|
|
66
74
|
return plan.since ? `${plan.since} 00:00` : '30 days ago';
|
|
67
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Avertissements : un lot visible (hors abandonné) sans titre public — le site retombe sur le titre technique du plan,
|
|
78
|
+
* sauf pour un lot terminé qu'une Nouveauté cite : le site en reprend le titre (cf. reusedNewsTitle).
|
|
79
|
+
*/
|
|
80
|
+
export function missingPublicTitles(lots, entries) {
|
|
81
|
+
return lots
|
|
82
|
+
.filter((l) => l.visible && l.status !== 'dropped' && !l.public && reusedNewsTitle(l, entries) === null)
|
|
83
|
+
.map((l) => ({ message: `${l.id} : lot visible sans titre public — raf public ${l.id} "…"`, warning: true }));
|
|
84
|
+
}
|
|
68
85
|
/** Écarts entre le plan, l'historique et les Nouveautés — ce que `raf check` affiche. */
|
|
69
86
|
export function audit(plan, root, newsDir, today, opts = {}) {
|
|
70
87
|
const lots = plan.lots();
|
|
@@ -82,10 +99,20 @@ export function audit(plan, root, newsDir, today, opts = {}) {
|
|
|
82
99
|
// Un plan en lecture seule ne reçoit aucun verdict de raf : les deux portes n'y valent pas, même si
|
|
83
100
|
// uxSince ou reviewSince y sont écrits à la main — l'écart ne pourrait jamais être levé.
|
|
84
101
|
const gates = plan.readonly ? [] : [...uxIssues(plan), ...reviewIssues(plan, root, all.byLot)];
|
|
85
|
-
|
|
102
|
+
// Un titre public trop long fait échouer le build du site : signalé dès ici, avec 80 caractères sans clé déclarée
|
|
103
|
+
// — mais seulement si le projet publie des Nouveautés ; sans site, aucune limite ne s'applique.
|
|
104
|
+
const max = readNewsConfig(plan.configFile ?? join(root, 'cadence.yaml')).publicTitleMax ?? (existsSync(newsDir) ? PUBLIC_TITLE_DEFAULT : Infinity);
|
|
105
|
+
// Sans public:, le site reprend le titre de la Nouveauté la plus récente du lot terminé (cas ccc L32).
|
|
106
|
+
const entries = loadEntries(newsDir);
|
|
107
|
+
const titles = lots.flatMap((l) => {
|
|
108
|
+
const reused = reusedNewsTitle(l, entries);
|
|
109
|
+
const m = l.public ? publicTitleTooLong(l.public, max) : reused ? publicTitleTooLong(reused, max) : null;
|
|
110
|
+
return m ? [{ message: `${l.id} : ${reused && !l.public ? m.replace('titre public', 'titre public repris de la Nouveauté') : m}` }] : [];
|
|
111
|
+
});
|
|
112
|
+
const issues = [...check(lots, { ...linked, byLot: all.byLot }, today, opts.idle ?? 7), ...newsIssues(lots, entries, newsDir), ...titles, ...gates, ...missingPublicTitles(lots, entries),
|
|
86
113
|
...plan.ignore.invalid.map((src) => ({ message: `ignore : motif invalide « ${src} »` }))];
|
|
87
114
|
// Un plan en lecture seule se corrige avec l'outil du projet : ne pas conseiller une commande raf qui refuserait.
|
|
88
|
-
return plan.readonly ? issues.map((i) => ({ ...i, message: i.message.replace(/ — raf start .*$/, '') })) : issues;
|
|
115
|
+
return plan.readonly ? issues.map((i) => ({ ...i, message: i.message.replace(/ — raf (start|public) .*$/, '') })) : issues;
|
|
89
116
|
}
|
|
90
117
|
/** Commits qui portent du travail sur un lot : ni antérieurs à l'adoption du plan, ni réduits au plan. */
|
|
91
118
|
function workCommits(plan, root, commits) {
|