@sylad/cadence 0.17.0 → 0.18.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 +1 -1
- package/README.md +18 -6
- package/dist/cli.js +6 -2
- package/dist/session.js +27 -4
- package/dist/state.js +15 -0
- package/package.json +1 -1
- package/skills/session-close/SKILL.md +2 -2
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
{
|
|
9
9
|
"name": "cadence",
|
|
10
10
|
"description": "Session start and close rituals driven by a versioned plan (raf), and deliveries proven by their effect. Needs the cadence CLI (npm i -g @sylad/cadence).",
|
|
11
|
-
"version": "0.
|
|
11
|
+
"version": "0.18.0",
|
|
12
12
|
"source": "./",
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "Sylvain Ladoire"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cadence",
|
|
3
3
|
"description": "A repo-native working method: session start and close rituals driven by a versioned plan (raf), deliveries proven by their effect, and three reviewer agents (UX, code, QA).",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.18.0",
|
|
5
5
|
"author": { "name": "Sylvain Ladoire" },
|
|
6
6
|
"homepage": "https://github.com/Sylad/cadence",
|
|
7
7
|
"repository": "https://github.com/Sylad/cadence",
|
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ Code prompt that shows the session's context, quota, cost and the orchestrate wa
|
|
|
24
24
|
|
|
25
25
|
## What's new
|
|
26
26
|
|
|
27
|
-
**0.17.0**: `cadence-hud` shows an `N cmd` segment next to the agents, the background commands of the session (Bash run in the background and Monitor, subagents' included), tracked from the tool results and ended by the task's notification or `TaskStop`. **0.16.0**: a lot can declare **neighbouring repositories** (`repos:` key of the lot, `{ path, cite }` for a repository shared between projects): `cadence orchestrate` refuses, locks and guards them, gives them to every session as `--add-dir`, and `raf commits`, `raf show` and the review verdict read the commits that cite the lot there too. **0.15.0**: `hook.autostart` in `cadence.yaml` (`warn` by default, `refuse` or `start`): a pre-commit hook that refuses, or starts in the same commit, a lot still todo that the commit cites; `cadence session start|close --all [--depth n]`, one section per project under a folder. **0.14.0**: `cadence lead tour`, the lead's morning table of every project in a folder, printed by a program (one line per project: lots in progress, gaps, last notes, next ready lot, repository) in place of one subagent per project; the `lead` skill calls it. **0.13.1**: `cadence-hud` takes back the segments that fit once a big one has dropped (reset times, then the per-model consumption, now short: `opus 1.2M sonnet 800k`); `CADENCE_HUD_AMBIGUOUS=1` for a terminal that draws `▰ ▱ ⚙ │ ↻` in one cell. **0.13.0**: `cadence orchestrate --status --watch` (a wave's table redrawn in the terminal, which
|
|
27
|
+
**0.18.0**: `cadence session next` prints what it records (issue #6); `cadence session close` covers the period since the last `session start` (or the last close) and says so in its title, `--since` still wins (issue #7). **0.17.0**: `cadence-hud` shows an `N cmd` segment next to the agents, the background commands of the session (Bash run in the background and Monitor, subagents' included), tracked from the tool results and ended by the task's notification or `TaskStop`. **0.16.0**: a lot can declare **neighbouring repositories** (`repos:` key of the lot, `{ path, cite }` for a repository shared between projects): `cadence orchestrate` refuses, locks and guards them, gives them to every session as `--add-dir`, and `raf commits`, `raf show` and the review verdict read the commits that cite the lot there too. **0.15.0**: `hook.autostart` in `cadence.yaml` (`warn` by default, `refuse` or `start`): a pre-commit hook that refuses, or starts in the same commit, a lot still todo that the commit cites; `cadence session start|close --all [--depth n]`, one section per project under a folder. **0.14.0**: `cadence lead tour`, the lead's morning table of every project in a folder, printed by a program (one line per project: lots in progress, gaps, last notes, next ready lot, repository) in place of one subagent per project; the `lead` skill calls it. **0.13.1**: `cadence-hud` takes back the segments that fit once a big one has dropped (reset times, then the per-model consumption, now short: `opus 1.2M sonnet 800k`); `CADENCE_HUD_AMBIGUOUS=1` for a terminal that draws `▰ ▱ ⚙ │ ↻` in one cell. **0.13.0**: `cadence orchestrate --status --watch` (a wave's table redrawn in the terminal, which
|
|
28
28
|
stops with the wave) and the lead skill that names the wave and follows its journal; lot ids with `/`
|
|
29
29
|
(sub-tasks of a read-only plan) accepted by `orchestrate`; `cadence-hud`'s first line measured in
|
|
30
30
|
terminal cells and kept to ASCII; `publish.yml` on the v5 actions. **0.12.0**: the `cadence-hud` plugin (a status band above the Claude Code prompt),
|
|
@@ -414,9 +414,10 @@ URL. The agent then returns a draft for you to correct — it never writes the f
|
|
|
414
414
|
cadence session start # notes from the last close, lots in progress (silent ones flagged),
|
|
415
415
|
# work done since yesterday by lot, drift, repo state, 3 proposals
|
|
416
416
|
cadence session start --since "3 days ago" --idle 2
|
|
417
|
-
cadence session close #
|
|
418
|
-
#
|
|
419
|
-
# exit 1 while something is still open
|
|
417
|
+
cadence session close # commits by lot since the last `session start` (or last successful close),
|
|
418
|
+
# commits without a lot, lots in progress with no commit in that window,
|
|
419
|
+
# drift, uncommitted / unpushed work; exit 1 while something is still open
|
|
420
|
+
cadence session close --since "2 days ago" # --since overrides the window
|
|
420
421
|
cadence session start --all # from a folder that is not a repository: one "## project" section per
|
|
421
422
|
cadence session close --all --depth 2 # sub-folder holding a plan (--depth n: n levels down, 1 by default);
|
|
422
423
|
# exit = the worst code of the projects (2 error, 1 open, 0 closed)
|
|
@@ -424,6 +425,17 @@ cadence session next "finish L3" "review L4" # shown by the next session star
|
|
|
424
425
|
cadence session next --clear # erase those notes, on purpose
|
|
425
426
|
```
|
|
426
427
|
|
|
428
|
+
`session start` records its time in the worktree's state (`session.json`, under the git dir), and a
|
|
429
|
+
`session close` that ends with `✓ prêt à fermer` records its own. The next close takes the later one
|
|
430
|
+
as its window and names it in the section title: `Commits de la période (depuis l'ouverture de 09:12)`
|
|
431
|
+
(`depuis la clôture de …`; with the date when it is not today). With no recorded time, or an unreadable
|
|
432
|
+
file, the window is the current day, said as such (`depuis 2026-09-28 00:00`); `--since` is quoted as given.
|
|
433
|
+
A refused close (exit 1) leaves the boundary alone.
|
|
434
|
+
|
|
435
|
+
On success `cadence session next` prints what it recorded (`2 lignes enregistrées pour la prochaine
|
|
436
|
+
ouverture`, then the lines as written, blank ones dropped), so `session-close` can report
|
|
437
|
+
them without running `session start` again.
|
|
438
|
+
|
|
427
439
|
`--all` runs the same command inside each project found under the current folder —
|
|
428
440
|
`docs/plan/raf.yaml`, or a `cadence.yaml` with `plan:` — and never enters a project
|
|
429
441
|
to look for another. `--since` and `--idle` go to every project; an error in one
|
|
@@ -492,8 +504,8 @@ An entry is proposed only if it could be measured entirely. Never proposed:
|
|
|
492
504
|
The command deletes nothing, never fails on the cleanup and does not change the exit code: the
|
|
493
505
|
`session-close` skill shows the list and removes the entries only after the human agrees.
|
|
494
506
|
|
|
495
|
-
The commands get `CADENCE_SINCE`
|
|
496
|
-
add facts and decide nothing: a failing command is reported and changes neither
|
|
507
|
+
The commands get `CADENCE_SINCE` and `CADENCE_TODAY`. `CADENCE_SINCE` is the `--since` as given; on `session close` without `--since` it is the instant of the last open or successful close as an ISO 8601 UTC string (`2026-09-28T07:12:00.000Z`), or `<today> 00:00` when no mark exists.
|
|
508
|
+
The commands add facts and decide nothing: a failing command is reported and changes neither
|
|
497
509
|
the exit code nor the verdict.
|
|
498
510
|
|
|
499
511
|
## lead tour
|
package/dist/cli.js
CHANGED
|
@@ -407,7 +407,7 @@ function dispatch(argv, io) {
|
|
|
407
407
|
const sconf = rest[0] === 'start' || rest[0] === 'close' ? readSessionConfig(configPath) : {};
|
|
408
408
|
const facts = rest[0] === 'start' || rest[0] === 'close' ? sconf[rest[0]] : undefined;
|
|
409
409
|
const clean = rest[0] === 'close' && sconf.clean ? { patterns: sconf.clean, days: sconf.cleanDays } : undefined;
|
|
410
|
-
const sctx = { plan: loadPlan(), root, newsDir, state: stateDir(root), shared: sharedStateDir(root), today, out: io.out, facts, clean };
|
|
410
|
+
const sctx = { plan: loadPlan(), root, newsDir, state: stateDir(root), shared: sharedStateDir(root), today, now: io.now(), out: io.out, facts, clean };
|
|
411
411
|
if (rest[0] !== 'start')
|
|
412
412
|
return session(rest, sctx, values);
|
|
413
413
|
// La reprise rejoue les vérifications d'effet (un essai, borné) : un effet rouge d'hier se lit avec les faits du matin.
|
|
@@ -773,7 +773,7 @@ function session([sub, ...args], ctx, values) {
|
|
|
773
773
|
return sessionStart(ctx, { since: values.since ?? '24 hours ago', idle });
|
|
774
774
|
}
|
|
775
775
|
case 'close':
|
|
776
|
-
return sessionClose(ctx, { since: values.since
|
|
776
|
+
return sessionClose(ctx, { since: values.since });
|
|
777
777
|
case 'next': {
|
|
778
778
|
const lines = args.filter((l) => l.trim() !== '');
|
|
779
779
|
if (values.clear) {
|
|
@@ -791,6 +791,10 @@ function session([sub, ...args], ctx, values) {
|
|
|
791
791
|
'usage : cadence session next "ligne" … (pour effacer les notes exprès : cadence session next --clear)');
|
|
792
792
|
}
|
|
793
793
|
writeNext(ctx.state, ctx.today, lines);
|
|
794
|
+
// Rien sur stdout laissait croire à un échec : on redit les lignes telles qu'enregistrées, pour que
|
|
795
|
+
// session-close les rapporte sans relancer session start.
|
|
796
|
+
const n = lines.length;
|
|
797
|
+
ctx.out(`${n} ligne${n > 1 ? 's' : ''} enregistrée${n > 1 ? 's' : ''} pour la prochaine ouverture\n${lines.map((l) => ` - ${l}`).join('\n')}`);
|
|
794
798
|
return 0;
|
|
795
799
|
}
|
|
796
800
|
default:
|
package/dist/session.js
CHANGED
|
@@ -5,7 +5,7 @@ import { audit, exemptPlanOnly, nextUp, planCommits } from './audit.js';
|
|
|
5
5
|
import { diffDays, maxDay } from './dates.js';
|
|
6
6
|
import { repoStatus } from './git.js';
|
|
7
7
|
import { linkCommits } from './link.js';
|
|
8
|
-
import { lockAlive, readLock, readNext } from './state.js';
|
|
8
|
+
import { lockAlive, readLock, readNext, readSessionMark, writeSessionMark } from './state.js';
|
|
9
9
|
const FACTS_TIMEOUT = 120_000;
|
|
10
10
|
/**
|
|
11
11
|
* Sortie de la commande du projet, ligne à ligne. Un échec se dit et ne bloque rien : ce sont des
|
|
@@ -81,6 +81,23 @@ export function lastActivity(lot, commits) {
|
|
|
81
81
|
const days = [commits[0]?.day, lot.started, ...lot.notes.map((n) => n.date)].filter((d) => !!d);
|
|
82
82
|
return days.length ? days.reduce(maxDay) : undefined;
|
|
83
83
|
}
|
|
84
|
+
const hhmm = (d) => `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
|
85
|
+
/**
|
|
86
|
+
* Fenêtre de la clôture : --since s'il est donné ; sinon depuis la dernière ouverture (ou clôture réussie) du
|
|
87
|
+
* projet, dite avec son heure ; sinon le jour même.
|
|
88
|
+
*/
|
|
89
|
+
function closeWindow(ctx, since) {
|
|
90
|
+
if (since !== undefined)
|
|
91
|
+
return { since, label: `depuis ${since}` };
|
|
92
|
+
const mark = readSessionMark(ctx.state);
|
|
93
|
+
if (!mark)
|
|
94
|
+
return { since: `${ctx.today} 00:00`, label: `depuis ${ctx.today} 00:00` };
|
|
95
|
+
const at = new Date(mark.at);
|
|
96
|
+
const what = mark.kind === 'start' ? "l'ouverture" : 'la clôture';
|
|
97
|
+
const sameDay = at.toDateString() === (ctx.now ?? new Date()).toDateString();
|
|
98
|
+
const when = sameDay ? `de ${hhmm(at)}` : `du ${at.getFullYear()}-${String(at.getMonth() + 1).padStart(2, '0')}-${String(at.getDate()).padStart(2, '0')} à ${hhmm(at)}`;
|
|
99
|
+
return { since: mark.at, label: `depuis ${what} ${when}` };
|
|
100
|
+
}
|
|
84
101
|
export function sessionStart(ctx, opts) {
|
|
85
102
|
const { plan, out, today } = ctx;
|
|
86
103
|
const lots = plan.lots();
|
|
@@ -117,17 +134,20 @@ export function sessionStart(ctx, opts) {
|
|
|
117
134
|
...ready.map((l) => ({ l, why: l.quickwin ? 'gain rapide prêt' : 'prêt' })),
|
|
118
135
|
].slice(0, 3);
|
|
119
136
|
section(out, 'Propositions', proposals.map(({ l, why }, i) => `${i + 1}. ${l.quickwin ? '⚡ ' : ''}${l.id} ${l.title} — ${why}`));
|
|
137
|
+
// La clôture du soir part de cette heure : elle ne relit pas ce que l'ouverture vient de rapporter.
|
|
138
|
+
writeSessionMark(ctx.state, { kind: 'start', at: (ctx.now ?? new Date()).toISOString() });
|
|
120
139
|
return 0;
|
|
121
140
|
}
|
|
122
141
|
export function sessionClose(ctx, opts) {
|
|
123
142
|
const { plan, out, today } = ctx;
|
|
124
143
|
out(`${plan.project} — clôture du ${today}`);
|
|
125
|
-
const
|
|
144
|
+
const window = closeWindow(ctx, opts.since);
|
|
145
|
+
const recent = period(ctx, window.since);
|
|
126
146
|
const commits = byLotLines(recent);
|
|
127
147
|
if (recent.orphans.length) {
|
|
128
148
|
commits.push(`${recent.orphans.length} commit(s) sans lot :`, ...recent.orphans.map((c) => ` ${short(c)}`));
|
|
129
149
|
}
|
|
130
|
-
section(out,
|
|
150
|
+
section(out, `Commits de la période (${window.label})`, commits.length ? commits : ['(aucun)']);
|
|
131
151
|
const quiet = plan.lots().filter((l) => l.status === 'doing' && !isRecurring(l) && !recent.byLot.has(l.id));
|
|
132
152
|
section(out, 'Lots en cours', quiet.map((l) => `${l.id} ${l.title} — aucun commit sur la période : ${plan.readonly ? "le fermer ou l'annoter avec l'outil du projet" : 'raf done ou raf note'}`));
|
|
133
153
|
const issues = audit(plan, ctx.root, ctx.newsDir, today);
|
|
@@ -135,7 +155,7 @@ export function sessionClose(ctx, opts) {
|
|
|
135
155
|
const repo = repoLine(ctx.root);
|
|
136
156
|
const lock = lockStatus(ctx.shared);
|
|
137
157
|
section(out, 'Dépôt', [repo.line, ...(lock ? [lock.line] : [])]);
|
|
138
|
-
section(out, 'Faits propres au projet', projectFacts(ctx,
|
|
158
|
+
section(out, 'Faits propres au projet', projectFacts(ctx, window.since));
|
|
139
159
|
// Une proposition, pas une condition : rien n'est supprimé ici, le skill demande l'accord. Le
|
|
140
160
|
// nettoyage ne change jamais le verdict et ne fait jamais tomber la clôture.
|
|
141
161
|
if (ctx.clean?.patterns.length) {
|
|
@@ -154,5 +174,8 @@ export function sessionClose(ctx, opts) {
|
|
|
154
174
|
}
|
|
155
175
|
const open = issues.filter((i) => !i.warning).length + repo.open + (lock?.live ? 1 : 0);
|
|
156
176
|
out(open === 0 ? '\n✓ prêt à fermer' : `\n✗ pas fermé : ${open} point(s)`);
|
|
177
|
+
// Seule une clôture réussie devient la borne : relancer la clôture après avoir réglé un point ne vide pas le rapport.
|
|
178
|
+
if (open === 0)
|
|
179
|
+
writeSessionMark(ctx.state, { kind: 'close', at: (ctx.now ?? new Date()).toISOString() });
|
|
157
180
|
return open === 0 ? 0 : 1;
|
|
158
181
|
}
|
package/dist/state.js
CHANGED
|
@@ -33,6 +33,21 @@ export function clearNext(dir) {
|
|
|
33
33
|
rmSync(join(dir, 'next.md'), { force: true });
|
|
34
34
|
return previous;
|
|
35
35
|
}
|
|
36
|
+
/** Dernière ouverture ou clôture réussie du projet ; null si inconnue ou illisible (la clôture retombe alors sur le jour même). */
|
|
37
|
+
export function readSessionMark(dir) {
|
|
38
|
+
try {
|
|
39
|
+
const raw = JSON.parse(readFileSync(join(dir, 'session.json'), 'utf8'));
|
|
40
|
+
if ((raw.kind === 'start' || raw.kind === 'close') && typeof raw.at === 'string' && !Number.isNaN(Date.parse(raw.at)))
|
|
41
|
+
return { kind: raw.kind, at: raw.at };
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// absent ou illisible : pas de borne connue
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
export function writeSessionMark(dir, mark) {
|
|
49
|
+
writeFileSync(join(dir, 'session.json'), `${JSON.stringify(mark)}\n`);
|
|
50
|
+
}
|
|
36
51
|
export function lockPath(dir) {
|
|
37
52
|
return join(dir, 'deliver.lock');
|
|
38
53
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylad/cadence",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "A small, repo-native working method: a versioned plan linked to your commits, a changelog with screenshots, session rituals and deliveries proven by their effect.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Sylvain Ladoire",
|
|
@@ -18,7 +18,7 @@ With no project named, run `cadence session close --all` from the parent folder,
|
|
|
18
18
|
|
|
19
19
|
## Steps
|
|
20
20
|
|
|
21
|
-
1. Run `cadence session close` (over several days: `--since "2 days ago"`). Exit code 1 means
|
|
21
|
+
1. Run `cadence session close` (the window runs from the last `session start` or successful close and is named in the "Commits de la période" title; over several days: `--since "2 days ago"`). Exit code 1 means
|
|
22
22
|
*not closed*: something below is still open.
|
|
23
23
|
2. **Plan hygiene**, lot by lot:
|
|
24
24
|
- a `doing` lot with no commit in the period → `raf done <id>` if its tests are green,
|
|
@@ -67,7 +67,7 @@ With no project named, run `cadence session close --all` from the parent folder,
|
|
|
67
67
|
these steps say `raf`.
|
|
68
68
|
6. **Clean state**: everything committed and pushed, no delivery running. If the command still exits 1,
|
|
69
69
|
say what remains and do NOT say the session is closed.
|
|
70
|
-
7. **Three lines for next time**: `cadence session next "…" "…" "…"` — the next `session-start` shows them.
|
|
70
|
+
7. **Three lines for next time**: `cadence session next "…" "…" "…"` — the next `session-start` shows them. The command prints the lines as recorded: report them from that output, do not run `session start` again.
|
|
71
71
|
The lines replace the previous notes. Without a line the command refuses and keeps them; erase
|
|
72
72
|
them on purpose with `cadence session next --clear`, only when nothing is left to say.
|
|
73
73
|
|