@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +185 -11
- package/agents/code-reviewer.md +89 -0
- package/bin/cadence.js +3 -2
- package/dist/audit.js +95 -5
- package/dist/check.js +2 -1
- package/dist/cli.js +100 -46
- package/dist/config.js +106 -0
- package/dist/dates.js +8 -0
- package/dist/deliver.js +87 -29
- package/dist/git.js +34 -1
- package/dist/link.js +3 -3
- package/dist/news.js +82 -9
- package/dist/plan.js +203 -19
- package/dist/session.js +36 -6
- package/package.json +3 -2
- package/skills/deliver/SKILL.md +23 -1
- package/skills/lead/SKILL.md +80 -0
- package/skills/session-close/SKILL.md +17 -0
- package/skills/session-start/SKILL.md +25 -1
|
@@ -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.
|
|
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
|
|
4
|
-
"version": "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
|
|
15
|
-
into rituals — `session-start`, `session-close`, `deliver
|
|
16
|
-
|
|
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
|
|
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
|
|
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
|
|
215
|
-
the `ux-reviewer`
|
|
216
|
-
`cadence skills install` (to `.claude/skills/cadence-*` and
|
|
217
|
-
`.claude/agents/cadence
|
|
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
|
-
/**
|
|
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 =
|
|
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,
|
|
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,
|
|
29
|
-
|
|
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
|
-
|
|
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));
|