@sylad/cadence 0.2.0 → 0.5.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.
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "name": "cadence",
8
8
  "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.2.0",
9
+ "version": "0.5.0",
10
10
  "source": "./",
11
11
  "author": { "name": "Sylvain Ladoire" }
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cadence",
3
- "description": "A repo-native working method: session start and close rituals driven by a versioned plan (raf), deliveries proven by their effect, and a UX reviewer agent.",
4
- "version": "0.2.0",
3
+ "description": "A repo-native working method: session start and close rituals driven by a versioned plan (raf), deliveries proven by their effect, and two reviewer agents (UX, code).",
4
+ "version": "0.5.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
@@ -11,9 +11,11 @@ Four tools:
11
11
  - **session**: the facts to start and to close a work session;
12
12
  - **deliver**: wait for the CI of the pushed commit, deploy, then **verify the effect**.
13
13
 
14
- And three [Claude Code](https://claude.com/claude-code) skills that turn them
15
- into rituals — `session-start`, `session-close`, `deliver` — plus a `ux-reviewer`
16
- agent: no user-facing change is done before its usability review.
14
+ And four [Claude Code](https://claude.com/claude-code) skills that turn them
15
+ into rituals — `session-start`, `session-close`, `deliver`, and `lead` to pilot
16
+ several projects through subagents — plus two reviewer agents, each behind an
17
+ opt-in gate: `ux-reviewer` (no user-facing change is done before its usability
18
+ review) and `code-reviewer` (no lot with commits is done before its code review).
17
19
 
18
20
  ## raf
19
21
 
@@ -49,6 +51,7 @@ raf gantt # docs/plan/gantt.html
49
51
  | `raf add "title" [--estimate d] [--quickwin] [--visible] [--after L2,L4] [--parent L3]` | add a lot or a sub-task, print its id |
50
52
  | `raf start <id>` · `raf done <id> [--force]` · `raf drop <id> [--reason text]` | dated transitions (`done` refuses open sub-tasks unless `--force`) |
51
53
  | `raf note <id> "text"` | dated note — keep decisions next to the work |
54
+ | `raf commits <id>` | the commits counted for a lot (the set the code review gate uses), one `<sha> <subject>` per line, oldest first |
52
55
  | `raf now` | what to do next |
53
56
  | `raf list [--status s]` | flat list |
54
57
  | `raf check [--since date] [--idle 7]` | since the plan's adoption date by default: commits without a lot (commits touching only the plan are exempt), unknown ids, `todo` lots that already have commits, idle lots, `done` lots with open sub-tasks, bad or circular dependencies |
@@ -65,6 +68,7 @@ version: 1
65
68
  project: my-app
66
69
  prefix: L
67
70
  since: 2026-09-28 # commits before this date are not audited
71
+ ignore: ['^chore\(batch\):'] # optional: subjects of automated commits, neither audited nor counted for a lot
68
72
  lots:
69
73
  - id: L1
70
74
  title: Monthly dedup on merge
@@ -81,6 +85,57 @@ lots:
81
85
  - { id: t1, title: write the migration, status: done }
82
86
  ```
83
87
 
88
+ ### A plan elsewhere, or in another format
89
+
90
+ `cadence.yaml`, at the repository root, can say where the plan is. The short form keeps raf's own
91
+ format, and the plan stays writable:
92
+
93
+ ```yaml
94
+ plan: planning/todo.yaml
95
+ ```
96
+
97
+ A project that already keeps its plan with its own tool is read **without migrating it**: describe
98
+ the file, and `raf now`, `raf list`, `raf commits`, `raf check`, `raf gantt` and
99
+ `cadence session start|close` work on it. Such a plan is **read-only** —
100
+ `raf add|start|done|note|ux|review` refuse and leave the file to the project's tool, and the two
101
+ review gates do not apply to it (a `uxSince` or `reviewSince` written in it is ignored).
102
+
103
+ ```yaml
104
+ plan:
105
+ path: docs/plan/taches.yaml
106
+ project: my-app # what the file does not say itself: project, since, ignore
107
+ since: 2026-10-02
108
+ ignore: ['^plan: ']
109
+ files: [docs/plan/journal.ndjson] # kept with the plan: a commit touching only these is a plan commit
110
+ lots: taches # root key holding the list (default: lots)
111
+ fields: # raf field: key in the file (a list = first one present)
112
+ title: titre
113
+ status: etat
114
+ estimate: effort
115
+ created: cree_le
116
+ started: demarre_le
117
+ finished: [livre_le, ferme_le]
118
+ notes: note
119
+ parent: parent
120
+ statuses: # raf status: their states
121
+ todo: [prevu, specifie]
122
+ doing: [en_cours, teste]
123
+ done: [deploye, valide]
124
+ dropped: caduc
125
+ estimates: { S: 0.5, M: 1, L: 3 } # their effort labels, in working days
126
+ ```
127
+
128
+ - Fields: `id`, `title`, `status`, `estimate`, `quickwin`, `visible`, `after`, `created`, `started`,
129
+ `finished`, `notes`, `parent`; one left out is read under its own name. A timestamp counts for
130
+ its day; a note written as plain text is one note.
131
+ - `parent`: an entry `B33/t1-fusion` whose parent is `B33` becomes the sub-task `t1-fusion` of `B33`.
132
+ - Ids need no prefix: a commit belongs to a lot when its message cites one of the plan's ids as a
133
+ whole word (`E-A2`, `NC2.4`, `B33/t1-fusion`).
134
+ An id that is not in the plan cannot be told from ordinary text: such a commit counts as
135
+ "without a lot", never as an unknown reference.
136
+ - A state missing from `statuses`, or an effort label missing from `estimates`, is reported by
137
+ `raf check`.
138
+
84
139
  ### Gantt scheduling
85
140
 
86
141
  One lane of work. Finished lots use their real dates (or their commits' dates);
@@ -112,6 +167,7 @@ cadence news build -o frontend/public/nouveautes
112
167
  ---
113
168
  title: Amounts like 3.000 read as three thousand
114
169
  date: 2026-09-29
170
+ created: 2026-09-29T14:32+02:00
115
171
  lots: [L8]
116
172
  captures: [captures/l8.png]
117
173
  # nocapture: reason, when a screenshot makes no sense
@@ -121,9 +177,10 @@ Imported statements now read **3.000** as three thousand, not three.
121
177
 
122
178
  | Command | Effect |
123
179
  |---|---|
124
- | `cadence news new <lot…> [--title t]` | entry skeleton, dated today, titled after the lot |
125
- | `cadence news list` | entries, newest first |
126
- | `cadence news check` | visible lots done without entry, unknown lots, missing or undeclared screenshots, bad headers |
180
+ | `cadence news new <lot…> [--title t]` | entry skeleton, dated and timed now (`date`, `created`), titled after the lot |
181
+ | `cadence news list` | entries, newest first (see *Order* below) |
182
+ | `cadence news check` | visible lots done without entry, unknown lots, missing or undeclared screenshots, entries without creation time, bad headers |
183
+ | `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) |
127
184
  | `cadence news build [-o dir]` | `nouveautes.json` + `index.html` + screenshots (default `docs/nouveautes/site`) |
128
185
 
129
186
  `--dir` changes the entries folder (default `docs/nouveautes` at the git root).
@@ -132,6 +189,21 @@ The Markdown is deliberately small: paragraphs, `-` lists, `**bold**`,
132
189
  `{ project, generated, entries: [{ slug, title, date, lots, captures, html }] }`,
133
190
  with screenshot paths relative to the JSON file.
134
191
 
192
+ **Order.** Everywhere (`list`, `build`, the JSON), entries are strictly newest
193
+ first: by `date`, then, on the same day, by creation time. Every entry carries
194
+ it to the minute in its `created` header, which `cadence news new` writes as
195
+ local time with an explicit offset (`2026-09-29T14:32+02:00`, or `Z`). The
196
+ offset is required, so the order does not depend on the machine's time zone:
197
+ `news check` (and `raf check`) flags a value without it, an impossible one, an
198
+ empty `created:`, and an entry without `created`. `cadence news stamp` fills
199
+ it in older entries (or replaces an empty one) from the author date of the
200
+ commit that added the file under its current name; until then, that date is
201
+ used for sorting (an entry not committed yet counts as the newest). Renames
202
+ are not followed: a renamed entry counts as added on the day of the rename,
203
+ so stamp it before renaming it. The file name only breaks the remaining ties,
204
+ since it follows the title, not the chronology. The page and the JSON still
205
+ show the day only.
206
+
135
207
  ### UX review
136
208
 
137
209
  ```sh
@@ -142,7 +214,32 @@ raf ux L8 "no screen: calculation fix"
142
214
 
143
215
  With the rule on, `raf done` refuses a visible lot without a review (`--force`
144
216
  to override) and `raf check` reports visible lots finished after the `uxSince`
145
- day without one. Plans without `uxSince` are not affected.
217
+ day without one. An empty verdict is refused. Plans without `uxSince` are not
218
+ affected.
219
+
220
+ ### Code review
221
+
222
+ ```sh
223
+ raf review enable # from today, a lot with commits needs a code review before done
224
+ raf commits L4 # what there is to review: the commits the gate counts for the lot
225
+ raf review L4 "compliant after 2 fixes" # record the verdict (from the code-reviewer agent)
226
+ ```
227
+
228
+ The counterpart of the UX review, off by default. With the rule on, `raf done`
229
+ refuses a lot that has at least one commit citing it and no recorded verdict
230
+ (`--force` to override), and `raf check` reports such lots finished after the
231
+ `reviewSince` day. A lot with no commit has nothing to review; neither does a
232
+ lot whose only commits touch the plan itself, predate the plan's `since` or
233
+ match an `ignore:` pattern — `raf commits <id>` prints exactly the counted set.
234
+
235
+ The verdict is tied to what was reviewed: `raf review` stores it on the lot with
236
+ the sha of the lot's latest counted commit (`review: { date, verdict, commit }`,
237
+ `commit: null` when the lot had none). A counted commit made after that one
238
+ makes the review stale: `raf done` refuses (`--force` to override), and
239
+ `raf check` reports a finished lot, until the lot is reviewed again and
240
+ `raf review` is rerun. A verdict
241
+ written by hand without a `commit` field is not checked for staleness. An empty
242
+ verdict is refused. Plans without `reviewSince` are not affected.
146
243
 
147
244
  ## session
148
245
 
@@ -161,6 +258,20 @@ done), quick wins first. Local state lives in the git directory, never committed
161
258
  the close notes per worktree, the delivery lock and log in `.git/cadence/`, shared
162
259
  by all the worktrees of a clone.
163
260
 
261
+ A project that already has its own morning and evening scripts keeps them: name
262
+ them in `cadence.yaml` and their output is added to the report, under "Faits
263
+ propres au projet", before the proposals (start) or the verdict (close).
264
+
265
+ ```yaml
266
+ session:
267
+ start: ./scripts/morning.sh "$CADENCE_SINCE" # sh, at the repo root, 120 s at most
268
+ close: ./scripts/evening.sh "$CADENCE_SINCE"
269
+ ```
270
+
271
+ The commands get `CADENCE_SINCE` (the `--since` in effect) and `CADENCE_TODAY`. They
272
+ add facts and decide nothing: a failing command is reported and changes neither
273
+ the exit code nor the verdict.
274
+
164
275
  ## deliver
165
276
 
166
277
  A delivery is done when its checks pass, not when a tool says "success".
@@ -202,6 +313,37 @@ cadence deliver # 0 delivered and verified · 1 a step failed · 2
202
313
  - On success the lots cited by the commits since the previous delivery are
203
314
  listed, so you can `raf done` those whose effect you have seen.
204
315
 
316
+ ### A project with its own delivery script
317
+
318
+ A project that already delivers with its own script (CI wait, deploy, business
319
+ checks) plugs it in instead of rewriting it as `ci` / `deploy` / `verify`:
320
+
321
+ ```yaml
322
+ deliver:
323
+ script: ./scripts/ship.sh "$CADENCE_SHORT" # replaces ci and deploy
324
+ allowDirty: true # optional: a modified tree is reported, not refused
325
+ deployTimeout: 3600 # seconds, for the whole script
326
+ verify: [] # optional here: the script's own checks count
327
+ ```
328
+
329
+ ```sh
330
+ cadence deliver -- api frontend --news docs/changelog/x.md -- map # everything after the first « -- » goes to the script
331
+ cadence deliver --dry-run -- api # shows the full command, runs nothing
332
+ cadence deliver --sha 6b0d9aa -- api # an earlier pushed commit instead of HEAD
333
+ ```
334
+
335
+ `--sha` (any mode) delivers a pushed commit other than `HEAD` — for a CI that
336
+ builds each service only on the commit that touched it. `allowDirty` suits a
337
+ working tree shared by several sessions when the delivery starts from a pushed
338
+ sha and never from local files; without it a modified tree is refused.
339
+
340
+ cadence keeps what the script usually lacks: the preconditions (clean tree, pushed
341
+ `HEAD`), the lock (never two deliveries at once), the delivery log and the lots
342
+ delivered. Arguments are quoted for `sh`, so spaces and quotes reach the script
343
+ intact. If the script commits and pushes during the delivery (stamping a
344
+ changelog entry, say), the new `HEAD` is the sha recorded as delivered. Exit code
345
+ 0 of the script means delivered; `verify` checks, if any, run after it.
346
+
205
347
  ## Claude Code skills
206
348
 
207
349
  As a plugin:
@@ -211,10 +353,10 @@ As a plugin:
211
353
  /plugin install cadence@cadence
212
354
  ```
213
355
 
214
- gives `/cadence:session-start`, `/cadence:session-close`, `/cadence:deliver` and
215
- the `ux-reviewer` agent. Or copy them into the repository with
216
- `cadence skills install` (to `.claude/skills/cadence-*` and
217
- `.claude/agents/cadence-ux-reviewer.md`; `--dir` for another `.claude` folder,
356
+ gives `/cadence:session-start`, `/cadence:session-close`, `/cadence:deliver`,
357
+ `/cadence:lead` and the `ux-reviewer` and `code-reviewer` agents. Or copy them into the
358
+ repository with `cadence skills install` (to `.claude/skills/cadence-*` and
359
+ `.claude/agents/cadence-*.md`; `--dir` for another `.claude` folder,
218
360
  `--force` to overwrite local edits).
219
361
 
220
362
  - **session-start**: reports the facts briefly, proposes three lots from the
@@ -222,12 +364,44 @@ the `ux-reviewer` agent. Or copy them into the repository with
222
364
  - **session-close**: plan hygiene, clean repository, memory limited to what the
223
365
  repository does not say, new skills or agents proposed but never created, three
224
366
  lines for next time.
367
+ - A project with its own tooling keeps it: its plan is read where it is (`plan:`),
368
+ its delivery script is called by `cadence deliver` (`deliver.script`), its
369
+ morning and evening scripts feed the session report (`session:`), and its own
370
+ skills can become one-line aliases of `session-start` / `session-close`.
225
371
  - **deliver**: dry run, delivery, and on failure the cause fixed rather than a
226
372
  blind retry.
373
+ - **lead**: from a folder holding several projects, one subagent per project
374
+ gathers the facts, you choose the priorities, each lot is delegated to a
375
+ subagent with a standard brief (test first, commits citing the lot, no push),
376
+ reviewed by the `code-reviewer` agent, re-verified by the lead, then delivered
377
+ one project at a time. Two subagents at most, never two in the same repository.
227
378
  - **ux-reviewer** (agent): captures at 1440 and 390 px, findings grounded in a
228
379
  named rule (Nielsen, WCAG 2.2 AA) or a measurement, ranked, turned into
229
380
  `raf add --parent` sub-tasks, and a one-line verdict for `raf ux`. It never
230
381
  edits code.
382
+ - **code-reviewer** (agent): any stack; given a repository and a lot id, it reads
383
+ the diff itself from the commits that cite the lot — not the author's summary —
384
+ and the project's CLAUDE.md, when there is one, for its conventions; findings grounded in a
385
+ measurement (a failing command, changed code without a test, a duplicated
386
+ block, dead code) or a named rule, each with `file:line` and a concrete
387
+ scenario; real defects only, ranked, what it could not verify, and a one-line
388
+ verdict for `raf review`. It takes the lot's commits from `raf commits`, never
389
+ runs a build whose output is used live, and never edits code.
390
+
391
+ ## Releasing
392
+
393
+ A version exists in three places and is published in two; a release does all of it, in this order:
394
+
395
+ 1. Bump `version` in `package.json` (then `npm install` to refresh `package-lock.json`),
396
+ `.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json`, in the commit that closes the lot.
397
+ 2. `npm publish --access public` — `prepublishOnly` runs the type-check and the tests first, `prepare`
398
+ builds `dist/`; a red suite stops the publication.
399
+ 3. `git tag v<version> && git push origin main v<version>`.
400
+ 4. Check the effect: `npm view @sylad/cadence version` answers the new version.
401
+
402
+ The Claude Code plugin is read from the repository, so step 3 is what updates it; npm is what
403
+ `npx @sylad/cadence` and a global install read. Skipping step 2 leaves npm behind without any error —
404
+ 0.3.0 and 0.4.0 were never published.
231
405
 
232
406
  ## License
233
407
 
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: code-reviewer
3
+ description: Code reviewer for any stack — reviews the commits of one lot of the plan before it is marked done. Given a repository path and a lot id, it reads the diff itself from the commits that cite the lot, never from the author's summary; grounds every finding in a measurement (a command that fails, changed code without a test, a duplicated block, dead code, a size) or a named rule (a convention quoted from the project's CLAUDE.md, a named language or framework practice), never in taste; reports real defects only, ranked, each with file:line and a concrete failure or maintenance scenario, and ends with a one-line verdict for `raf review <lot>`. Use when a lot that has commits is about to be closed, or after a subagent reports its work. Does not modify code.
4
+ tools: Read, Grep, Glob, Bash
5
+ ---
6
+
7
+ You review the code of one lot of a plan. You report; you never edit code.
8
+
9
+ ## Inputs
10
+
11
+ The absolute path of the repository and the id of the lot. Nothing else is needed, and anything else
12
+ you are given — the author's report, a list of files, "the tests pass" — is a claim to check, not a
13
+ fact. If the path or the id is missing, or the lot has no commit to review, say so and stop.
14
+
15
+ ## Method
16
+
17
+ 1. **Read the rules of the project first**: its CLAUDE.md (and the files it points to). Note the
18
+ conventions that are written down: only those, and the named practices of the language or
19
+ framework in use, can be held against the code. If the repository has no CLAUDE.md, say so in
20
+ the report and hold only the lot's goal and the named practices against the code — a parent
21
+ folder's CLAUDE.md does not count unless it names this project. Then find the commands that
22
+ test, type-check, lint and build: in the README, then in the manifest (`package.json` scripts,
23
+ Makefile, `pyproject.toml`…).
24
+ 2. **Read the lot**: its title, notes and sub-tasks in the plan (`docs/plan/raf.yaml`, or the file
25
+ named by `plan:` in `cadence.yaml`). That is the goal the commits are measured against. When the
26
+ lot has only a title, also read the bodies of its commits and any spec the lot cites.
27
+ 3. **Get the commits from the tool**: `raf commits <id>` prints exactly the set the gate counts —
28
+ `<sha> <subject>` per line, oldest first, the commits that only touch the plan left out. Only
29
+ when `raf` is not available, fall back to
30
+ `git log --reverse --format='%h %cs %s' -E --grep='(^|[^[:alnum:]_/.-])<id>($|[^[:alnum:]_.-]|\.($|[^[:alnum:]_]))'`
31
+ (escape the dots of the id; the right guard keeps a `NC2.4` commit out of lot `NC2`) and drop
32
+ the commits that only touch the plan. List the commits you review in the report.
33
+ 4. **Read the diff yourself**: `git show --stat <sha>` then `git show <sha>` for each commit, and
34
+ every changed file as it stands now — a later commit may have moved what an earlier one wrote,
35
+ and a finding must point at a line that exists today. Read what the changed code calls and what
36
+ calls it, far enough to know whether a caller is broken.
37
+ 5. **Run what verifies**: the project's tests, type check and linter, with the commands found in
38
+ step 1. A linter or coverage tool the project does not have goes under "not verified": it is
39
+ not a finding. Do not run a command that deploys, publishes, pushes, migrates data or reaches a
40
+ remote system, and never run a build whose output directory is used live — the hint is an
41
+ output directory that a `bin` entry or a symlink on the PATH points to; list what you did not
42
+ run under "not verified". An experiment (a reproduction, a scratch repository) is allowed in a
43
+ temporary directory outside the repository, removed afterwards: the working tree is left as you
44
+ found it.
45
+ 6. **Check, and measure where a number exists:**
46
+ - does the code do what the lot says, in the cases the lot names and at their edges (empty,
47
+ absent, twice, in the wrong order, refused);
48
+ - changed behaviour without a test: name the changed function or branch and show that no test
49
+ reaches it (`grep` its name in the tests, or run the coverage if the project has it);
50
+ - a test that cannot fail, or that asserts something else than what its title says;
51
+ - a duplicated block: both places, the number of lines, what will drift when only one is fixed;
52
+ - dead code: a symbol the lot added or orphaned, with the search that finds no reference;
53
+ - sizes: lines of a changed file or function before and after the lot — a size is a finding
54
+ only against a limit the project states, or through its consequence (two responsibilities in
55
+ one function, one of them untested);
56
+ - errors swallowed, inputs trusted, resources not released, secrets or personal data written
57
+ to a log or to the repository;
58
+ - a written convention of the project not followed: quote the line of CLAUDE.md.
59
+ 7. **Rank** each finding: *blocking* (wrong result, lost data, security hole, crash, a command that
60
+ 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
62
+ practically unreachable, and a rule that holds as written while an edge defeats its purpose, are
63
+ *minor* — unless they can lose or corrupt data.
64
+
65
+ ## Output
66
+
67
+ A short report:
68
+
69
+ - **Commits reviewed**: sha and subject, and the commands you ran with their result (counts).
70
+ - **Findings**, most severe first, each with: `file:line`, what is wrong, the measurement or the
71
+ named rule it rests on, and the scenario — the input or the sequence that fails, or the change
72
+ that will be made wrong later because of it. No finding without all four.
73
+ - **Not verified**: what you could not run or see (no test environment, a deployed effect, an
74
+ external service, uncommitted changes in the working tree), stated plainly.
75
+ - **Proposed sub-tasks**: one `raf add --parent <lot> "…"` line per finding worth doing; on a
76
+ read-only plan (`cadence.yaml` maps the fields of a file kept by another tool), plain lines for
77
+ the project's own tool instead.
78
+ - **Verdict**, one line, alone, suitable for `raf review <lot> "…"` — e.g. "compliant",
79
+ "compliant after 2 fixes", "not compliant: 1 blocking". When there is nothing to report, say so
80
+ in that one line: an empty list of findings is a valid review. It is the last line of the
81
+ review; extra sections a caller asks for come after it.
82
+
83
+ ## Do not
84
+
85
+ - Judge on taste: naming, formatting or structure you would have written differently is not a
86
+ finding unless a written convention or a named practice says so.
87
+ - 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.
89
+ - Edit code, commit, or record `raf review` yourself: the session that owns the lot does it.
package/bin/cadence.js CHANGED
@@ -27,8 +27,9 @@ if (tool === 'raf') {
27
27
  faits de reprise : notes de la veille, en cours, fait depuis, écarts, propositions
28
28
  cadence session close [--since …] faits de clôture ; code 1 tant que ce n'est pas fermé
29
29
  cadence session next "ligne" … notes pour la prochaine session (sans argument : efface)
30
- cadence deliver [--dry-run] [--config cadence.yaml]
31
- CI du sha poussé → déploiement → vérifications de l'effet
30
+ cadence deliver [--dry-run] [--sha rév] [--config cadence.yaml] [-- arguments du script du projet]
31
+ CI du sha poussé → déploiement → vérifications de l'effet ;
32
+ ou le script de livraison du projet (deliver.script), sous verrou et journal
32
33
  cadence skills install [--dir .claude] [--force]
33
34
  installe les skills Claude Code session-start, session-close, deliver et l'agent ux-reviewer`);
34
35
  process.exitCode = !tool || ['help', '--help', '-h'].includes(tool) ? 0 : 2;
package/dist/audit.js CHANGED
@@ -4,10 +4,32 @@ import { changedFiles, readCommits } from './git.js';
4
4
  import { linkCommits } from './link.js';
5
5
  import { loadEntries, newsIssues } from './news.js';
6
6
  import { isOpen } from './plan.js';
7
- /** Un commit qui ne touche que le plan (ou la page Gantt) n'a pas besoin de citer un lot. */
7
+ /** Le plan, sa page Gantt et les fichiers tenus avec lui (cadence.yaml : plan.files). */
8
+ function ownFiles(plan, root) {
9
+ return new Set([relative(root, plan.path), relative(root, join(dirname(plan.path), 'gantt.html')), ...plan.files]);
10
+ }
11
+ /** Commits du dépôt sans les commits automatiques (motifs `ignore`) : ni audités ni comptés pour un lot. */
12
+ export function planCommits(plan, root, opts = {}) {
13
+ const { patterns } = plan.ignore;
14
+ const commits = readCommits(root, opts);
15
+ return patterns.length ? commits.filter((c) => !patterns.some((re) => re.test(c.subject))) : commits;
16
+ }
17
+ /** Le commit ne touche-t-il que le plan (ou la page Gantt) ? */
18
+ export function isPlanOnly(sha, plan, root) {
19
+ const own = ownFiles(plan, root);
20
+ const files = changedFiles(root, sha);
21
+ return files.length > 0 && files.every((f) => own.has(f));
22
+ }
23
+ /**
24
+ * N'ont pas besoin de citer un lot : un commit qui ne touche que le plan (ou la page Gantt), et un
25
+ * commit automatique dont le sujet correspond à un motif `ignore:` du plan.
26
+ */
8
27
  export function exemptPlanOnly(linked, plan, root) {
9
- const own = new Set([relative(root, plan.path), relative(root, join(dirname(plan.path), 'gantt.html'))]);
28
+ const own = ownFiles(plan, root);
29
+ const { patterns } = plan.ignore;
10
30
  const orphans = linked.orphans.filter((c) => {
31
+ if (patterns.some((re) => re.test(c.subject)))
32
+ return false;
11
33
  const files = changedFiles(root, c.sha);
12
34
  return files.length === 0 || !files.every((f) => own.has(f));
13
35
  });
@@ -23,10 +45,58 @@ export function auditSince(plan, explicit) {
23
45
  /** Écarts entre le plan, l'historique et les Nouveautés — ce que `raf check` affiche. */
24
46
  export function audit(plan, root, newsDir, today, opts = {}) {
25
47
  const lots = plan.lots();
26
- const linked = exemptPlanOnly(linkCommits(lots, readCommits(root, { since: auditSince(plan, opts.since) }), plan.prefix), plan, root);
48
+ const linked = exemptPlanOnly(linkCommits(lots, planCommits(plan, root, { since: auditSince(plan, opts.since) }), plan.refs), plan, root);
27
49
  // L'inactivité se mesure sur tout l'historique, pas seulement la fenêtre --since.
28
- const all = linkCommits(lots, readCommits(root), plan.prefix);
29
- return [...check(lots, { ...linked, byLot: all.byLot }, today, opts.idle ?? 7), ...newsIssues(lots, loadEntries(newsDir), newsDir), ...uxIssues(plan)];
50
+ const all = linkCommits(lots, planCommits(plan, root), plan.refs);
51
+ // Planifier un lot (commit qui ne touche que le plan) n'est pas y travailler : un lot « todo »
52
+ // cité seulement par de tels commits n'a pas à être démarré. Ni par des commits antérieurs à
53
+ // l'adoption du plan : citer un identifiant n'engageait alors à rien.
54
+ for (const l of lots) {
55
+ const cs = all.byLot.get(l.id);
56
+ if (l.status === 'todo' && cs)
57
+ all.byLot.set(l.id, workCommits(plan, root, cs));
58
+ }
59
+ // Un plan en lecture seule ne reçoit aucun verdict de raf : les deux portes n'y valent pas, même si
60
+ // uxSince ou reviewSince y sont écrits à la main — l'écart ne pourrait jamais être levé.
61
+ const gates = plan.readonly ? [] : [...uxIssues(plan), ...reviewIssues(plan, root, all.byLot)];
62
+ const issues = [...check(lots, { ...linked, byLot: all.byLot }, today, opts.idle ?? 7), ...newsIssues(lots, loadEntries(newsDir), newsDir), ...gates,
63
+ ...plan.ignore.invalid.map((src) => ({ message: `ignore : motif invalide « ${src} »` }))];
64
+ // Un plan en lecture seule se corrige avec l'outil du projet : ne pas conseiller une commande raf qui refuserait.
65
+ return plan.readonly ? issues.map((i) => ({ ...i, message: i.message.replace(/ — raf start .*$/, '') })) : issues;
66
+ }
67
+ /** Commits qui portent du travail sur un lot : ni antérieurs à l'adoption du plan, ni réduits au plan. */
68
+ function workCommits(plan, root, commits) {
69
+ const adopted = plan.since;
70
+ return commits.filter((c) => (!adopted || c.day >= adopted) && !isPlanOnly(c.sha, plan, root));
71
+ }
72
+ /** Commits liés à un lot, du plus récent au plus ancien, avant tout tri : commits de plan et d'avant l'adoption compris. */
73
+ function lotCommits(plan, root, lotId) {
74
+ return linkCommits(plan.lots(), planCommits(plan, root), plan.refs).byLot.get(lotId) ?? [];
75
+ }
76
+ /** Commits à relire d'un lot, du plus récent au plus ancien — ce que compte la porte de revue de code et que liste `raf commits`. */
77
+ export function lotWork(plan, root, lotId) {
78
+ return workCommits(plan, root, lotCommits(plan, root, lotId));
79
+ }
80
+ /**
81
+ * Commits à relire que la revue de code d'un lot ne couvre pas, parmi ses commits liés (du plus récent
82
+ * au plus ancien) : tous sans verdict ; aucun pour un verdict qui ne dit pas quel commit il a relu
83
+ * (écrit à la main) ; sinon ceux qui suivent le commit relu — tous quand il n'y en avait aucun, ou
84
+ * quand il n'est plus parmi ceux du lot (historique réécrit) : la revue ne se rattache alors à rien.
85
+ */
86
+ function unreviewed(plan, root, review, linked) {
87
+ if (!review)
88
+ return workCommits(plan, root, linked);
89
+ const reviewed = review.commit;
90
+ if (reviewed === undefined)
91
+ return [];
92
+ const at = reviewed === null ? -1 : linked.findIndex((c) => c.sha === reviewed);
93
+ // Seuls les commits postérieurs sont examinés : un lot relu à son dernier commit ne coûte aucun appel git.
94
+ return workCommits(plan, root, at < 0 ? linked : linked.slice(0, at));
95
+ }
96
+ /** Nombre de commits d'un lot que sa revue de code ne couvre pas — ce que `raf done` refuse quand la porte est active. */
97
+ export function unreviewedWork(plan, root, lotId) {
98
+ const lot = plan.lots().find((l) => l.id === lotId);
99
+ return lot ? unreviewed(plan, root, lot.review, lotCommits(plan, root, lotId)).length : 0;
30
100
  }
31
101
  /**
32
102
  * Lots visibles terminés APRÈS le jour d'activation sans revue UX enregistrée. Le jour même est exclu :
@@ -42,6 +112,26 @@ export function uxIssues(plan) {
42
112
  .filter((l) => l.visible && l.status === 'done' && !l.ux && (!l.finished || l.finished > since))
43
113
  .map((l) => ({ message: `${l.id} est visible et terminé sans revue UX — raf ux ${l.id} "verdict"` }));
44
114
  }
115
+ /**
116
+ * Lots terminés APRÈS le jour d'activation avec des commits à relire que leur revue de code ne couvre
117
+ * pas : aucun verdict, ou un verdict antérieur à ces commits. Même règle de date que `uxIssues` ; un
118
+ * lot sans commit n'a rien à faire relire.
119
+ */
120
+ export function reviewIssues(plan, root, byLot) {
121
+ const since = plan.reviewSince;
122
+ if (!since)
123
+ return [];
124
+ return plan
125
+ .lots()
126
+ .filter((l) => l.status === 'done' && (!l.finished || l.finished > since))
127
+ .map((l) => ({ id: l.id, reviewed: !!l.review, commits: unreviewed(plan, root, l.review, byLot.get(l.id) ?? []).length }))
128
+ .filter((l) => l.commits > 0)
129
+ .map((l) => ({
130
+ message: l.reviewed
131
+ ? `${l.id} est terminé avec ${l.commits} commit(s) postérieur(s) à sa revue de code, à refaire — raf review ${l.id} "verdict"`
132
+ : `${l.id} est terminé avec ${l.commits} commit(s) sans revue de code — raf review ${l.id} "verdict"`,
133
+ }));
134
+ }
45
135
  /** Ce qui vient ensuite : lots en cours, puis lots prêts (dépendances closes), gains rapides d'abord. */
46
136
  export function nextUp(lots) {
47
137
  const byId = new Map(lots.map((l) => [l.id, l]));
package/dist/check.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { diffDays } from './dates.js';
2
2
  import { isOpen } from './plan.js';
3
- const short = (c) => `${c.sha.slice(0, 7)} ${c.subject}`;
3
+ /** Un commit sur une ligne : sha abrégé et sujet. */
4
+ export const short = (c) => `${c.sha.slice(0, 7)} ${c.subject}`;
4
5
  export function check(lots, linked, today, idleDays = 7) {
5
6
  const issues = [];
6
7
  const ids = new Set(lots.map((l) => l.id));