@sylad/cadence 0.2.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 +14 -0
- package/.claude-plugin/plugin.json +10 -0
- package/LICENSE +21 -0
- package/README.md +234 -0
- package/agents/ux-reviewer.md +49 -0
- package/bin/cadence.js +35 -0
- package/bin/raf.js +10 -0
- package/dist/audit.js +54 -0
- package/dist/check.js +60 -0
- package/dist/cli.js +384 -0
- package/dist/dates.js +63 -0
- package/dist/deliver.js +305 -0
- package/dist/gantt.js +176 -0
- package/dist/git.js +91 -0
- package/dist/hook.js +36 -0
- package/dist/link.js +32 -0
- package/dist/news.js +200 -0
- package/dist/plan.js +255 -0
- package/dist/schedule.js +55 -0
- package/dist/session.js +108 -0
- package/dist/skills.js +61 -0
- package/dist/state.js +126 -0
- package/package.json +28 -0
- package/skills/deliver/SKILL.md +41 -0
- package/skills/session-close/SKILL.md +33 -0
- package/skills/session-start/SKILL.md +38 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cadence",
|
|
3
|
+
"description": "cadence — a repo-native working method for Claude Code sessions",
|
|
4
|
+
"owner": { "name": "Sylvain Ladoire" },
|
|
5
|
+
"plugins": [
|
|
6
|
+
{
|
|
7
|
+
"name": "cadence",
|
|
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",
|
|
10
|
+
"source": "./",
|
|
11
|
+
"author": { "name": "Sylvain Ladoire" }
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
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",
|
|
5
|
+
"author": { "name": "Sylvain Ladoire" },
|
|
6
|
+
"homepage": "https://github.com/Sylad/cadence",
|
|
7
|
+
"repository": "https://github.com/Sylad/cadence",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"keywords": ["planning", "workflow", "delivery", "changelog", "sessions"]
|
|
10
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sylvain Ladoire
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# cadence
|
|
2
|
+
|
|
3
|
+
A small working method that lives in your repository. Solo developers and
|
|
4
|
+
AI-assisted sessions lose track of *what is left to do* and *what each commit
|
|
5
|
+
was for*; cadence keeps both in plain files next to the code.
|
|
6
|
+
|
|
7
|
+
Four tools:
|
|
8
|
+
|
|
9
|
+
- **raf** (French *reste à faire*, "what is left to do"): the plan, linked to your commits;
|
|
10
|
+
- **news**: a user-facing changelog with screenshots, tied to the plan;
|
|
11
|
+
- **session**: the facts to start and to close a work session;
|
|
12
|
+
- **deliver**: wait for the CI of the pushed commit, deploy, then **verify the effect**.
|
|
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.
|
|
17
|
+
|
|
18
|
+
## raf
|
|
19
|
+
|
|
20
|
+
- The plan is a YAML file in the repo (`docs/plan/raf.yaml`), edited by the CLI.
|
|
21
|
+
Your comments and hand edits are preserved.
|
|
22
|
+
- A commit belongs to a lot when its message cites the id: `feat(L3): …`,
|
|
23
|
+
`fix: L3/t1 …`. The link is **computed from `git log`**, never stored, so
|
|
24
|
+
committing never dirties the plan.
|
|
25
|
+
- `raf check` audits drift between the plan and the history.
|
|
26
|
+
- `raf gantt` writes a single self-contained HTML page (no server, no CDN).
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npm install -g @sylad/cadence # or: npx -p @sylad/cadence raf …
|
|
30
|
+
|
|
31
|
+
raf init # docs/plan/raf.yaml + post-commit hook
|
|
32
|
+
raf add "Monthly dedup on merge" --estimate 2
|
|
33
|
+
raf add "Typo in footer" --quickwin
|
|
34
|
+
raf add "Loan cache" --after L1
|
|
35
|
+
raf add "write the migration" --parent L1 # → L1/t1
|
|
36
|
+
raf start L1
|
|
37
|
+
git commit -m "feat(L1): dedup by calendar month"
|
|
38
|
+
raf now # in progress, next up (quickwins first), recently done
|
|
39
|
+
raf done L1/t1 && raf done L1
|
|
40
|
+
raf check # exit 1 on drift
|
|
41
|
+
raf gantt # docs/plan/gantt.html
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Commands
|
|
45
|
+
|
|
46
|
+
| Command | Effect |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `raf init [--project name] [--prefix L] [--no-hook]` | create the plan and install the hook |
|
|
49
|
+
| `raf add "title" [--estimate d] [--quickwin] [--visible] [--after L2,L4] [--parent L3]` | add a lot or a sub-task, print its id |
|
|
50
|
+
| `raf start <id>` · `raf done <id> [--force]` · `raf drop <id> [--reason text]` | dated transitions (`done` refuses open sub-tasks unless `--force`) |
|
|
51
|
+
| `raf note <id> "text"` | dated note — keep decisions next to the work |
|
|
52
|
+
| `raf now` | what to do next |
|
|
53
|
+
| `raf list [--status s]` | flat list |
|
|
54
|
+
| `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 |
|
|
55
|
+
| `raf gantt [-o file]` | standalone Gantt page |
|
|
56
|
+
| `raf hook install` | add the (non-blocking, read-only) post-commit hook |
|
|
57
|
+
|
|
58
|
+
Global options: `--file path` or `RAF_FILE`; `RAF_TODAY=YYYY-MM-DD` to freeze
|
|
59
|
+
the date. `cadence raf …` is the same as `raf …`.
|
|
60
|
+
|
|
61
|
+
### Plan file
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
version: 1
|
|
65
|
+
project: my-app
|
|
66
|
+
prefix: L
|
|
67
|
+
since: 2026-09-28 # commits before this date are not audited
|
|
68
|
+
lots:
|
|
69
|
+
- id: L1
|
|
70
|
+
title: Monthly dedup on merge
|
|
71
|
+
status: doing # todo | doing | done | dropped
|
|
72
|
+
estimate: 2 # working days
|
|
73
|
+
quickwin: false
|
|
74
|
+
visible: true # user-facing: a news entry is expected when done
|
|
75
|
+
after: [L0]
|
|
76
|
+
created: 2026-09-28
|
|
77
|
+
started: 2026-09-29
|
|
78
|
+
notes:
|
|
79
|
+
- { date: 2026-09-29, text: "keep the bank line when both sources exist" }
|
|
80
|
+
tasks:
|
|
81
|
+
- { id: t1, title: write the migration, status: done }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Gantt scheduling
|
|
85
|
+
|
|
86
|
+
One lane of work. Finished lots use their real dates (or their commits' dates);
|
|
87
|
+
lots in progress run until `max(start + estimate, today)`; lots to do follow in
|
|
88
|
+
file order, after their dependencies, on working days.
|
|
89
|
+
|
|
90
|
+
## news
|
|
91
|
+
|
|
92
|
+
What changed *for the user*, one entry per visible lot, each with a screenshot.
|
|
93
|
+
|
|
94
|
+
- Mark a lot as user-facing with `raf add … --visible` (or `visible: true` in
|
|
95
|
+
the YAML). `raf done` and the post-commit hook remind you to write its entry;
|
|
96
|
+
`raf check` fails while a visible lot is done without one.
|
|
97
|
+
- An entry is a Markdown file in `docs/nouveautes/`, with a YAML header.
|
|
98
|
+
Screenshots are files you take yourself, stored next to the entries.
|
|
99
|
+
- `cadence news build` writes `nouveautes.json` for your app to display, a
|
|
100
|
+
self-contained `index.html`, and copies the screenshots.
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
raf add "Amounts like 3.000 read as three thousand" --visible # → L8
|
|
104
|
+
raf start L8 && git commit -m "fix(L8): thousands separator" && raf done L8
|
|
105
|
+
cadence news new L8 # docs/nouveautes/2026-09-29-amounts-like-3-000-….md
|
|
106
|
+
# add docs/nouveautes/captures/l8.png, list it under `captures:`, write the text
|
|
107
|
+
cadence news check # exit 1 on drift (also part of raf check)
|
|
108
|
+
cadence news build -o frontend/public/nouveautes
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```markdown
|
|
112
|
+
---
|
|
113
|
+
title: Amounts like 3.000 read as three thousand
|
|
114
|
+
date: 2026-09-29
|
|
115
|
+
lots: [L8]
|
|
116
|
+
captures: [captures/l8.png]
|
|
117
|
+
# nocapture: reason, when a screenshot makes no sense
|
|
118
|
+
---
|
|
119
|
+
Imported statements now read **3.000** as three thousand, not three.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
| Command | Effect |
|
|
123
|
+
|---|---|
|
|
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 |
|
|
127
|
+
| `cadence news build [-o dir]` | `nouveautes.json` + `index.html` + screenshots (default `docs/nouveautes/site`) |
|
|
128
|
+
|
|
129
|
+
`--dir` changes the entries folder (default `docs/nouveautes` at the git root).
|
|
130
|
+
The Markdown is deliberately small: paragraphs, `-` lists, `**bold**`,
|
|
131
|
+
`` `code` ``, `[links](url)`; everything else is escaped text. The JSON holds
|
|
132
|
+
`{ project, generated, entries: [{ slug, title, date, lots, captures, html }] }`,
|
|
133
|
+
with screenshot paths relative to the JSON file.
|
|
134
|
+
|
|
135
|
+
### UX review
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
raf ux enable # from today, a --visible lot needs a UX review before done
|
|
139
|
+
raf ux L4 "compliant after 2 fixes" # record the verdict (from the ux-reviewer agent)
|
|
140
|
+
raf ux L8 "no screen: calculation fix"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
With the rule on, `raf done` refuses a visible lot without a review (`--force`
|
|
144
|
+
to override) and `raf check` reports visible lots finished after the `uxSince`
|
|
145
|
+
day without one. Plans without `uxSince` are not affected.
|
|
146
|
+
|
|
147
|
+
## session
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
cadence session start # notes from the last close, lots in progress (silent ones flagged),
|
|
151
|
+
# work done since yesterday by lot, drift, repo state, 3 proposals
|
|
152
|
+
cadence session start --since "3 days ago" --idle 2
|
|
153
|
+
cadence session close # today's commits by lot, commits without a lot, lots in progress
|
|
154
|
+
# with no commit today, drift, uncommitted / unpushed work
|
|
155
|
+
# exit 1 while something is still open
|
|
156
|
+
cadence session next "finish L3" "review L4" # shown by the next session start
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Proposals come from the plan only: lots in progress, then ready lots (dependencies
|
|
160
|
+
done), quick wins first. Local state lives in the git directory, never committed:
|
|
161
|
+
the close notes per worktree, the delivery lock and log in `.git/cadence/`, shared
|
|
162
|
+
by all the worktrees of a clone.
|
|
163
|
+
|
|
164
|
+
## deliver
|
|
165
|
+
|
|
166
|
+
A delivery is done when its checks pass, not when a tool says "success".
|
|
167
|
+
|
|
168
|
+
```yaml
|
|
169
|
+
# cadence.yaml, at the repository root
|
|
170
|
+
deliver:
|
|
171
|
+
ci: github # github | none | { command: "…" } (default: none)
|
|
172
|
+
ciTimeout: 1800 # seconds
|
|
173
|
+
deploy: # sh commands, in order, at the repo root
|
|
174
|
+
- ./scripts/deploy.sh "$CADENCE_SHORT"
|
|
175
|
+
verify: # at least one; retried every 10 s until verifyTimeout
|
|
176
|
+
- url: https://app.example.com/api/health
|
|
177
|
+
status: 200 # default 200
|
|
178
|
+
- url: https://app.example.com/version.txt
|
|
179
|
+
contains: "${SHORT}"
|
|
180
|
+
- command: kubectl rollout status deploy/app --timeout=60s
|
|
181
|
+
verifyTimeout: 300
|
|
182
|
+
deployTimeout: 1800 # seconds, per deploy command
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
```sh
|
|
186
|
+
cadence deliver --dry-run # preconditions, then the resolved steps; nothing runs
|
|
187
|
+
cadence deliver # 0 delivered and verified · 1 a step failed · 2 refused before acting
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
- **Preconditions**: no modified tracked file; `HEAD` is on a remote branch (the
|
|
191
|
+
CI can only build what was pushed); no other delivery running (a lock whose
|
|
192
|
+
process died is removed with a warning); with `ci: github`, `gh` installed and
|
|
193
|
+
logged in.
|
|
194
|
+
- Every command is killed when it exceeds its budget (CI, `deployTimeout`, what is
|
|
195
|
+
left of `verifyTimeout`) and reported as "délai dépassé".
|
|
196
|
+
- **CI** `github`: polls `gh run list --commit <sha>` every 15 s; no run after
|
|
197
|
+
5 minutes is a failure (you probably pushed another commit than the one you
|
|
198
|
+
deliver); every run must end `success`, `skipped` or `neutral`. `gh` errors
|
|
199
|
+
are retried, and reported with their cause after 5 minutes.
|
|
200
|
+
- Commands get `CADENCE_SHA`, `CADENCE_SHORT` (7 characters) and `CADENCE_BRANCH`;
|
|
201
|
+
`${SHA}` and `${SHORT}` are replaced in `url` and `contains`.
|
|
202
|
+
- On success the lots cited by the commits since the previous delivery are
|
|
203
|
+
listed, so you can `raf done` those whose effect you have seen.
|
|
204
|
+
|
|
205
|
+
## Claude Code skills
|
|
206
|
+
|
|
207
|
+
As a plugin:
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
/plugin marketplace add Sylad/cadence
|
|
211
|
+
/plugin install cadence@cadence
|
|
212
|
+
```
|
|
213
|
+
|
|
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,
|
|
218
|
+
`--force` to overwrite local edits).
|
|
219
|
+
|
|
220
|
+
- **session-start**: reports the facts briefly, proposes three lots from the
|
|
221
|
+
plan, then waits for your priority — nothing starts before your answer.
|
|
222
|
+
- **session-close**: plan hygiene, clean repository, memory limited to what the
|
|
223
|
+
repository does not say, new skills or agents proposed but never created, three
|
|
224
|
+
lines for next time.
|
|
225
|
+
- **deliver**: dry run, delivery, and on failure the cause fixed rather than a
|
|
226
|
+
blind retry.
|
|
227
|
+
- **ux-reviewer** (agent): captures at 1440 and 390 px, findings grounded in a
|
|
228
|
+
named rule (Nielsen, WCAG 2.2 AA) or a measurement, ranked, turned into
|
|
229
|
+
`raf add --parent` sub-tasks, and a one-line verdict for `raf ux`. It never
|
|
230
|
+
edits code.
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ux-reviewer
|
|
3
|
+
description: Usability and accessibility reviewer for any web interface — reviews a page, a screen or a user-facing change before it is marked done, and runs the planned reviews of existing screens. Grounds every finding in a named rule (Nielsen heuristics, WCAG 2.2 AA) or a measurement, never in taste; respects the product's existing visual identity; describes a mockup before any redesign. Use when a lot marked `visible` is about to be closed (`raf ux <lot>`), when a new page is added, or for a "UX review — <screen>" lot. Does not modify code.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You review the usability and accessibility of a web interface. You report; you never edit code.
|
|
7
|
+
|
|
8
|
+
## Inputs
|
|
9
|
+
|
|
10
|
+
The lot or screen to review, and how to reach it (local dev server, deployed URL, route). If the
|
|
11
|
+
screen needs data or a login, ask for — or find in the project docs — the way to get a realistic state
|
|
12
|
+
(demo data, stubbed API). Read the project's CLAUDE.md and design notes first: the product's existing
|
|
13
|
+
identity (colours, density, tone) is a constraint, not something to "fix".
|
|
14
|
+
|
|
15
|
+
## Method
|
|
16
|
+
|
|
17
|
+
1. **Look before judging.** Capture the screen with the browser tool available (Playwright or
|
|
18
|
+
equivalent) at **1440 px** and **390 px** wide, in its main states: empty, loaded, error, loading,
|
|
19
|
+
and the key interaction. Look at every capture. Store them in the project's temporary folder.
|
|
20
|
+
2. **Walk the main task** a real user comes for (find, read, compare, act, undo) and count the steps.
|
|
21
|
+
3. **Check, and measure where a number exists:**
|
|
22
|
+
- Nielsen's 10 heuristics — especially visibility of system status, match with the user's words,
|
|
23
|
+
consistency, error prevention and recovery, recognition rather than recall;
|
|
24
|
+
- WCAG 2.2 AA — text contrast ≥ 4.5:1 (≥ 3:1 for large text and UI parts), everything usable with
|
|
25
|
+
the keyboard, visible focus, targets ≥ 24×24 px (44 px recommended on touch), text alternatives,
|
|
26
|
+
no information carried by colour alone, content that reflows at 320 px without horizontal scroll;
|
|
27
|
+
- data display — units shown, numbers aligned and formatted for the locale, charts readable without
|
|
28
|
+
their legend colours alone, empty states that say what to do;
|
|
29
|
+
- on the phone width, a dense control-heavy page is often better **hidden or reduced** than squeezed.
|
|
30
|
+
4. **Rank** each finding: *blocking* (a user cannot complete the task, or an accessibility failure),
|
|
31
|
+
*major* (slows or misleads), *minor* (polish).
|
|
32
|
+
|
|
33
|
+
## Output
|
|
34
|
+
|
|
35
|
+
A short report:
|
|
36
|
+
|
|
37
|
+
- **Verdict** in one line, suitable for `raf ux <lot> "…"` — e.g. "compliant", "compliant after 2 fixes",
|
|
38
|
+
"not compliant: 1 blocking".
|
|
39
|
+
- **Findings**, most severe first, each with: what (with the capture), the rule or the measure, the
|
|
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.
|
|
42
|
+
- For any redesign, a **described mockup** (layout, hierarchy, what moves where) to be approved before
|
|
43
|
+
anyone codes it.
|
|
44
|
+
|
|
45
|
+
## Do not
|
|
46
|
+
|
|
47
|
+
- Judge on taste, or propose a new visual identity.
|
|
48
|
+
- Report a finding you have not seen in a capture or measured.
|
|
49
|
+
- Edit code, commit, or record `raf ux` yourself: the session that owns the lot does it.
|
package/bin/cadence.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { run } from '../dist/cli.js';
|
|
4
|
+
|
|
5
|
+
const [tool, ...args] = process.argv.slice(2);
|
|
6
|
+
const io = {
|
|
7
|
+
cwd: process.cwd(),
|
|
8
|
+
env: process.env,
|
|
9
|
+
out: (l) => console.log(l),
|
|
10
|
+
err: (l) => console.error(l),
|
|
11
|
+
now: () => new Date(),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
if (tool === 'raf') {
|
|
15
|
+
process.exitCode = await run(args, io);
|
|
16
|
+
} else if (['news', 'session', 'deliver', 'skills'].includes(tool)) {
|
|
17
|
+
process.exitCode = await run([tool, ...args], io);
|
|
18
|
+
} else if (tool === '--version' || tool === '-v') {
|
|
19
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
20
|
+
console.log(pkg.version);
|
|
21
|
+
} else {
|
|
22
|
+
console.log(`cadence — une méthode de travail qui vit dans le dépôt
|
|
23
|
+
|
|
24
|
+
cadence raf … plan « reste à faire » relié aux commits (aussi disponible en « raf »)
|
|
25
|
+
cadence news … Nouveautés : une entrée avec capture par lot visible, JSON + page autonome
|
|
26
|
+
cadence session start [--since "24 hours ago"] [--idle 2]
|
|
27
|
+
faits de reprise : notes de la veille, en cours, fait depuis, écarts, propositions
|
|
28
|
+
cadence session close [--since …] faits de clôture ; code 1 tant que ce n'est pas fermé
|
|
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
|
|
32
|
+
cadence skills install [--dir .claude] [--force]
|
|
33
|
+
installe les skills Claude Code session-start, session-close, deliver et l'agent ux-reviewer`);
|
|
34
|
+
process.exitCode = !tool || ['help', '--help', '-h'].includes(tool) ? 0 : 2;
|
|
35
|
+
}
|
package/bin/raf.js
ADDED
package/dist/audit.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { dirname, join, relative } from 'node:path';
|
|
2
|
+
import { check } from './check.js';
|
|
3
|
+
import { changedFiles, readCommits } from './git.js';
|
|
4
|
+
import { linkCommits } from './link.js';
|
|
5
|
+
import { loadEntries, newsIssues } from './news.js';
|
|
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. */
|
|
8
|
+
export function exemptPlanOnly(linked, plan, root) {
|
|
9
|
+
const own = new Set([relative(root, plan.path), relative(root, join(dirname(plan.path), 'gantt.html'))]);
|
|
10
|
+
const orphans = linked.orphans.filter((c) => {
|
|
11
|
+
const files = changedFiles(root, c.sha);
|
|
12
|
+
return files.length === 0 || !files.every((f) => own.has(f));
|
|
13
|
+
});
|
|
14
|
+
return { ...linked, orphans };
|
|
15
|
+
}
|
|
16
|
+
/** Fenêtre de l'audit : --since, sinon la date d'adoption du plan, sinon 30 jours. */
|
|
17
|
+
export function auditSince(plan, explicit) {
|
|
18
|
+
if (explicit)
|
|
19
|
+
return explicit;
|
|
20
|
+
// Une date seule vaudrait « ce jour-là à l'heure actuelle » pour git : minuit explicite.
|
|
21
|
+
return plan.since ? `${plan.since} 00:00` : '30 days ago';
|
|
22
|
+
}
|
|
23
|
+
/** Écarts entre le plan, l'historique et les Nouveautés — ce que `raf check` affiche. */
|
|
24
|
+
export function audit(plan, root, newsDir, today, opts = {}) {
|
|
25
|
+
const lots = plan.lots();
|
|
26
|
+
const linked = exemptPlanOnly(linkCommits(lots, readCommits(root, { since: auditSince(plan, opts.since) }), plan.prefix), plan, root);
|
|
27
|
+
// 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)];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Lots visibles terminés APRÈS le jour d'activation sans revue UX enregistrée. Le jour même est exclu :
|
|
33
|
+
* à la journée près, on ne distingue pas un lot fermé avant l'activation d'un lot fermé après
|
|
34
|
+
* (et `raf done` refuse de toute façon dès l'activation).
|
|
35
|
+
*/
|
|
36
|
+
export function uxIssues(plan) {
|
|
37
|
+
const since = plan.uxSince;
|
|
38
|
+
if (!since)
|
|
39
|
+
return [];
|
|
40
|
+
return plan
|
|
41
|
+
.lots()
|
|
42
|
+
.filter((l) => l.visible && l.status === 'done' && !l.ux && (!l.finished || l.finished > since))
|
|
43
|
+
.map((l) => ({ message: `${l.id} est visible et terminé sans revue UX — raf ux ${l.id} "verdict"` }));
|
|
44
|
+
}
|
|
45
|
+
/** Ce qui vient ensuite : lots en cours, puis lots prêts (dépendances closes), gains rapides d'abord. */
|
|
46
|
+
export function nextUp(lots) {
|
|
47
|
+
const byId = new Map(lots.map((l) => [l.id, l]));
|
|
48
|
+
const doing = lots.filter((l) => l.status === 'doing');
|
|
49
|
+
const ready = lots
|
|
50
|
+
.filter((l) => l.status === 'todo' && l.after.every((d) => !byId.has(d) || !isOpen(byId.get(d).status)))
|
|
51
|
+
.sort((a, b) => Number(b.quickwin) - Number(a.quickwin));
|
|
52
|
+
const blocked = lots.filter((l) => l.status === 'todo' && !ready.includes(l));
|
|
53
|
+
return { doing, ready, blocked };
|
|
54
|
+
}
|
package/dist/check.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { diffDays } from './dates.js';
|
|
2
|
+
import { isOpen } from './plan.js';
|
|
3
|
+
const short = (c) => `${c.sha.slice(0, 7)} ${c.subject}`;
|
|
4
|
+
export function check(lots, linked, today, idleDays = 7) {
|
|
5
|
+
const issues = [];
|
|
6
|
+
const ids = new Set(lots.map((l) => l.id));
|
|
7
|
+
for (const c of linked.orphans)
|
|
8
|
+
issues.push({ kind: 'orphan-commit', message: `commit sans lot : ${short(c)}` });
|
|
9
|
+
for (const u of linked.unknown) {
|
|
10
|
+
issues.push({ kind: 'unknown-ref', message: `${u.ref} inconnu, cité par ${short(u.commit)}` });
|
|
11
|
+
}
|
|
12
|
+
for (const lot of lots) {
|
|
13
|
+
const commits = linked.byLot.get(lot.id) ?? [];
|
|
14
|
+
if (lot.status === 'todo' && commits.length > 0) {
|
|
15
|
+
issues.push({ kind: 'todo-with-commits', message: `${lot.id} a ${commits.length} commit(s) mais est encore todo — raf start ${lot.id}` });
|
|
16
|
+
}
|
|
17
|
+
if (lot.status === 'doing') {
|
|
18
|
+
const last = commits[0]?.day ?? lot.started;
|
|
19
|
+
if (last && diffDays(last, today) > idleDays) {
|
|
20
|
+
issues.push({ kind: 'idle', message: `${lot.id} en cours sans commit depuis ${diffDays(last, today)} j (${lot.title})` });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (lot.status === 'done') {
|
|
24
|
+
const open = lot.tasks.filter((t) => isOpen(t.status));
|
|
25
|
+
if (open.length > 0) {
|
|
26
|
+
issues.push({ kind: 'done-open-tasks', message: `${lot.id} terminé avec sous-tâche(s) ouverte(s) : ${open.map((t) => t.id).join(', ')}` });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
for (const p of lot.problems)
|
|
30
|
+
issues.push({ kind: 'bad-field', message: `${lot.id} : ${p}` });
|
|
31
|
+
for (const d of lot.after) {
|
|
32
|
+
if (!ids.has(d))
|
|
33
|
+
issues.push({ kind: 'bad-dependency', message: `${lot.id} dépend de ${d}, absent du plan` });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const cycle of findCycles(lots))
|
|
37
|
+
issues.push({ kind: 'cycle', message: `dépendances circulaires : ${cycle.join(' → ')}` });
|
|
38
|
+
return issues;
|
|
39
|
+
}
|
|
40
|
+
function findCycles(lots) {
|
|
41
|
+
const deps = new Map(lots.map((l) => [l.id, l.after]));
|
|
42
|
+
const state = new Map();
|
|
43
|
+
const cycles = [];
|
|
44
|
+
const visit = (id, path) => {
|
|
45
|
+
if (state.get(id) === 'done')
|
|
46
|
+
return;
|
|
47
|
+
if (state.get(id) === 'visiting') {
|
|
48
|
+
cycles.push([...path.slice(path.indexOf(id)), id]);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
state.set(id, 'visiting');
|
|
52
|
+
for (const d of deps.get(id) ?? [])
|
|
53
|
+
if (deps.has(d))
|
|
54
|
+
visit(d, [...path, id]);
|
|
55
|
+
state.set(id, 'done');
|
|
56
|
+
};
|
|
57
|
+
for (const l of lots)
|
|
58
|
+
visit(l.id, []);
|
|
59
|
+
return cycles;
|
|
60
|
+
}
|