@sylad/cadence 1.15.0 → 1.16.1
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 +14 -1
- package/bin/cadence.js +1 -3
- package/dist/audit.js +40 -3
- package/dist/cli.js +13 -3
- package/dist/git.js +28 -0
- package/dist/lead.js +6 -5
- package/dist/orchestrate/changelog-merge.js +38 -6
- package/dist/orchestrate/command.js +40 -1
- package/dist/release.js +1 -0
- package/dist/session.js +7 -4
- package/docs/lead.md +2 -0
- package/docs/orchestrate.md +6 -1
- package/docs/overview.md +2 -0
- package/docs/quickstart-dark.svg +33 -0
- package/docs/quickstart-light.svg +33 -0
- package/docs/quickstart.md +138 -0
- package/docs/raf.md +9 -1
- package/docs/releasing.md +16 -6
- package/docs/session.md +2 -2
- package/docs/whats-new.md +9 -0
- package/docs/with-your-tools-dark.svg +41 -0
- package/docs/with-your-tools-light.svg +41 -0
- package/docs/with-your-tools.md +95 -0
- package/package.json +7 -1
|
@@ -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": "1.
|
|
11
|
+
"version": "1.16.1",
|
|
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": "1.
|
|
4
|
+
"version": "1.16.1",
|
|
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
|
@@ -7,7 +7,7 @@ cadence keeps both in plain files next to the code, and [Claude Code](https://cl
|
|
|
7
7
|
|
|
8
8
|
## Start
|
|
9
9
|
|
|
10
|
-
Needs Node 22.18 or later (see [docs/stabilite.md](docs/stabilite.md)).
|
|
10
|
+
Needs Node 22.18 or later (see [docs/stabilite.md](docs/stabilite.md)); waves also need Claude Code installed and logged in.
|
|
11
11
|
|
|
12
12
|
```sh
|
|
13
13
|
npm install -g @sylad/cadence
|
|
@@ -16,12 +16,25 @@ raf add "My first lot" --estimate 1
|
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
Then `raf start L1`, commit with `feat(L1): …`, `raf now` to see where you are, `raf done L1` when it is finished.
|
|
19
|
+
`raf -v` prints the installed version. Every command is in [docs/raf.md](docs/raf.md).
|
|
20
|
+
|
|
21
|
+
## Lead several projects in 10 minutes
|
|
22
|
+
|
|
23
|
+
A parent folder, a plan in each project, a `/cadence:lead` session that picks the lots, a wave that does the work.
|
|
24
|
+
[docs/quickstart.md](docs/quickstart.md) goes from install to the first wave, and lists the usual stops.
|
|
25
|
+
Something broken or missing? [Open an issue](https://github.com/Sylad/cadence/issues/new/choose).
|
|
19
26
|
|
|
20
27
|
<picture>
|
|
21
28
|
<source media="(prefers-color-scheme: dark)" srcset="docs/cycle-vague-dark.svg">
|
|
22
29
|
<img alt="A lot goes through spec, mockup, accept, implement, review, ux and deliver; the lead picks the lots, answers the questions, re-verifies and delivers" src="docs/cycle-vague-light.svg">
|
|
23
30
|
</picture>
|
|
24
31
|
|
|
32
|
+
## With the tools you already have
|
|
33
|
+
|
|
34
|
+
Linear, a `HANDOFF.md`, ADRs, Conventional Commits? Keep them: cadence replaces none, and each part is optional.
|
|
35
|
+
It can read a plan your tracker keeps (read-only) and write its plan commits in your format.
|
|
36
|
+
[docs/with-your-tools.md](docs/with-your-tools.md): what stays, what is optional, what plugs in.
|
|
37
|
+
|
|
25
38
|
## Install as a Claude Code plugin
|
|
26
39
|
|
|
27
40
|
```
|
package/bin/cadence.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
3
2
|
import { run } from '../dist/cli.js';
|
|
4
3
|
|
|
5
4
|
const [tool, ...args] = process.argv.slice(2);
|
|
@@ -16,8 +15,7 @@ if (tool === 'raf') {
|
|
|
16
15
|
} else if (['news', 'session', 'deliver', 'verify', 'skills', 'orchestrate', 'lead', 'corrections', 'perf', 'spec', 'ref', 'config', 'release'].includes(tool)) {
|
|
17
16
|
process.exitCode = await run([tool, ...args], io);
|
|
18
17
|
} else if (tool === '--version' || tool === '-v') {
|
|
19
|
-
|
|
20
|
-
console.log(pkg.version);
|
|
18
|
+
process.exitCode = await run([tool], io);
|
|
21
19
|
} else {
|
|
22
20
|
console.log(`cadence — une méthode de travail qui vit dans le dépôt
|
|
23
21
|
|
package/dist/audit.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { dirname, join, relative } from 'node:path';
|
|
3
3
|
import { check } from './check.js';
|
|
4
4
|
import { parse } from 'yaml';
|
|
5
|
-
import { changedFiles, fileAt, readCommits } from './git.js';
|
|
5
|
+
import { changedFiles, fileAt, preloadChangedFiles, readCommits } from './git.js';
|
|
6
6
|
import { citedRefs, linkCommits, scopeOf } from './link.js';
|
|
7
7
|
import { PLAN_DEFAULT, PLAN_LEGACY, readDocsConfig, readNewsConfig, readOrchestrateConfig } from './config.js';
|
|
8
8
|
import { ficheHypotheses } from './spec.js';
|
|
@@ -25,6 +25,7 @@ export function ownFiles(plan, root) {
|
|
|
25
25
|
if (relative(root, plan.path) === PLAN_DEFAULT) {
|
|
26
26
|
own.add(PLAN_LEGACY);
|
|
27
27
|
own.add(join(dirname(PLAN_LEGACY), 'gantt.html'));
|
|
28
|
+
own.add(join(dirname(PLAN_DEFAULT), 'notes/.gitkeep')); // ce que `raf init` pose avec le plan (L377)
|
|
28
29
|
}
|
|
29
30
|
return own;
|
|
30
31
|
}
|
|
@@ -45,6 +46,29 @@ function onlyPlanKeyChanged(root, sha, file) {
|
|
|
45
46
|
const after = withoutPlanKey(fileAt(root, sha, file));
|
|
46
47
|
return before !== null && before === after;
|
|
47
48
|
}
|
|
49
|
+
/** Les lignes que `raf init` ajoute à .gitignore (L377) ; le fichier est un fichier du plan quand un commit n'y ajoute que celles-là. */
|
|
50
|
+
const INIT_GITIGNORE = ['.playwright-mcp/'];
|
|
51
|
+
function gitignoreLines(text) {
|
|
52
|
+
return (text ?? '').split('\n').map((l) => l.trim()).filter(Boolean);
|
|
53
|
+
}
|
|
54
|
+
/** Le commit ne change de .gitignore que les lignes de `raf init` ? Rien n'est retiré, aucune autre ligne n'est ajoutée. */
|
|
55
|
+
function onlyInitGitignore(root, sha, file) {
|
|
56
|
+
const before = gitignoreLines(fileAt(root, `${sha}^`, file));
|
|
57
|
+
const added = gitignoreLines(fileAt(root, sha, file));
|
|
58
|
+
const kept = added.filter((l) => !INIT_GITIGNORE.includes(l) || before.includes(l));
|
|
59
|
+
return before.every((l) => added.includes(l)) && kept.length === before.length;
|
|
60
|
+
}
|
|
61
|
+
/** Les clés d'un cadence.yaml qui naît dans le commit d'init : le plan et la porte de tests de la vague ; deliver:, session:… sont du travail. */
|
|
62
|
+
const INIT_CONFIG_KEYS = ['plan', 'orchestrate'];
|
|
63
|
+
function onlyInitKeys(text) {
|
|
64
|
+
try {
|
|
65
|
+
const doc = parse(text ?? '');
|
|
66
|
+
return doc !== null && typeof doc === 'object' && !Array.isArray(doc) && Object.keys(doc).every((k) => INIT_CONFIG_KEYS.includes(k));
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
48
72
|
/** Commits du dépôt sans les commits automatiques (motifs `ignore`) : ni audités ni comptés pour un lot. */
|
|
49
73
|
export function planCommits(plan, root, opts = {}) {
|
|
50
74
|
const { patterns } = plan.ignore;
|
|
@@ -54,14 +78,17 @@ export function planCommits(plan, root, opts = {}) {
|
|
|
54
78
|
/**
|
|
55
79
|
* Commit d'entretien du plan : TOUS ses fichiers sont des fichiers du plan (le plan, sa page Gantt,
|
|
56
80
|
* plan.files — son plan publié par exemple —, les attendus QA et la configuration lue, mais celle-ci seulement quand la
|
|
57
|
-
* clé `plan:` est la seule à changer : deliver/session/… sont du travail
|
|
81
|
+
* clé `plan:` est la seule à changer, ou qu'elle naît dans le commit avec les seules clés `plan:` et `orchestrate:` : deliver/session/… modifiés sont du travail ; et, de ce que `raf init` écrit, .cadence/notes/.gitkeep
|
|
82
|
+
* et les lignes qu'il ajoute à .gitignore, pour que le commit qui suit `raf init` n'ouvre pas d'écart, L377). Les fichiers décident,
|
|
58
83
|
* jamais le sujet : « chore(plan): … » qui touche un fichier source est un commit comme un autre.
|
|
59
84
|
*/
|
|
60
85
|
export function isPlanOnly(sha, plan, root) {
|
|
61
86
|
const own = ownFiles(plan, root);
|
|
62
87
|
const config = configRel(plan, root);
|
|
63
88
|
const files = changedFiles(root, sha);
|
|
64
|
-
|
|
89
|
+
const created = (f) => fileAt(root, `${sha}^`, f) === null && onlyInitKeys(fileAt(root, sha, f)); // cadence.yaml naît avec le plan : `raf init` puis la porte de tests, un seul commit (L377)
|
|
90
|
+
const initScaffold = (f) => f === '.gitignore' && onlyInitGitignore(root, sha, f);
|
|
91
|
+
return files.length > 0 && files.every((f) => initScaffold(f) || (own.has(f) && (f !== config || created(f) || onlyPlanKeyChanged(root, sha, f))));
|
|
65
92
|
}
|
|
66
93
|
/** Fichiers d'une version : manifestes JSON (dont seul le champ `version` bouge) et CHANGELOG. */
|
|
67
94
|
const VERSION_JSON = /(^|\/)(package\.json|package-lock\.json|\.claude-plugin\/(plugin|marketplace)\.json)$/;
|
|
@@ -213,6 +240,16 @@ function workCommits(plan, root, commits) {
|
|
|
213
240
|
export function lotCommits(plan, root, lotId) {
|
|
214
241
|
return linkCommits(plan.lots(), planCommits(plan, root), plan.refs).byLot.get(lotId) ?? [];
|
|
215
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Commits de travail des lots d'un rapprochement déjà fait (cf. lotWork) : le compte que montrent `raf now`, le tour et `session start`.
|
|
245
|
+
* `ids` : les seuls lots dont l'écran affiche un compte — chaque commit filtré coûte des `git diff-tree`, inutiles pour les autres.
|
|
246
|
+
*/
|
|
247
|
+
export function workByLot(plan, root, linked, ids) {
|
|
248
|
+
const wanted = ids ? new Set(ids) : null;
|
|
249
|
+
const shown = [...linked.byLot].filter(([id]) => !wanted || wanted.has(id));
|
|
250
|
+
preloadChangedFiles(root, shown.flatMap(([, commits]) => commits.map((c) => c.sha)));
|
|
251
|
+
return new Map(shown.map(([id, commits]) => [id, workCommits(plan, root, commits)]));
|
|
252
|
+
}
|
|
216
253
|
/** 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`. */
|
|
217
254
|
export function lotWork(plan, root, lotId) {
|
|
218
255
|
return workCommits(plan, root, lotCommits(plan, root, lotId));
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { basename, dirname, join, posix, relative, resolve } from 'node:path';
|
|
|
4
4
|
import { parseArgs } from 'node:util';
|
|
5
5
|
import { defaultPlan, PLAN_DEFAULT, PLAN_LEGACY, readDocsConfig, readHookConfig, readNewsConfig, readOrchestrateConfig, readPerfConfig, readPlanConfig, readSessionConfig } from './config.js';
|
|
6
6
|
import { repoSections, repoShas, resolveLotRepos } from './repos.js';
|
|
7
|
-
import { audit, exemptPlanOnly, gateProgress, gateText, gateWaiting, hypothesesOf, ownFiles, isPlanOnly, lotWork, nextUp, planCommits, unreviewedWork } from './audit.js';
|
|
7
|
+
import { audit, exemptPlanOnly, gateProgress, gateText, gateWaiting, hypothesesOf, ownFiles, isPlanOnly, lotWork, nextUp, planCommits, unreviewedWork, workByLot } from './audit.js';
|
|
8
8
|
import { short } from './check.js';
|
|
9
9
|
import { isDay, toDay } from './dates.js';
|
|
10
10
|
import { deliver, parseDeliverConfig, realDeps, shellQuote } from './deliver.js';
|
|
@@ -117,9 +117,18 @@ function legacyPlanLine(root, legacy) {
|
|
|
117
117
|
return `raf: ${PLAN_LEGACY} est ignoré : ${PLAN_DEFAULT} existe aussi et fait foi (supprimer ou fusionner l'ancien)`;
|
|
118
118
|
return `raf: ${PLAN_LEGACY} est l'ancien emplacement du plan ; le déplacer : cd ${shellQuote(root)} && mkdir -p ${dirname(PLAN_DEFAULT)} && ${gitRoot(root) && isTracked(root, PLAN_LEGACY) ? 'git mv' : 'mv'} ${PLAN_LEGACY} ${PLAN_DEFAULT}`;
|
|
119
119
|
}
|
|
120
|
+
/** La version du paquet (`package.json` est à côté de `src/` comme de `dist/`). */
|
|
121
|
+
function packageVersion() {
|
|
122
|
+
return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
123
|
+
}
|
|
120
124
|
export function run(argv, io) {
|
|
121
125
|
setPlanNotice(io.err);
|
|
122
126
|
try {
|
|
127
|
+
// Comme `cadence --version` (L376) : en premier argument seulement, ailleurs c'est une option comme une autre.
|
|
128
|
+
if (argv[0] === '--version' || argv[0] === '-v') {
|
|
129
|
+
io.out(packageVersion());
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
123
132
|
// orchestrate a ses propres options (--status et --resume prennent une valeur facultative, --answer en prend deux).
|
|
124
133
|
if (argv[0] === 'orchestrate') {
|
|
125
134
|
return orchestrate(argv.slice(1), io, realOrchestrateDeps(io.env)).catch((e) => failure(e, io));
|
|
@@ -663,7 +672,7 @@ function dispatch(argv, io) {
|
|
|
663
672
|
const lots = plan.lots();
|
|
664
673
|
const linked = linkCommits(lots, planCommits(plan, root), plan.refs);
|
|
665
674
|
const stamp = io.now();
|
|
666
|
-
const html = renderGantt(ganttData(plan.project, schedule(lots, linked
|
|
675
|
+
const html = renderGantt(ganttData(plan.project, schedule(lots, workByLot(plan, root, linked), today), today, `${toDay(stamp)} ${stamp.toTimeString().slice(0, 5)}`));
|
|
667
676
|
const out = resolve(io.cwd, values.output ?? join(dirname(planPath), 'gantt.html'));
|
|
668
677
|
mkdirSync(dirname(out), { recursive: true });
|
|
669
678
|
writeFileSync(out, html);
|
|
@@ -841,8 +850,9 @@ function describe(l, commits) {
|
|
|
841
850
|
function now(plan, root, newsDir, today, io) {
|
|
842
851
|
const lots = plan.lots();
|
|
843
852
|
const linked = linkCommits(lots, planCommits(plan, root), plan.refs);
|
|
844
|
-
const count = (id) => linked.byLot.get(id)?.length ?? 0;
|
|
845
853
|
const { doing, ready, blocked } = nextUp(lots);
|
|
854
|
+
const work = workByLot(plan, root, linked, [...doing, ...ready.slice(0, 5)].map((l) => l.id));
|
|
855
|
+
const count = (id) => work.get(id)?.length ?? 0;
|
|
846
856
|
io.out(`${plan.project} — ${today}`);
|
|
847
857
|
io.out('\nEn cours');
|
|
848
858
|
if (doing.length === 0)
|
package/dist/git.js
CHANGED
|
@@ -99,9 +99,37 @@ export function syncIndexWithHead(cwd, file) {
|
|
|
99
99
|
* le commit supprime (`--diff-filter=d`) : un fichier supprimé n'est plus du code à tester.
|
|
100
100
|
*/
|
|
101
101
|
export function changedFiles(cwd, sha, opts = {}) {
|
|
102
|
+
const known = opts.withoutDeleted ? undefined : filesCache.get(`${cwd}\0${sha}`);
|
|
103
|
+
if (known)
|
|
104
|
+
return known;
|
|
102
105
|
// -z : noms séparés par NUL, jamais échappés ; quotepath=off en plus pour les sorties qui citeraient quand même.
|
|
103
106
|
return git(cwd, ['-c', 'core.quotepath=off', 'diff-tree', '--root', '--no-commit-id', '--name-only', '-r', '-z', ...(opts.withoutDeleted ? ['--diff-filter=d'] : []), sha]).split('\0').filter(Boolean);
|
|
104
107
|
}
|
|
108
|
+
/** Fichiers modifiés par commit (hors suppressions écartées), par dépôt : un sha ne change jamais de contenu. */
|
|
109
|
+
const filesCache = new Map();
|
|
110
|
+
/**
|
|
111
|
+
* Lit d'un seul appel git les fichiers modifiés de ces commits, pour que `changedFiles` ne lance pas un `git diff-tree` par commit
|
|
112
|
+
* (L381 : filtrer les milliers de commits liés du gantt coûtait ~20 s). Même résultat que `changedFiles` ; un commit de fusion n'a
|
|
113
|
+
* aucun fichier, comme avec `diff-tree`. Sans effet sur un dépôt illisible : `changedFiles` retombe sur `git diff-tree`.
|
|
114
|
+
*/
|
|
115
|
+
export function preloadChangedFiles(cwd, shas) {
|
|
116
|
+
const missing = shas.filter((sha) => !filesCache.has(`${cwd}\0${sha}`));
|
|
117
|
+
if (missing.length < 2)
|
|
118
|
+
return;
|
|
119
|
+
let out;
|
|
120
|
+
try {
|
|
121
|
+
out = execFileSync('git', ['-c', 'core.quotepath=off', 'log', '--no-walk=unsorted', '--stdin', '--root', '--format=%x01%H', '--name-only', '-z'], {
|
|
122
|
+
cwd, encoding: 'utf8', input: missing.join('\n') + '\n', stdio: ['pipe', 'pipe', 'pipe'], maxBuffer: 1024 * 1024 * 1024,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
for (const block of out.split('\x01').slice(1)) {
|
|
129
|
+
const [sha, ...files] = block.split('\0');
|
|
130
|
+
filesCache.set(`${cwd}\0${sha}`, files.map((f) => f.replace(/^\n/, '')).filter(Boolean));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
105
133
|
/** Contenu d'un fichier à un commit (`<sha>^` pour l'état d'avant) ; null s'il n'existe pas à ce commit. */
|
|
106
134
|
export function fileAt(cwd, rev, file) {
|
|
107
135
|
try {
|
package/dist/lead.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
2
|
import { basename, join, relative, sep } from 'node:path';
|
|
3
|
-
import { audit, gateWaiting, nextUp, planCommits } from './audit.js';
|
|
3
|
+
import { audit, gateWaiting, nextUp, planCommits, workByLot } from './audit.js';
|
|
4
4
|
import { PLAN_DEFAULT, PLAN_LEGACY, planLoader, readPlanConfig } from './config.js';
|
|
5
5
|
import { diffDays } from './dates.js';
|
|
6
6
|
import { gitRoot, repoStatus } from './git.js';
|
|
@@ -114,13 +114,14 @@ export function tourRow(dir, today, idle = TOUR_IDLE, name = basename(dir)) {
|
|
|
114
114
|
const lots = plan.lots();
|
|
115
115
|
const all = linkCommits(lots, planCommits(plan, root), plan.refs);
|
|
116
116
|
const { doing, ready, blocked } = nextUp(lots);
|
|
117
|
+
const work = workByLot(plan, root, all, doing.map((l) => l.id));
|
|
117
118
|
row.doing = doing.map((l) => {
|
|
118
|
-
const
|
|
119
|
-
const last = lastActivity(l,
|
|
119
|
+
const count = work.get(l.id)?.length ?? 0;
|
|
120
|
+
const last = lastActivity(l, all.byLot.get(l.id) ?? []);
|
|
120
121
|
if (!last)
|
|
121
|
-
return { id: l.id, commits:
|
|
122
|
+
return { id: l.id, commits: count, noActivity: true };
|
|
122
123
|
const days = diffDays(last, today);
|
|
123
|
-
return days > idle ? { id: l.id, commits:
|
|
124
|
+
return days > idle ? { id: l.id, commits: count, silentDays: days } : { id: l.id, commits: count };
|
|
124
125
|
});
|
|
125
126
|
row.drift = audit(plan, root, join(root, 'docs/nouveautes'), today).map((i) => `${i.warning ? '⚠' : '✗'} ${i.message}`);
|
|
126
127
|
row.notes = readNext(stateDir(root))?.lines ?? [];
|
|
@@ -12,8 +12,13 @@ function split(text) {
|
|
|
12
12
|
end = lines.length;
|
|
13
13
|
return { head: lines.slice(0, at + 1).join('\n'), block: lines.slice(at + 1, end), tail: lines.slice(end).join('\n') };
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
|
|
15
|
+
/** Clé des entrées nues (avant tout `### `) quand `sections` les accepte ; aucun titre réel n'est vide. */
|
|
16
|
+
const BARE = '';
|
|
17
|
+
/**
|
|
18
|
+
* Sections du bloc `[Unreleased]` : une entrée est une ligne `- ` et ses lignes de suite. Deux titres identiques sont fondus. Du texte hors entrée : null.
|
|
19
|
+
* Une entrée avant tout titre est refusée (null), sauf avec `allowBare` : elle est alors rangée sous la clé `BARE`.
|
|
20
|
+
*/
|
|
21
|
+
function sections(block, allowBare = false) {
|
|
17
22
|
const out = new Map();
|
|
18
23
|
let current = null;
|
|
19
24
|
for (const line of block) {
|
|
@@ -24,6 +29,11 @@ function sections(block) {
|
|
|
24
29
|
current = out.get(title) ?? [];
|
|
25
30
|
out.set(title, current);
|
|
26
31
|
}
|
|
32
|
+
else if (!current && allowBare && line.startsWith('- ')) {
|
|
33
|
+
current = out.get(BARE) ?? [];
|
|
34
|
+
out.set(BARE, current);
|
|
35
|
+
current.push(line);
|
|
36
|
+
}
|
|
27
37
|
else if (!current) {
|
|
28
38
|
return null;
|
|
29
39
|
}
|
|
@@ -39,6 +49,27 @@ function sections(block) {
|
|
|
39
49
|
}
|
|
40
50
|
return out;
|
|
41
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Range les entrées nues des trois côtés sous une rubrique (L383) : celle que les côtés ont ajoutée à la base si elle est seule, sinon la seule rubrique qu'ils
|
|
54
|
+
* portent. Aucune rubrique, ou plusieurs candidates : le choix serait le nôtre, on rend false et le conflit reste à la main.
|
|
55
|
+
*/
|
|
56
|
+
function fileBare(base, ours, theirs) {
|
|
57
|
+
if (![base, ours, theirs].some((s) => s.has(BARE)))
|
|
58
|
+
return true;
|
|
59
|
+
const titles = [...new Set([...ours.keys(), ...theirs.keys()])].filter((t) => t !== BARE);
|
|
60
|
+
const added = titles.filter((t) => !base.has(t));
|
|
61
|
+
const target = added.length === 1 ? added[0] : titles.length === 1 ? titles[0] : null;
|
|
62
|
+
if (target === null)
|
|
63
|
+
return false;
|
|
64
|
+
for (const side of [base, ours, theirs]) {
|
|
65
|
+
const bare = side.get(BARE);
|
|
66
|
+
if (!bare)
|
|
67
|
+
continue;
|
|
68
|
+
side.delete(BARE);
|
|
69
|
+
side.set(target, [...(side.get(target) ?? []), ...bare]);
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
42
73
|
/** `base` est une sous-suite de `side` : rien n'a été retiré ni modifié, seul de l'ajout s'y est glissé. */
|
|
43
74
|
function onlyAdds(base, side) {
|
|
44
75
|
let i = 0;
|
|
@@ -63,7 +94,8 @@ function pick(base, ours, theirs) {
|
|
|
63
94
|
* Fusion à trois du CHANGELOG quand deux lots y ont chacun ajouté une entrée sous `[Unreleased]` (L314) : union par section, un seul titre par section,
|
|
64
95
|
* sections dans l'ordre de Keep a Changelog, les entrées de `ours` puis celles de `theirs` qui n'y sont pas déjà. Le reste du fichier suit le côté qui l'a
|
|
65
96
|
* changé. Rend null quand ce n'est pas de l'ajout pur — une entrée de base retirée ou modifiée, du texte libre sous `[Unreleased]`, le reste du fichier
|
|
66
|
-
* changé des deux côtés, pas de section `[Unreleased]` — : le conflit reste alors à la main.
|
|
97
|
+
* changé des deux côtés, pas de section `[Unreleased]` — : le conflit reste alors à la main. Une entrée nue (sans rubrique) d'un côté est rangée sous la
|
|
98
|
+
* rubrique que l'autre a ajoutée (L383) ; sans rubrique unique où la ranger, le conflit reste à la main.
|
|
67
99
|
*
|
|
68
100
|
* `start` est le CHANGELOG du point de départ de la branche rejouée (L323). Une entrée de `base` qui n'y était pas a été ajoutée par un commit précédent du
|
|
69
101
|
* lot lui-même : `theirs` peut la modifier ou la retirer, sa version gagne (l'ancienne est écartée de `ours`). Sans `start`, aucune entrée n'est au lot.
|
|
@@ -74,11 +106,11 @@ export function mergeUnreleased(base, ours, theirs, start) {
|
|
|
74
106
|
return null;
|
|
75
107
|
const head = pick(b.head, o.head, t.head);
|
|
76
108
|
const tail = pick(b.tail, o.tail, t.tail);
|
|
77
|
-
const [bs, os, ts] = [sections(b.block), sections(o.block), sections(t.block)];
|
|
78
|
-
if (head === null || tail === null || !bs || !os || !ts)
|
|
109
|
+
const [bs, os, ts] = [sections(b.block, true), sections(o.block, true), sections(t.block, true)];
|
|
110
|
+
if (head === null || tail === null || !bs || !os || !ts || !fileBare(bs, os, ts))
|
|
79
111
|
return null;
|
|
80
112
|
const st = start === undefined ? null : split(start);
|
|
81
|
-
const startEntries = new Set([...(st ? (sections(st.block)?.values() ?? []) : [])].flat());
|
|
113
|
+
const startEntries = new Set([...(st ? (sections(st.block, true)?.values() ?? []) : [])].flat());
|
|
82
114
|
const own = new Set(start === undefined || !st ? [] : [...bs.values()].flat().filter((e) => !startEntries.has(e)));
|
|
83
115
|
// Une entrée de base que ours range sous une version publiée (la branche a été publiée pendant la vie du lot) n'a pas été retirée : elle a changé de place.
|
|
84
116
|
// Seule une entrée de base compte, entière : une entrée nouvelle de theirs qui est un bout d'une entrée publiée n'est pas la même, elle reste au lot.
|
|
@@ -239,7 +239,7 @@ function resolveTargets(args, io, refusals) {
|
|
|
239
239
|
}
|
|
240
240
|
seen.add(key);
|
|
241
241
|
if (!existsSync(projectDir) || !statSync(projectDir).isDirectory()) {
|
|
242
|
-
refusals.push(`${key} : ${l.project ? `dossier ${projectDir} introuvable` : 'dossier courant illisible'}`);
|
|
242
|
+
refusals.push(`${key} : ${l.project ? `dossier ${projectDir} introuvable (le projet est un sous-dossier du dossier courant ; dans un dépôt, \`.:${l.lot}\`)` : 'dossier courant illisible'}`);
|
|
243
243
|
continue;
|
|
244
244
|
}
|
|
245
245
|
if (!isProject(projectDir)) {
|
|
@@ -266,6 +266,10 @@ function olderThanEffort(version) {
|
|
|
266
266
|
const [a, b, c] = [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
267
267
|
return a < 2 || (a === 2 && (b < 1 || (b === 1 && c < 284)));
|
|
268
268
|
}
|
|
269
|
+
/** L378 : claude installé mais non connecté échoue à la première session, après le démarrage du lot dans le plan. Null si connecté ou état illisible. */
|
|
270
|
+
function notLoggedIn(deps) {
|
|
271
|
+
return deps.claudeAuth?.()?.loggedIn === false ? "claude n'est pas connecté : lancer `claude` puis /login (état : `claude auth status`)" : null;
|
|
272
|
+
}
|
|
269
273
|
/** Préconditions : tout ce qui peut être refusé l'est ici, avant d'agir, sans verrou ni écriture. */
|
|
270
274
|
async function preflight(args, targets, io, deps, launch, opts = {}) {
|
|
271
275
|
const refusals = [];
|
|
@@ -276,6 +280,9 @@ async function preflight(args, targets, io, deps, launch, opts = {}) {
|
|
|
276
280
|
refusals.push('claude introuvable (CADENCE_CLAUDE_BIN, ou claude dans le PATH)');
|
|
277
281
|
else if (!info.jsonSchema)
|
|
278
282
|
refusals.push(`claude ${info.version} n'a pas --json-schema : mettre claude à jour`);
|
|
283
|
+
const login = info && !args.dryRun ? notLoggedIn(deps) : null; // la simulation n'ouvre pas de session : elle le signale (dryRun) sans refuser
|
|
284
|
+
if (login)
|
|
285
|
+
refusals.push(login);
|
|
279
286
|
const lots = [];
|
|
280
287
|
const repoChecked = new Set();
|
|
281
288
|
const byProject = new Map();
|
|
@@ -459,6 +466,9 @@ function dryRun(lots, io, deps, budget, id) {
|
|
|
459
466
|
const tmp = mkdtempSync(join(tmpdir(), 'cadence-orchestrate-'));
|
|
460
467
|
const agents = agentsOf(deps);
|
|
461
468
|
io.out(`vague ${id} — simulation, rien n'est lancé (budget ${budget} tokens comptés)`);
|
|
469
|
+
const login = notLoggedIn(deps);
|
|
470
|
+
if (login)
|
|
471
|
+
io.out(`⚠ ${login} — sans cela le vrai lancement est refusé`);
|
|
462
472
|
const repos = [...new Set(lots.map((l) => l.repo))];
|
|
463
473
|
const noTestSaid = new Set();
|
|
464
474
|
const redSaid = new Set();
|
|
@@ -674,8 +684,17 @@ function removeWorktree(args, io) {
|
|
|
674
684
|
io.out(`orchestrate : ${key} : worktree retiré (${gone.abandoned} commit(s) abandonné(s))`);
|
|
675
685
|
return 0;
|
|
676
686
|
}
|
|
687
|
+
/** L382 : `claude auth status` peut durer jusqu'à 20 s ; en --continue, preflight() le relancerait pour chaque lot candidat de chaque tour. Une seule lecture par lancement (une vague). */
|
|
688
|
+
function onceAuth(deps) {
|
|
689
|
+
const read = deps.claudeAuth;
|
|
690
|
+
if (!read)
|
|
691
|
+
return deps;
|
|
692
|
+
let cached;
|
|
693
|
+
return { ...deps, claudeAuth: () => (cached ??= { value: read() }).value };
|
|
694
|
+
}
|
|
677
695
|
/** Point d'entrée de `cadence orchestrate`. 0 prêts · 1 rendus au lead · 2 refus avant d'agir · 3 suspendue (budget, quota). */
|
|
678
696
|
export async function orchestrate(argv, io, deps) {
|
|
697
|
+
deps = onceAuth(deps);
|
|
679
698
|
const args = parseOrchestrateArgs(argv);
|
|
680
699
|
const launch = io.cwd;
|
|
681
700
|
const today = io.env.RAF_TODAY && isDay(io.env.RAF_TODAY) ? io.env.RAF_TODAY : toDay(io.now());
|
|
@@ -1612,6 +1631,16 @@ async function resume(args, argv, io, deps, launch, today) {
|
|
|
1612
1631
|
}
|
|
1613
1632
|
return continueRounds(await execute(wave, live, store, io, deps, today, maxSessions(args, io), lots), wave, lots, store, io, deps, today, args, launch);
|
|
1614
1633
|
}
|
|
1634
|
+
/** `loggedIn` de la sortie JSON de `claude auth status` ; null si elle n'est pas lisible (versions sans cette commande : pas de refus). */
|
|
1635
|
+
export function parseAuthStatus(text) {
|
|
1636
|
+
try {
|
|
1637
|
+
const v = JSON.parse(text);
|
|
1638
|
+
return typeof v.loggedIn === 'boolean' ? { loggedIn: v.loggedIn } : null;
|
|
1639
|
+
}
|
|
1640
|
+
catch {
|
|
1641
|
+
return null;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1615
1644
|
/** Dépendances réelles : `claude` (ou CADENCE_CLAUDE_BIN), agents et gabarits du paquet, journaux de ~/.claude. */
|
|
1616
1645
|
export function realOrchestrateDeps(env) {
|
|
1617
1646
|
const bin = env.CADENCE_CLAUDE_BIN || 'claude';
|
|
@@ -1630,6 +1659,16 @@ export function realOrchestrateDeps(env) {
|
|
|
1630
1659
|
return null;
|
|
1631
1660
|
}
|
|
1632
1661
|
},
|
|
1662
|
+
claudeAuth: () => {
|
|
1663
|
+
try {
|
|
1664
|
+
// `auth status` sort en 1 quand claude n'est pas connecté, et imprime quand même le JSON.
|
|
1665
|
+
const text = execFileSync(bin, ['auth', 'status'], { env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 20_000 });
|
|
1666
|
+
return parseAuthStatus(text);
|
|
1667
|
+
}
|
|
1668
|
+
catch (e) {
|
|
1669
|
+
return parseAuthStatus(String(e.stdout ?? ''));
|
|
1670
|
+
}
|
|
1671
|
+
},
|
|
1633
1672
|
agentsDir: AGENTS_DIR,
|
|
1634
1673
|
snapshot: child ? undefined : { packageRoot: PACKAGE_ROOT, reexec: spawnReexec },
|
|
1635
1674
|
claudeHome: env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'),
|
package/dist/release.js
CHANGED
|
@@ -199,6 +199,7 @@ function steps(p, files, branch) {
|
|
|
199
199
|
const tag = `v${p.version}`;
|
|
200
200
|
const out = [
|
|
201
201
|
`git add ${files.join(' ')} && git commit -m "release: ${p.version}"`,
|
|
202
|
+
'cadence verify --gate --sha HEAD (la suite et le build rejoués sur le commit de version, où le CHANGELOG n\'a plus de [Unreleased] plein ; rouge = pas d\'éval ni de tag)',
|
|
202
203
|
...(p.hasEvalScript ? ['npm run eval:plugin -- --trust-plugin (facturé ; un cas sous le seuil arrête la release)'] : []),
|
|
203
204
|
`git tag ${tag}`,
|
|
204
205
|
`git push origin ${branch} ${tag} (le tag lance le workflow de publication)`,
|
package/dist/session.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { CLEAN_DAYS, scanStale } from './clean.js';
|
|
3
3
|
import { dueLine, isRecurring, recurringByDue } from './recurring.js';
|
|
4
|
-
import { audit, exemptPlanOnly, gateWaiting, nextUp, planCommits } from './audit.js';
|
|
4
|
+
import { audit, exemptPlanOnly, gateWaiting, nextUp, planCommits, workByLot } 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';
|
|
@@ -32,10 +32,12 @@ function projectFacts(ctx, since) {
|
|
|
32
32
|
return lines;
|
|
33
33
|
}
|
|
34
34
|
const short = (c) => `${c.sha.slice(0, 7)} ${c.subject}`;
|
|
35
|
-
/** Commits de la fenêtre reliés aux lots
|
|
35
|
+
/** Commits de la fenêtre reliés aux lots : ceux qui ne touchent que le plan ne comptent ni « sans lot » ni comme travail d'un lot (comme `raf commits`). */
|
|
36
36
|
function period(ctx, since) {
|
|
37
37
|
const commits = planCommits(ctx.plan, ctx.root, { since });
|
|
38
|
-
const
|
|
38
|
+
const exempt = exemptPlanOnly(linkCommits(ctx.plan.lots(), commits, ctx.plan.refs), ctx.plan, ctx.root);
|
|
39
|
+
const byLot = new Map([...workByLot(ctx.plan, ctx.root, exempt)].filter(([, cs]) => cs.length > 0));
|
|
40
|
+
const linked = { ...exempt, byLot };
|
|
39
41
|
// Comme raf check : un commit antérieur à l'adoption du plan n'avait pas à citer de lot.
|
|
40
42
|
const adopted = ctx.plan.since;
|
|
41
43
|
return adopted ? { ...linked, orphans: linked.orphans.filter((c) => c.day >= adopted) } : linked;
|
|
@@ -113,6 +115,7 @@ export function sessionStart(ctx, opts) {
|
|
|
113
115
|
if (lock)
|
|
114
116
|
out(`\n${lock.line}`);
|
|
115
117
|
const { doing, ready, blocked } = nextUp(lots);
|
|
118
|
+
const work = workByLot(plan, ctx.root, all, doing.map((l) => l.id));
|
|
116
119
|
out('\nEn cours');
|
|
117
120
|
if (doing.length === 0)
|
|
118
121
|
out(' (rien)');
|
|
@@ -121,7 +124,7 @@ export function sessionStart(ctx, opts) {
|
|
|
121
124
|
const last = lastActivity(l, commits);
|
|
122
125
|
const idle = last ? diffDays(last, today) : 0;
|
|
123
126
|
const state = last ? `dernière activité ${last}${idle > opts.idle ? `, silencieux depuis ${idle} j` : ''}` : 'aucune activité';
|
|
124
|
-
out(` ${l.id} ${l.title} (${commitCount(
|
|
127
|
+
out(` ${l.id} ${l.title} (${commitCount(work.get(l.id)?.length ?? 0)}, ${state})`);
|
|
125
128
|
}
|
|
126
129
|
section(out, 'En attente', gateWaiting(lots, blocked).map(({ lot, text }) => `${lot.id} ${lot.title} (${text})`));
|
|
127
130
|
const recent = period(ctx, opts.since);
|
package/docs/lead.md
CHANGED
|
@@ -156,6 +156,8 @@ alpha · en cours L1 (1 commit, silencieux 4j) · dérive 1 (✗ L2 a 1 commit(s
|
|
|
156
156
|
beta · en cours rien · dérive aucune · notes : aucune · prochain T2 Second · dépôt propre
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
+
The commit count of a lot in progress is the one of `raf commits`: plan commits (`plan: L1 démarré` and the like, which touch only plan files) are left out, so a lot just started reads `L1 (0 commit)` (L379).
|
|
160
|
+
|
|
159
161
|
| Column | Content |
|
|
160
162
|
|---|---|
|
|
161
163
|
| `en cours` | ids of the lots in progress, each with its number of commits (`0 commit` included: a close note on a lot without commit is not « done, `raf done` forgotten »); then `aucune activité` when it has no activity at all, `silencieux Nj` when the lot's last activity (commit, note or start) is more than `--idle` days old (default 3) |
|
package/docs/orchestrate.md
CHANGED
|
@@ -11,6 +11,7 @@ exception is a **formatting retry** (below).
|
|
|
11
11
|
```sh
|
|
12
12
|
cadence orchestrate finance-tracker:L41 ol-companion:L22 cadence:L18@haiku
|
|
13
13
|
cadence orchestrate L18 # from inside a project
|
|
14
|
+
cadence orchestrate .:L18 # the same, with the project named « . » (inside a repository)
|
|
14
15
|
cadence orchestrate maritime-atlas:Q4/accueil-4-ux12@haiku # a lot id may contain "/" (sub-tasks of read-only plans)
|
|
15
16
|
cadence orchestrate maritime:B28.4 --goal "…" # the goal replaces the stale title as the objective of the briefs (below)
|
|
16
17
|
cadence orchestrate … --budget 1.5M # 1500000, 1.5M, 800k; default 2M
|
|
@@ -35,6 +36,10 @@ cadence orchestrate --drop ccc:L29 [--wave <wave>] # from another terminal: ta
|
|
|
35
36
|
cadence orchestrate --stop-after-current [--wave <wave>] # from another terminal: let the running sessions finish, start nothing else
|
|
36
37
|
```
|
|
37
38
|
|
|
39
|
+
**Claude Code must be installed and logged in** (L378). Before starting a lot, `cadence orchestrate` runs `claude auth status` (no model, no cost); when it says `loggedIn: false` the command is refused (exit 2) before anything is written: no `raf start`, no plan commit, the lot stays `todo`. The message says what to do: « claude n'est pas connecté : lancer `claude` puis /login (état : `claude auth status`) ». `--dry-run` starts no session, so it is not refused: it prints the same message after its first line, as a `⚠`. `claude auth status` is read once per launch, so with `--continue` the rounds of a wave do not run it again for every candidate lot. An unreadable answer (a `claude` without `auth status`) is not a refusal.
|
|
40
|
+
|
|
41
|
+
A `<project>` is a sub-folder of the current folder. Launched from inside `alpha`, `alpha:L1` looks for `alpha/alpha` and is refused: `alpha:L1 : dossier …/alpha/alpha introuvable (le projet est un sous-dossier du dossier courant ; dans un dépôt, `.:L1`)` (L376). Inside a repository, `.:L1` or `L1` names the lot of the repository itself.
|
|
42
|
+
|
|
38
43
|
**`--drop` and `--stop-after-current` — steering a running wave (L79)**: both are run from the folder the wave was launched
|
|
39
44
|
from (`--wave <id>` when several waves run from it), and write a request in the wave's state folder
|
|
40
45
|
(`.cadence/runs/<wave>/control.log`, append-only), which the wave reads before each session — no signal, no edit of
|
|
@@ -586,7 +591,7 @@ nothing below applies and the wave behaves as before; any other value is refused
|
|
|
586
591
|
stacked). After the rebase the program lists the entries the lot's commits wrote under `[Unreleased]` that are neither at the starting point nor at
|
|
587
592
|
the lot's tip (nor under a version the tip itself carries: an entry the lot published itself is final), drops them from the result (under `[Unreleased]` and under a version), and puts back an entry of the tip that is missing, in a commit of
|
|
588
593
|
its own (`docs: CHANGELOG — une entrée finale par lot …`); an entry the repository's branch also carries is left alone. The union of an earlier replayed commit files the lot's entry at the end of its section; where the lot first put it (top of the section included) does not matter for the next replayed commit. The journal says
|
|
589
|
-
`<project>:<lot> CHANGELOG.md : une entrée finale par lot, les versions intermédiaires du lot écartées`. Anything else — an entry that
|
|
594
|
+
`<project>:<lot> CHANGELOG.md : une entrée finale par lot, les versions intermédiaires du lot écartées`. **A bare entry** (L383): an entry written under `## [Unreleased]` without a section on one side, against a section (`### Fixed`) the other side added, is not a free text: it is filed under that section (the entries of the repository's branch first), so the result has one title and one entry per lot. The section is the only one the two sides added to the base, or the only one they carry; with none, or several candidates, the conflict is left by hand. Anything else — an entry that
|
|
590
595
|
was already there removed or reworded, free text under `[Unreleased]`, the rest of the
|
|
591
596
|
file changed on both sides, another file in conflict with it — aborts the rebase and hands the lot back as above. The rebased commits differ, so
|
|
592
597
|
steps 3 and 4 apply: tests replayed, and a short review since the patch-id changed;
|
package/docs/overview.md
CHANGED
|
@@ -61,6 +61,8 @@ The two plugins are independent. `cadence@cadence` carries the rituals, which ca
|
|
|
61
61
|
|
|
62
62
|
| Page | What it covers |
|
|
63
63
|
|---|---|
|
|
64
|
+
| [docs/quickstart.md](quickstart.md) | getting started as a lead of several projects, in 10 minutes: install, the parent folder, `raf init`, the tour, the first wave |
|
|
65
|
+
| [docs/with-your-tools.md](with-your-tools.md) | cadence next to a tracker, `HANDOFF.md`, ADRs and a commit convention: what it does not replace, what is optional, what plugs in (`plan:` read-only, `plan.commit`, `docs.sync`), what it adds |
|
|
64
66
|
| [docs/raf.md](raf.md) | the plan: commands, plan file, foreign plan formats, Gantt |
|
|
65
67
|
| [docs/orchestrate.md](orchestrate.md) | waves of sessions: budgets, spec, accept and review steps, neighbouring repositories, one worktree per lot (`orchestrate.worktrees: true`, on in cadence's own `cadence.yaml`, off by default elsewhere; `--integrate`, `--replay`, `--remove-worktree`; recovery of a handed-back lot's branch and the `CHANGELOG.md` union at the integration are described there) |
|
|
66
68
|
| [docs/deliver.md](deliver.md) | delivery: wait for CI, deploy, verify the effect |
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1010 190" width="1010" height="190" role="img" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif" font-size="13" fill="#e6edf3">
|
|
2
|
+
<title>One lead, several projects, one wave</title>
|
|
3
|
+
<desc>A parent folder holds the projects alpha and beta, each with its own plan. The lead session reads their tour and you choose the lots. A wave of fresh sessions does the work, then you review and deliver.</desc>
|
|
4
|
+
<defs><marker id="arq" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#8b949e"/></marker></defs>
|
|
5
|
+
<rect width="1010" height="190" fill="#0d1117"/>
|
|
6
|
+
<text x="20" y="22" font-weight="600" font-size="13.5" fill="#e6edf3">1. Parent folder</text>
|
|
7
|
+
<rect x="20" y="32" width="220" height="140" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5" stroke-dasharray="5 4"/>
|
|
8
|
+
<text x="130" y="52" text-anchor="middle" font-size="11.5" fill="#8b949e">atelier/ (not a repository)</text>
|
|
9
|
+
<rect x="40" y="64" width="180" height="42" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
10
|
+
<text x="130" y="83" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">alpha</text>
|
|
11
|
+
<text x="130" y="98" text-anchor="middle" font-size="11" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#8b949e">raf init · plan</text>
|
|
12
|
+
<rect x="40" y="116" width="180" height="42" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
13
|
+
<text x="130" y="135" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">beta</text>
|
|
14
|
+
<text x="130" y="150" text-anchor="middle" font-size="11" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#8b949e">raf init · plan</text>
|
|
15
|
+
<text x="300" y="22" font-weight="600" font-size="13.5" fill="#e6edf3">2. The lead</text>
|
|
16
|
+
<rect x="300" y="64" width="210" height="94" rx="6" fill="#0c2d6b" stroke="#58a6ff" stroke-width="2"/>
|
|
17
|
+
<text x="405.0" y="96" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Lead session</text>
|
|
18
|
+
<text x="405.0" y="114" text-anchor="middle" font-size="11.5" fill="#c9d1d9">reads the tour, asks you</text>
|
|
19
|
+
<text x="405.0" y="132" text-anchor="middle" font-size="11" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#c9d1d9">/cadence:lead</text>
|
|
20
|
+
<text x="570" y="22" font-weight="600" font-size="13.5" fill="#e6edf3">3. The wave</text>
|
|
21
|
+
<rect x="570" y="64" width="200" height="94" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
22
|
+
<text x="670.0" y="96" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Fresh sessions</text>
|
|
23
|
+
<text x="670.0" y="114" text-anchor="middle" font-size="11.5" fill="#8b949e">implement, then review</text>
|
|
24
|
+
<text x="670.0" y="132" text-anchor="middle" font-size="11" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#8b949e">cadence orchestrate</text>
|
|
25
|
+
<text x="830" y="22" font-weight="600" font-size="13.5" fill="#e6edf3">4. You</text>
|
|
26
|
+
<rect x="830" y="64" width="160" height="94" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
27
|
+
<text x="910.0" y="96" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Review, deliver</text>
|
|
28
|
+
<text x="910.0" y="114" text-anchor="middle" font-size="11.5" fill="#8b949e">one project at a time</text>
|
|
29
|
+
<text x="910.0" y="132" text-anchor="middle" font-size="11" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#8b949e">/cadence:deliver</text>
|
|
30
|
+
<polyline points="240,111 298,111" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arq)"/>
|
|
31
|
+
<polyline points="510,111 568,111" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arq)"/>
|
|
32
|
+
<polyline points="770,111 828,111" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arq)"/>
|
|
33
|
+
</svg>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1010 190" width="1010" height="190" role="img" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif" font-size="13" fill="#1f2328">
|
|
2
|
+
<title>One lead, several projects, one wave</title>
|
|
3
|
+
<desc>A parent folder holds the projects alpha and beta, each with its own plan. The lead session reads their tour and you choose the lots. A wave of fresh sessions does the work, then you review and deliver.</desc>
|
|
4
|
+
<defs><marker id="arq" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#57606a"/></marker></defs>
|
|
5
|
+
<rect width="1010" height="190" fill="#ffffff"/>
|
|
6
|
+
<text x="20" y="22" font-weight="600" font-size="13.5" fill="#1f2328">1. Parent folder</text>
|
|
7
|
+
<rect x="20" y="32" width="220" height="140" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5" stroke-dasharray="5 4"/>
|
|
8
|
+
<text x="130" y="52" text-anchor="middle" font-size="11.5" fill="#57606a">atelier/ (not a repository)</text>
|
|
9
|
+
<rect x="40" y="64" width="180" height="42" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
|
|
10
|
+
<text x="130" y="83" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">alpha</text>
|
|
11
|
+
<text x="130" y="98" text-anchor="middle" font-size="11" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#57606a">raf init · plan</text>
|
|
12
|
+
<rect x="40" y="116" width="180" height="42" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
|
|
13
|
+
<text x="130" y="135" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">beta</text>
|
|
14
|
+
<text x="130" y="150" text-anchor="middle" font-size="11" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#57606a">raf init · plan</text>
|
|
15
|
+
<text x="300" y="22" font-weight="600" font-size="13.5" fill="#1f2328">2. The lead</text>
|
|
16
|
+
<rect x="300" y="64" width="210" height="94" rx="6" fill="#ddf4ff" stroke="#0969da" stroke-width="2"/>
|
|
17
|
+
<text x="405.0" y="96" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Lead session</text>
|
|
18
|
+
<text x="405.0" y="114" text-anchor="middle" font-size="11.5" fill="#57606a">reads the tour, asks you</text>
|
|
19
|
+
<text x="405.0" y="132" text-anchor="middle" font-size="11" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#57606a">/cadence:lead</text>
|
|
20
|
+
<text x="570" y="22" font-weight="600" font-size="13.5" fill="#1f2328">3. The wave</text>
|
|
21
|
+
<rect x="570" y="64" width="200" height="94" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
|
|
22
|
+
<text x="670.0" y="96" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Fresh sessions</text>
|
|
23
|
+
<text x="670.0" y="114" text-anchor="middle" font-size="11.5" fill="#57606a">implement, then review</text>
|
|
24
|
+
<text x="670.0" y="132" text-anchor="middle" font-size="11" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#57606a">cadence orchestrate</text>
|
|
25
|
+
<text x="830" y="22" font-weight="600" font-size="13.5" fill="#1f2328">4. You</text>
|
|
26
|
+
<rect x="830" y="64" width="160" height="94" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
|
|
27
|
+
<text x="910.0" y="96" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Review, deliver</text>
|
|
28
|
+
<text x="910.0" y="114" text-anchor="middle" font-size="11.5" fill="#57606a">one project at a time</text>
|
|
29
|
+
<text x="910.0" y="132" text-anchor="middle" font-size="11" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#57606a">/cadence:deliver</text>
|
|
30
|
+
<polyline points="240,111 298,111" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arq)"/>
|
|
31
|
+
<polyline points="510,111 568,111" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arq)"/>
|
|
32
|
+
<polyline points="770,111 828,111" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arq)"/>
|
|
33
|
+
</svg>
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
[← cadence](../README.md)
|
|
2
|
+
|
|
3
|
+
# Start in 10 minutes: one lead, several projects
|
|
4
|
+
|
|
5
|
+
You keep a folder with a few small projects. A **lead** session looks at all of them, you choose what to
|
|
6
|
+
do, and a program runs each lot in fresh sessions. This page takes you from nothing to the first wave.
|
|
7
|
+
|
|
8
|
+
<picture>
|
|
9
|
+
<source media="(prefers-color-scheme: dark)" srcset="quickstart-dark.svg">
|
|
10
|
+
<img alt="A parent folder holds the projects alpha and beta; the lead session reads their tour and you choose; a wave of fresh sessions works; you review and deliver" src="quickstart-light.svg">
|
|
11
|
+
</picture>
|
|
12
|
+
|
|
13
|
+
The commands below were played on a clean Windows 11 machine with WSL (Ubuntu 24.04, L357): about 40 seconds of commands,
|
|
14
|
+
29 of them for Node and Claude Code. The rest of the ten minutes is reading and typing.
|
|
15
|
+
|
|
16
|
+
## Before you start
|
|
17
|
+
|
|
18
|
+
You need four things; the page says how to check each.
|
|
19
|
+
|
|
20
|
+
- **Node 22.18 or later** (`node --version`). If you have none: [nvm](https://github.com/nvm-sh/nvm), or the NodeSource
|
|
21
|
+
package of your distribution (`setup_22.x`). Node 20 is not enough.
|
|
22
|
+
- **git, with an identity.** Without one, the first `git commit` stops with `empty ident name`:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
git config --global user.name "Your Name"
|
|
26
|
+
git config --global user.email "you@example.com"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- **Claude Code, installed and logged in.** `claude auth status` must print `"loggedIn": true`. A wave opens
|
|
30
|
+
`claude -p` sessions; `cadence orchestrate` checks `claude auth status` first and, when Claude Code is not logged in,
|
|
31
|
+
is refused before it writes anything in the plan (« claude n'est pas connecté : lancer `claude` puis /login »).
|
|
32
|
+
Install it from [claude.com/claude-code](https://claude.com/claude-code), then run `claude` once and log in.
|
|
33
|
+
- **A test command** for each project (`npm test`, `pytest`, `go test ./...`). The wave runs it after every change.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
npm install -g @sylad/cadence
|
|
39
|
+
cadence --version
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
If npm answers `EACCES: permission denied, mkdir '/usr/lib/node_modules/@sylad'`, your Node belongs to the system.
|
|
43
|
+
Give npm a folder of your own, then install again:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
npm config set prefix ~/.npm-global
|
|
47
|
+
export PATH="$HOME/.npm-global/bin:$PATH" # add this line to ~/.bashrc or ~/.zshrc too
|
|
48
|
+
npm install -g @sylad/cadence
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The skills and agents come as a Claude Code plugin:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
claude plugin marketplace add Sylad/cadence
|
|
55
|
+
claude plugin install cadence@cadence
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## The parent folder
|
|
59
|
+
|
|
60
|
+
The lead works from a folder that holds the projects as sub-folders. The folder itself is not a repository.
|
|
61
|
+
Each project is one: give it a plan with `raf init`, a first lot, and the test command, in one commit.
|
|
62
|
+
Run the block once per project (here `alpha`, then `beta`):
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
mkdir alpha && cd alpha
|
|
66
|
+
git init -b main
|
|
67
|
+
raf init
|
|
68
|
+
raf add "Create hello.txt containing hello" --estimate 0.25
|
|
69
|
+
cat > cadence.yaml <<'EOF'
|
|
70
|
+
orchestrate:
|
|
71
|
+
test: test -f hello.txt # passes once the first lot has created hello.txt
|
|
72
|
+
EOF
|
|
73
|
+
git add -A && git commit -m "chore: init raf"
|
|
74
|
+
cd ..
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
What it does: `raf init` writes the plan (`.cadence/raf.yaml`), a commit hook and a `.gitignore` line. The commit
|
|
78
|
+
that adds them cites no lot and is no gap: like any commit that touches only the plan files, it is left out of `raf check`
|
|
79
|
+
and of the tour's drift column (what `raf init` writes counts as plan files, `cadence.yaml` created in the same commit
|
|
80
|
+
with its `orchestrate.test` line included; a code file in it would make it a `commit sans lot` again). `cadence.yaml` is the project's settings file; its single
|
|
81
|
+
`orchestrate.test` line is the minimum the wave needs, and without it the tour warns `porte de tests absente`.
|
|
82
|
+
The folder is empty, so the gate here is `test -f hello.txt`, which the toy lot satisfies. On a project that already exists,
|
|
83
|
+
put its own test command there (`npm test`, `pytest`, `go test ./...`): `npm test` in a folder without `package.json` fails.
|
|
84
|
+
The reference for every other key is [orchestrate](orchestrate.md) and [deliver](deliver.md).
|
|
85
|
+
|
|
86
|
+
Then, from the parent folder:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
cadence lead tour
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
It prints one line per project: lots in progress, drift, next lot, repository state. Expect `alpha · en cours rien ·
|
|
93
|
+
dérive aucune · … · prochain L1 Create hello.txt containing hello · dépôt propre`, and the same for `beta`. This is the table the lead reads.
|
|
94
|
+
|
|
95
|
+
## Start the lead
|
|
96
|
+
|
|
97
|
+
Open Claude Code in the parent folder and call the skill:
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
claude
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
then type `/cadence:lead` (or say « pilot all projects »). The lead runs the tour, reports in a table, proposes three
|
|
104
|
+
lots and **waits for your priority**: nothing starts before you answer. [The lead workflow](lead.md) draws every step.
|
|
105
|
+
|
|
106
|
+
## The first wave
|
|
107
|
+
|
|
108
|
+
When you pick `alpha:L1`, the lead launches a wave. You can do it yourself, from the parent folder:
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
cadence orchestrate alpha:L1 --dry-run
|
|
112
|
+
cadence orchestrate alpha:L1 --wave first
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The first line only prints what would run: each step, its model and its budget. The second runs the lot in fresh
|
|
116
|
+
sessions: implement, then review, and hands the lot back to you. From inside a project, write `.:L1` instead of
|
|
117
|
+
`alpha:L1` (the project is a sub-folder of the current folder, so `alpha:L1` from inside `alpha` finds nothing).
|
|
118
|
+
|
|
119
|
+
The sessions of a wave never push and never deliver. You read the table, answer the questions it brings back, then
|
|
120
|
+
`/cadence:deliver` ships one project at a time ([deliver](deliver.md)). How waves, budgets and reviews work: [orchestrate](orchestrate.md).
|
|
121
|
+
|
|
122
|
+
## If it stops
|
|
123
|
+
|
|
124
|
+
| You see | Cause | Do |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| `npm: command not found` | no Node | install Node 22.18+ (above) |
|
|
127
|
+
| `EACCES: permission denied, mkdir '/usr/lib/node_modules/@sylad'` | system-owned Node | `npm config set prefix ~/.npm-global` (above) |
|
|
128
|
+
| `fatal: empty ident name … not allowed` | no git identity | the two `git config --global` lines (above) |
|
|
129
|
+
| « claude n'est pas connecté : lancer `claude` puis /login » | Claude Code not logged in | `claude`, log in, then `claude auth status` |
|
|
130
|
+
| `porte de tests absente` | no `orchestrate.test` | the `cadence.yaml` line of the project block |
|
|
131
|
+
| `alpha:L1 … introuvable` | launched from inside `alpha` | use `.:L1` |
|
|
132
|
+
|
|
133
|
+
A problem that is not here: [open an issue](https://github.com/Sylad/cadence/issues/new/choose) with `cadence --version` and the command you ran.
|
|
134
|
+
|
|
135
|
+
## Not played
|
|
136
|
+
|
|
137
|
+
The lead skill and a wave played to the end (review, `raf done`, delivery) were not played on that machine, which was not logged
|
|
138
|
+
in to Claude Code; the tour, the dry run and the dry delivery were. Other systems (macOS, native Linux, PowerShell without WSL) were not played either.
|
package/docs/raf.md
CHANGED
|
@@ -21,7 +21,10 @@
|
|
|
21
21
|
the plan, a journal), or the QA expectations file (`docs/qa/expectations.md`,
|
|
22
22
|
or the file named by `qa.expectations`; the lead commits the `qa-reviewer`'s draft of it). Such a commit is never a "commit without a lot", and it
|
|
23
23
|
does not count as work on the lots it cites: it is absent from `raf commits`,
|
|
24
|
-
does not start a `todo` lot, does not make a code review stale,
|
|
24
|
+
does not start a `todo` lot, does not make a code review stale, is not in the commit
|
|
25
|
+
count that `raf now`, `cadence lead tour` and `cadence session start` show for a lot in
|
|
26
|
+
progress (the same count as `raf commits` and `raf show`: after `plan: L1 démarré` alone,
|
|
27
|
+
`raf now` prints no commit and the tour `L1 (0 commit)`, L379; the commit column and the tooltip of `raf gantt` read the same count, L381), and
|
|
25
28
|
`cadence deliver` does not announce the lots it cites as delivered. The files
|
|
26
29
|
decide, never the subject: a `chore(plan): …` commit that touches a source
|
|
27
30
|
file is a commit like any other.
|
|
@@ -47,6 +50,7 @@ raf gantt # .cadence/gantt.html (next to the plan)
|
|
|
47
50
|
|
|
48
51
|
| Command | Effect |
|
|
49
52
|
|---|---|
|
|
53
|
+
| `raf --version` · `raf -v` | print the version of the package, as `cadence --version` does (L376); only as the first argument, `raf show --version` stays an unknown option |
|
|
50
54
|
| `raf init [--project name] [--prefix L] [--no-hook]` | create the plan, install the hook, add `.playwright-mcp/` to `.gitignore` |
|
|
51
55
|
| `raf add "title" [--estimate d] [--quickwin] [--visible [--screen page\|component\|journey]] [--spec] [--public "title"] [--after L2,L4] [--uses banc,passerelle] [--parent L3]` | add a lot or a sub-task, print its id (`--public`: the lot's title in the public's words, written as `public:` right after `title:`; `--screen`: the screen a visible lot creates or changes — a new `page`, a new `component` or a modified `journey` — written as `screen:` right after `visible:`, shown by `raf show`; it requires `--visible`, and `raf check` reports a `screen:` without `visible:` or with another value as an error. Only `page` triggers the spec step of `orchestrate` (L209), never inferred from the title; `--spec`: the lot changes a behaviour (something a user, an API client or another service can observe), written as `spec: true` right after `screen:`, `visible:`, `quickwin:` or `estimate:` (whichever comes last), shown by `raf show`, refused on a sub-task. Also set by the lead at planning time, never inferred from the title); `--uses`: the shared resources the lot occupies, written as `uses: [banc, passerelle]` after `after:` and before `created:` ([shared resources](#shared-resources-uses-and-resources), L251), refused on a sub-task) |
|
|
52
56
|
| `raf add "title" --every <days>` · `raf did <id> ["text"]` | recurring lot: `every` (days) and `last` (last time done) fields; the lot stays `todo`, `raf now` and `session start` list it under « Récurrent » with « dû depuis N j » / « prochain dans N j »; `raf did` resets the count (spec: `docs/superpowers/specs/2026-10-04-L11-tache-recurrente.md`) |
|
|
@@ -129,6 +133,10 @@ then the legacy `docs/plan/raf.yaml`.
|
|
|
129
133
|
- `raf init` creates `.cadence/raf.yaml` and `.cadence/notes/` (with a `.gitkeep`, so git tracks the empty folder). It refuses
|
|
130
134
|
when a plan already exists where it would create one, a legacy `docs/plan/raf.yaml` included: it never creates a second plan
|
|
131
135
|
beside it. With `plan: path` in `cadence.yaml`, or `--file`, the plan and nothing else is created there.
|
|
136
|
+
The commit that follows `raf init` opens no « commit sans lot » gap (L377): the plan, `.cadence/notes/.gitkeep`, the
|
|
137
|
+
`.playwright-mcp/` line of `.gitignore` and a `cadence.yaml` created in the same commit with only `plan:` and `orchestrate:` keys are plan files, so such a commit is
|
|
138
|
+
a plan-only commit for `raf check`, the hook and the tour. Any other file in it, another `.gitignore` line, a `deliver:` or `session:` key
|
|
139
|
+
in a new `cadence.yaml` or a later edit of it keeps it a commit like any other.
|
|
132
140
|
- A project that still has `docs/plan/raf.yaml` (and no `.cadence/raf.yaml`) keeps working, read and written as before. Every command
|
|
133
141
|
that loads the plan prints one line on **stderr** (standard output, `--json` included, is unchanged) with the move to do —
|
|
134
142
|
`cd <repository root> && mkdir -p .cadence && git mv docs/plan/raf.yaml .cadence/raf.yaml` (`mv` outside a git repository or when git does not track the file, where `git mv` would fail; the root is spelled out, quoted if
|
package/docs/releasing.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Releasing
|
|
4
4
|
|
|
5
5
|
A version exists in three places and is published in two; a release does all of it, in this order.
|
|
6
|
-
`cadence release patch|minor|major [--dry-run]` (L341) prepares step 1 and prints steps 2 to
|
|
6
|
+
`cadence release patch|minor|major [--dry-run]` (L341) prepares step 1 and prints steps 2 to 6 in order; it publishes nothing.
|
|
7
7
|
|
|
8
8
|
1. Bump `version` in `package.json` (then `npm install` to refresh `package-lock.json`),
|
|
9
9
|
`.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json`, and write the version's section of
|
|
@@ -16,15 +16,22 @@ A version exists in three places and is published in two; a release does all of
|
|
|
16
16
|
in that commit, not before the review. A commit that touches any other file
|
|
17
17
|
— or another field of a manifest, or a dependency in `package-lock.json` — is work like any other and has
|
|
18
18
|
to be reviewed.
|
|
19
|
-
2.
|
|
19
|
+
2. Replay the project's gate on that commit: `cadence verify --gate --sha HEAD` (L380). The version commit empties
|
|
20
|
+
`## [Unreleased]` of `CHANGELOG.md`, and a test that read only that section would pass before it and fail after — the red
|
|
21
|
+
publish run of 1.14.0 (L368) and the stopped 1.15.2 (L380) — so the suite is run on the commit that will be tagged, before the
|
|
22
|
+
billed evaluation and before the tag. Red: fix it in a commit of its own and redo the version commit. A test that looks
|
|
23
|
+
for the entry of a lot in the CHANGELOG reads the section that carries it (`[Unreleased]` or the published version) through
|
|
24
|
+
`changelogSectionOf` (`test/changelog-section.ts`); `test/changelog-garde.test.ts` refuses a test that selects `## [Unreleased]`
|
|
25
|
+
of the repository's CHANGELOG itself.
|
|
26
|
+
3. Run the plugin evaluation, `npm run eval:plugin` (billed, see [Evaluating the plugin](#evaluating-the-plugin-before-a-release)),
|
|
20
27
|
on that version commit; a case below the threshold stops the release until you have read it in the report.
|
|
21
|
-
|
|
28
|
+
4. `git tag v<version> && git push origin main v<version>` — the tag starts `.github/workflows/publish.yml`,
|
|
22
29
|
which publishes to npm through Trusted Publishing (OIDC, no token stored anywhere): it checks the tag
|
|
23
30
|
matches `package.json`, `.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json` and that `CHANGELOG.md` has a `## [x.y.z]` section for it (no section, no publication), then `npm publish --provenance`, where `prepublishOnly` runs the type-check and the
|
|
24
31
|
tests and `prepare` builds `dist/`; a red suite stops the publication; then it creates the GitHub release with that CHANGELOG section as its text. The trusted publisher is declared
|
|
25
32
|
once on npmjs.com (package settings → Trusted Publisher → GitHub Actions, `Sylad/cadence`, `publish.yml`).
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
5. Watch the run: `gh run watch` (or `gh run list --workflow publish.yml`).
|
|
34
|
+
6. Check the effect: `npm view @sylad/cadence version` answers the new version.
|
|
28
35
|
A run is safe to re-run, and two runs for one tag queue instead of racing (`concurrency` per ref, never cancelling the one that publishes):
|
|
29
36
|
a version already on npm skips `npm publish`, and a GitHub release that is missing is created (`--verify-tag`)
|
|
30
37
|
while an existing one is left alone. If the package is on npm but the release is still missing, re-run the job;
|
|
@@ -32,10 +39,12 @@ A version exists in three places and is published in two; a release does all of
|
|
|
32
39
|
|
|
33
40
|
`.github/workflows/tests.yml` runs the type-check and the test suite on the same runner image and `npm ci` as the publish job, in a matrix of Node 22 and 24 (the minimum `engines.node` allows, and the Node the publish job uses), and never publishes: start it with `gh workflow run tests.yml --ref <branch>` or by pushing a `diag/**` branch, to see on the runner what a red publish run saw.
|
|
34
41
|
|
|
42
|
+
The suite runs the same way on your machine as on that runner, **without `raf` or `cadence` installed** (L384): `test/global-setup.ts` replaces, in the `PATH` of the tests, every folder outside the repository that carries a `raf` or a `cadence` (`~/.local/bin`, the global npm folder, the plugin's `bin/`) by a mirror of that folder without those two names — the other tools stay, `node` included. A test that relies on the installed tool turns red on the lead's machine and in the gate, not only in the publish run: the 1.16.0 publication failed because the post-commit hook of `test/raf-init-commit.test.ts` (R2) found `raf` in the developer's `PATH` and not on the runner. A test that needs `raf` (a hook, a script) builds its own from the sources (`CADENCE_TEST_PACKAGE`).
|
|
43
|
+
|
|
35
44
|
The Claude Code plugin is read from the repository, so pushing `main` is what updates it; npm is what
|
|
36
45
|
`npx @sylad/cadence` and a global install read, and only the tag publishes there. A missing tag, or a red
|
|
37
46
|
publish run, leaves npm behind without any other error — 0.3.0 and 0.4.0 were never published — hence
|
|
38
|
-
step
|
|
47
|
+
step 6.
|
|
39
48
|
|
|
40
49
|
## `cadence release`
|
|
41
50
|
|
|
@@ -70,6 +79,7 @@ introduction (the newest version first, as the file is read). Exit code 1 when a
|
|
|
70
79
|
version files are written, the commit is not ready. `--dry-run` reads the forms the same way (nothing is written) and exits 1 on the same findings, so the forms can be checked before the real run.
|
|
71
80
|
|
|
72
81
|
It prints the lead's steps, in order, and runs none: the `git add` of exactly the files of the version commit and the commit,
|
|
82
|
+
`cadence verify --gate --sha HEAD` (always printed, before the evaluation and the tag),
|
|
73
83
|
`npm run eval:plugin -- --trust-plugin` (only when `package.json` has that script), `git tag v<x.y.z>`, `git push origin <branch> v<x.y.z>`,
|
|
74
84
|
`gh run watch`, `npm view <package> version` (not for a private package) and, only with a `.claude-plugin/plugin.json`,
|
|
75
85
|
the plugin update: `claude plugin marketplace update <marketplace>` first (without it the installed plugin stays at the old
|
package/docs/session.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# session
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
cadence session start # notes from the last close, lots in progress (commit count, `0 commit` included, and last activity; silent ones flagged),
|
|
6
|
+
cadence session start # notes from the last close, lots in progress (work commit count, `0 commit` included — plan commits are not counted, as in `raf commits` — and last activity; silent ones flagged),
|
|
7
7
|
# work done since yesterday by lot, drift, repo state, 3 proposals
|
|
8
8
|
cadence session start --since "3 days ago" --idle 2
|
|
9
9
|
cadence session close # commits by lot since the last `session start`,
|
|
@@ -26,7 +26,7 @@ cadence session context # `ctx 42 % (84000/200000)`: the context of Y
|
|
|
26
26
|
`session close` takes it as its window and names it in the section title: `Commits de la période (depuis
|
|
27
27
|
l'ouverture de 09:12)` (with the date when it is not today). A close never moves the boundary, refused
|
|
28
28
|
or not: run again without a `session start` in between (the `session-close` skill does, after fixing a
|
|
29
|
-
point), it still reports since that opening instead of an empty period. With no recorded opening, or an
|
|
29
|
+
point), it still reports since that opening instead of an empty period. The period journal (`Fait depuis`, `Commits de la période`) lists work commits only: plan commits (`plan: L1 démarré`) are left out, as in `raf commits`. With no recorded opening, or an
|
|
30
30
|
unreadable file, the window is the current day, said as such (`depuis 2026-09-28 00:00`); `--since` is
|
|
31
31
|
quoted as given. (A `session.json` written by 0.18.0 after a close, `depuis la clôture de …`, is still read.)
|
|
32
32
|
|
package/docs/whats-new.md
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
## In two lines
|
|
6
6
|
|
|
7
|
+
- **1.16.1**: publishes 1.16.0 (tagged, never published), plus `raf gantt` counting commits without plan commits, one login check per `orchestrate --continue` launch, and a wider `CHANGELOG.md` conflict settled by worktree integration.
|
|
8
|
+
- **1.16.0**: easier arrival for a team already equipped and for a new user: a `docs/with-your-tools.md` page, `raf --version`, no gap after `raf init`, Claude Code login checked before any write, commit counts without plan commits.
|
|
9
|
+
- **1.15.1**: a getting-started page (`docs/quickstart.md`) takes a lead of several projects from install to the first wave and lists the usual stops; GitHub issue templates for bugs and requests; a contrast fix in the dark figure.
|
|
7
10
|
- **1.15.0**: worktree integration settles the `CHANGELOG.md` conflict by itself; `--integrate` accepts a lot corrected and reviewed outside the wave; `raf ignore` clears a plan commit citing lots; `raf done` gives the plan commit.
|
|
8
11
|
- **1.14.1**: publishes 1.14.0 (tagged, never published): Node 22.18 or later is required; `plan.commit`, `plan.onWrite`, `orchestrate.testReplay`, `spec green`, `ref coverage` and `verify --gate --sha`.
|
|
9
12
|
- **1.14.0**: Node 22.18 or later is now required (Node 20 dropped). `plan.commit`, `plan.onWrite` and `orchestrate.testReplay` fit a wave to your hooks, files and load; `spec green`, `ref coverage` and `verify --gate --sha` are new.
|
|
@@ -36,6 +39,12 @@ Each is a paragraph in [docs/whats-new.md](whats-new.md) and a full section in [
|
|
|
36
39
|
|
|
37
40
|
The long form of « In two lines » above (the README no longer carries a « What's new » section since L326: it links here). The full history, with the date of each version, is in [CHANGELOG.md](../CHANGELOG.md).
|
|
38
41
|
|
|
42
|
+
**1.16.1**: 1.16.0 was tagged but never published: a test added with it (R2 of L377) supposed `raf` installed in the `PATH`, found it on the developer's machine and not on the runner, and the publish run went red, a fault of the test and not of the tool (L384); 1.16.1 publishes it as it stands, so everything listed under 1.16.0 below arrives with it. The test suite no longer depends on a `raf` or a `cadence` installed on the machine (L384, `docs/releasing.md`): the test now has its hook run `raf` from the sources, and the suite removes every installed `raf` and `cadence` from the `PATH` of the tests (the other tools stay), so such a test is red on the lead's machine and in the gate, not only at publication. `raf gantt` counts the commits of a lot as `raf commits` does (L381, `docs/raf.md`): plan commits no longer inflate the commit column nor the tooltip of a bar, a lot whose only commit is `plan: L1 démarré` reads 0, and the filter reads the files of the commits in one `git log` call instead of one `git diff-tree` per commit (about 20 s down to 2 s on 2,700 commits for the Gantt page); a test now holds the same count for `cadence session start` and `close`, and the test that held `raf now` to the lots it shows can now fail. With `--continue`, `cadence orchestrate` reads `claude auth status` once per launch instead of again, up to 20 s each, for every candidate lot of every round (L382, `docs/orchestrate.md`), and the message quoted in `docs/quickstart.md` and `docs/orchestrate.md` no longer nests backticks inside backticks, which broke the Markdown rendering. The worktree integration of `cadence orchestrate` also settles a `CHANGELOG.md` conflict where one lot adds a section under `[Unreleased]` (`### Fixed`) with its entry and the other a bare entry (L383, `docs/orchestrate.md`): the bare entry is filed under the section the other side added, one section title and one entry per lot, instead of handing the lot back; with no single section to file it under, the conflict stays by hand.
|
|
43
|
+
|
|
44
|
+
**1.16.0**: easier arrival, for a team that already has its tools and for someone who has never used cadence. A new page, `docs/with-your-tools.md` (L363, a short section of the README), is written for teams that already have a tracker, a `HANDOFF.md`, ADRs and a commit convention: what cadence does not replace, what is optional (`raf`, `session start|close`, the hook), what plugs into what you have (a plan kept by another tool, read-only, with your own ticket ids; `plan.commit` for the subject of the plan commits, which by default still read `plan: L12 démarré (orchestrate …)`, GitHub issue #15, L375; `docs.sync` for your docs) and what it adds (waves with an automatic review, a deliver that verifies the effect), with a figure. The other changes come from the walkthrough of a new user. `raf --version` and `raf -v` print the version, as `cadence --version` does, instead of `Unknown option '--version'` (L376, only as the first argument: `raf show --version` stays an unknown option), and `cadence orchestrate alpha:L1` launched from inside `alpha` says why the folder is not found (the project is a sub-folder of the current folder; inside a repository `.:L1` works). The commit that follows `raf init` no longer opens a « commit sans lot » gap (L377, `docs/raf.md`, `docs/quickstart.md`): what `raf init` writes and a `cadence.yaml` created in the same commit with only `plan:` and `orchestrate:` keys count as plan files, while a code file, another `.gitignore` line, a `deliver:` key or a later edit of `cadence.yaml` keeps it a gap, and the getting-started page drops its `raf ignore` workaround. `cadence orchestrate` checks that Claude Code is logged in (`claude auth status`) before starting a lot (L378, `docs/orchestrate.md`): when it is not, the command is refused before any write to the plan (no `raf start`, no commit, the lot stays `todo`) and the message says to run `claude` then `/login`, `--dry-run` reports it with a `⚠`; before, the lot was started and failed at the first session with `Not logged in`, left `doing`. `raf now`, `cadence lead tour` and `cadence session start` count the commits of a lot as `raf commits` does (L379, `docs/raf.md`, `docs/lead.md`, `docs/session.md`): plan commits, which touch only plan files, are left out, so a lot just started reads `0 commit` instead of `1 commit(s)` while `raf commits` lists nothing, and the period journals of `session start` and `session close` leave them out too. `cadence release` prints `cadence verify --gate --sha HEAD` right after the version commit, before the plugin evaluation and the tag, so the suite runs on the commit that will be published (L380, `docs/releasing.md`), and the tests that look for the entry of a lot in `CHANGELOG.md` read the section that carries it, not `[Unreleased]` alone, which the version commit empties, a fault that had stopped the release of 1.14.0 and again the next one (a guard test refuses the pattern).
|
|
45
|
+
|
|
46
|
+
**1.15.1**: a way in for a lead who has never used cadence. A new getting-started page, `docs/quickstart.md` (L358, a section of the README), goes from install to the first wave: the parent folder, `raf init` in each project with the minimal `cadence.yaml`, the tour, `/cadence:lead`, the first wave, and a table of the usual stops (no Node, `EACCES`, no git identity, Claude Code not logged in). It comes from a walkthrough measured on a clean machine. Two GitHub issue templates, bug and request, are forms (L358, `.github/ISSUE_TEMPLATE/`); a `config.yml` turns off blank issues and points to the getting-started page, and the bug form asks for `cadence --version`, the steps and what was printed. The dark getting-started figure (`docs/quickstart-dark.svg`) draws the grey texts of « Lead session » in a lighter grey, 4.26:1 on the blue before and now above the 4.5:1 of WCAG 2.2 AA, and `docs/overview.md` lists `docs/quickstart.md` in its map of the pages (L374).
|
|
47
|
+
|
|
39
48
|
**1.15.0**: fewer lots handed back, and fewer manual detours, on the lead's side. The integration of `cadence orchestrate` in worktree mode no longer hands a lot back for a `CHANGELOG.md` conflict when the branch was released while the lot lived (L369, `docs/orchestrate.md`): an entry that the release moved under the published version is no longer read as a removal, and the entries a lot's commits wrote then reworded are dropped in favour of the version at the tip of the lot's branch, so one final entry per lot is left. `cadence orchestrate --resume --integrate <project:lot>` now accepts a lot handed back for its budget or a non-compliant review that the lead corrected and reviewed outside the wave (L370, `docs/orchestrate.md`, `docs/lead.md`): fix in the worktree, `raf review` there with the plan committed there, then `--integrate`; the lot restarts at the integration step with that verdict instead of the refusal « le lot n'est pas rendu à l'étape d'intégration ». A review older than a commit of the lot, or none, keeps the refusal. If the lead also wrote on that lot in the repository's plan meanwhile, the plan conflict of the rebase is settled by the program, provided that plan commit adds nothing but lot reviews. `raf ignore` also acknowledges a commit that cites known lots (L372): a plan commit that creates lots or cites a setting in parentheses was counted as work on those lots and could not be cleared; an acknowledged commit now counts for no lot, `raf commits` no longer lists it, and `raf check --ignored` does. `raf done <lot>` says that it did not commit the plan (L371): it never has, `raf` only writes the plan file, so when the plan or a file `plan.onWrite` produces has changes, the message prints the `git add` and `git commit` that take them, the subject following the project's `plan.commit` template.
|
|
40
49
|
|
|
41
50
|
**1.14.1**: 1.14.0 was tagged but never published (a test of the Node minimum read only `[Unreleased]`, which the version commit empties, a fault of the test and not of the tool, L368); 1.14.1 publishes it as it stands, so everything listed under 1.14.0 below arrives with it, Node 22.18 or later first.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1010 290" width="1010" height="290" role="img" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif" font-size="13" fill="#e6edf3">
|
|
2
|
+
<title>cadence next to the tools you already have</title>
|
|
3
|
+
<desc>Your tools stay as they are: the tracker, HANDOFF.md, the ADRs and your commit convention. The plan can be read from your tracker's file, the plan commits follow your format and your docs are kept in step. On top, cadence adds orchestrated waves with an automatic review, and a deliver that verifies the effect.</desc>
|
|
4
|
+
<defs><marker id="arw" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#8b949e"/></marker></defs>
|
|
5
|
+
<rect width="1010" height="290" fill="#0d1117"/>
|
|
6
|
+
<text x="20" y="22" font-weight="600" font-size="13.5" fill="#e6edf3">1. Your tools (kept)</text>
|
|
7
|
+
<rect x="20" y="36" width="290" height="52" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
8
|
+
<text x="165" y="57" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Linear, Jira</text>
|
|
9
|
+
<text x="165" y="74" text-anchor="middle" font-size="11.5" fill="#8b949e">tickets, board, assignment</text>
|
|
10
|
+
<rect x="20" y="98" width="290" height="52" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
11
|
+
<text x="165" y="119" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">HANDOFF.md</text>
|
|
12
|
+
<text x="165" y="136" text-anchor="middle" font-size="11.5" fill="#8b949e">your hand-over notes</text>
|
|
13
|
+
<rect x="20" y="160" width="290" height="52" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
14
|
+
<text x="165" y="181" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">ADRs</text>
|
|
15
|
+
<text x="165" y="198" text-anchor="middle" font-size="11.5" fill="#8b949e">your decisions</text>
|
|
16
|
+
<rect x="20" y="222" width="290" height="52" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
17
|
+
<text x="165" y="243" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Conventional Commits</text>
|
|
18
|
+
<text x="165" y="260" text-anchor="middle" font-size="11.5" fill="#8b949e">your commits</text>
|
|
19
|
+
<text x="360" y="22" font-weight="600" font-size="13.5" fill="#e6edf3">2. What plugs in</text>
|
|
20
|
+
<rect x="360" y="36" width="290" height="52" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
21
|
+
<text x="505" y="57" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">plan: read-only</text>
|
|
22
|
+
<text x="505" y="74" text-anchor="middle" font-size="11.5" fill="#8b949e">your tracker export is the plan</text>
|
|
23
|
+
<rect x="360" y="98" width="290" height="52" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
24
|
+
<text x="505" y="119" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">plan.commit</text>
|
|
25
|
+
<text x="505" y="136" text-anchor="middle" font-size="11.5" fill="#8b949e">plan commits in your format</text>
|
|
26
|
+
<rect x="360" y="160" width="290" height="52" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
27
|
+
<text x="505" y="181" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">docs.sync</text>
|
|
28
|
+
<text x="505" y="198" text-anchor="middle" font-size="11.5" fill="#8b949e">your docs kept in step</text>
|
|
29
|
+
<rect x="360" y="222" width="290" height="52" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
30
|
+
<text x="505" y="243" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">commit-msg hook</text>
|
|
31
|
+
<text x="505" y="260" text-anchor="middle" font-size="11.5" fill="#8b949e">checked before a wave runs</text>
|
|
32
|
+
<text x="700" y="22" font-weight="600" font-size="13.5" fill="#e6edf3">3. What cadence adds</text>
|
|
33
|
+
<rect x="700" y="36" width="290" height="100" rx="6" fill="#0c2d6b" stroke="#58a6ff" stroke-width="2"/>
|
|
34
|
+
<text x="845" y="76" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Orchestrated wave</text>
|
|
35
|
+
<text x="845" y="96" text-anchor="middle" font-size="11.5" fill="#c9d1d9">fresh sessions, review in the loop</text>
|
|
36
|
+
<rect x="700" y="148" width="290" height="100" rx="6" fill="#0c2d6b" stroke="#58a6ff" stroke-width="2"/>
|
|
37
|
+
<text x="845" y="188" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Deliver</text>
|
|
38
|
+
<text x="845" y="208" text-anchor="middle" font-size="11.5" fill="#c9d1d9">waits for CI, verifies the effect</text>
|
|
39
|
+
<polyline points="310,145 358,145" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arw)"/>
|
|
40
|
+
<polyline points="650,145 698,145" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arw)"/>
|
|
41
|
+
</svg>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1010 290" width="1010" height="290" role="img" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif" font-size="13" fill="#1f2328">
|
|
2
|
+
<title>cadence next to the tools you already have</title>
|
|
3
|
+
<desc>Your tools stay as they are: the tracker, HANDOFF.md, the ADRs and your commit convention. The plan can be read from your tracker's file, the plan commits follow your format and your docs are kept in step. On top, cadence adds orchestrated waves with an automatic review, and a deliver that verifies the effect.</desc>
|
|
4
|
+
<defs><marker id="arw" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#57606a"/></marker></defs>
|
|
5
|
+
<rect width="1010" height="290" fill="#ffffff"/>
|
|
6
|
+
<text x="20" y="22" font-weight="600" font-size="13.5" fill="#1f2328">1. Your tools (kept)</text>
|
|
7
|
+
<rect x="20" y="36" width="290" height="52" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
|
|
8
|
+
<text x="165" y="57" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Linear, Jira</text>
|
|
9
|
+
<text x="165" y="74" text-anchor="middle" font-size="11.5" fill="#57606a">tickets, board, assignment</text>
|
|
10
|
+
<rect x="20" y="98" width="290" height="52" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
|
|
11
|
+
<text x="165" y="119" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">HANDOFF.md</text>
|
|
12
|
+
<text x="165" y="136" text-anchor="middle" font-size="11.5" fill="#57606a">your hand-over notes</text>
|
|
13
|
+
<rect x="20" y="160" width="290" height="52" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
|
|
14
|
+
<text x="165" y="181" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">ADRs</text>
|
|
15
|
+
<text x="165" y="198" text-anchor="middle" font-size="11.5" fill="#57606a">your decisions</text>
|
|
16
|
+
<rect x="20" y="222" width="290" height="52" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
|
|
17
|
+
<text x="165" y="243" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Conventional Commits</text>
|
|
18
|
+
<text x="165" y="260" text-anchor="middle" font-size="11.5" fill="#57606a">your commits</text>
|
|
19
|
+
<text x="360" y="22" font-weight="600" font-size="13.5" fill="#1f2328">2. What plugs in</text>
|
|
20
|
+
<rect x="360" y="36" width="290" height="52" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
|
|
21
|
+
<text x="505" y="57" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">plan: read-only</text>
|
|
22
|
+
<text x="505" y="74" text-anchor="middle" font-size="11.5" fill="#57606a">your tracker export is the plan</text>
|
|
23
|
+
<rect x="360" y="98" width="290" height="52" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
|
|
24
|
+
<text x="505" y="119" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">plan.commit</text>
|
|
25
|
+
<text x="505" y="136" text-anchor="middle" font-size="11.5" fill="#57606a">plan commits in your format</text>
|
|
26
|
+
<rect x="360" y="160" width="290" height="52" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
|
|
27
|
+
<text x="505" y="181" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">docs.sync</text>
|
|
28
|
+
<text x="505" y="198" text-anchor="middle" font-size="11.5" fill="#57606a">your docs kept in step</text>
|
|
29
|
+
<rect x="360" y="222" width="290" height="52" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
|
|
30
|
+
<text x="505" y="243" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">commit-msg hook</text>
|
|
31
|
+
<text x="505" y="260" text-anchor="middle" font-size="11.5" fill="#57606a">checked before a wave runs</text>
|
|
32
|
+
<text x="700" y="22" font-weight="600" font-size="13.5" fill="#1f2328">3. What cadence adds</text>
|
|
33
|
+
<rect x="700" y="36" width="290" height="100" rx="6" fill="#ddf4ff" stroke="#0969da" stroke-width="2"/>
|
|
34
|
+
<text x="845" y="76" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Orchestrated wave</text>
|
|
35
|
+
<text x="845" y="96" text-anchor="middle" font-size="11.5" fill="#57606a">fresh sessions, review in the loop</text>
|
|
36
|
+
<rect x="700" y="148" width="290" height="100" rx="6" fill="#ddf4ff" stroke="#0969da" stroke-width="2"/>
|
|
37
|
+
<text x="845" y="188" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Deliver</text>
|
|
38
|
+
<text x="845" y="208" text-anchor="middle" font-size="11.5" fill="#57606a">waits for CI, verifies the effect</text>
|
|
39
|
+
<polyline points="310,145 358,145" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arw)"/>
|
|
40
|
+
<polyline points="650,145 698,145" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arw)"/>
|
|
41
|
+
</svg>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
[← cadence](../README.md)
|
|
2
|
+
|
|
3
|
+
# cadence with your tools
|
|
4
|
+
|
|
5
|
+
A team that already has a tracker (Linear, Jira), a `HANDOFF.md`, ADRs and a commit convention does not have to choose.
|
|
6
|
+
cadence is a few plain files next to the code and a set of commands; each part is optional, and the parts that touch your habits bend to them.
|
|
7
|
+
|
|
8
|
+
<picture>
|
|
9
|
+
<source media="(prefers-color-scheme: dark)" srcset="with-your-tools-dark.svg">
|
|
10
|
+
<img alt="Your tools stay: tracker, HANDOFF.md, ADRs, commit convention. The plan read from your tracker's file, the plan commits in your format and your docs kept in step plug in. cadence adds orchestrated waves with an automatic review, and a deliver that verifies the effect" src="with-your-tools-light.svg">
|
|
11
|
+
</picture>
|
|
12
|
+
|
|
13
|
+
## What cadence does not replace
|
|
14
|
+
|
|
15
|
+
| You have | cadence does not | What it keeps out of |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| A tracker (Linear, Jira, GitHub issues) | no board, no assignment, no sprint, no comments, no notifications: its plan is a list of *lots* in a file | the tracker stays where the team agrees what to do and who does it |
|
|
18
|
+
| `HANDOFF.md` | writes no hand-over document: `cadence session next "…"` keeps a one-line memo for the next `session start`, nothing more | keep your `HANDOFF.md`; cadence never reads or edits it |
|
|
19
|
+
| ADRs | records no decision: a lot's notes are working notes (what was tried, what is left) | decisions that must last stay in your ADRs |
|
|
20
|
+
| Conventional Commits (commitlint, husky) | does not impose a commit format: a commit belongs to a lot when it **cites the lot's id**, in the scope (`feat(ENG-12): …`) or anywhere in the message | your `commit-msg` hook keeps judging every commit, including the ones cadence writes (below) |
|
|
21
|
+
|
|
22
|
+
## What is optional
|
|
23
|
+
|
|
24
|
+
Nothing here needs the rest. Take one piece, leave the others:
|
|
25
|
+
|
|
26
|
+
- **`raf`, the plan** (`raf init`, `raf add`, `raf start`, `raf done`): only if you want the plan in the repository. A plan kept by another tool is read as it is, never written (next section).
|
|
27
|
+
- **`cadence session start` and `cadence session close`**: a morning and an evening report (lots in progress, commits without a lot, what is uncommitted or unpushed). Skip them if your stand-up or `HANDOFF.md` does that; they change nothing in the repository.
|
|
28
|
+
- **`raf hook install`**: warns when a commit cites a lot that is not started. It never blocks a commit unless you set `hook.autostart` to say so.
|
|
29
|
+
- **`cadence orchestrate` and `cadence deliver`**: a wave needs a plan it can read, a `raf` plan or a read-only one, and nothing else of the above; it works on a plan cadence never writes. `cadence deliver` is set by its own `deliver:` block and runs without a wave.
|
|
30
|
+
|
|
31
|
+
## What plugs into what you have
|
|
32
|
+
|
|
33
|
+
**A plan kept by another tool, read-only.** Point cadence at the file your tracker (or its export script) writes and map its keys. `raf list`, `raf now`, `raf commits`, `raf check`, `cadence session` and `cadence orchestrate` read it; `raf add|start|done|note` refuse and leave the write to your tool. The lot ids are yours (`ENG-12`), so your commits keep citing the ticket:
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
# tracker/tickets.yaml
|
|
37
|
+
tickets:
|
|
38
|
+
- key: ENG-12
|
|
39
|
+
summary: Export CSV
|
|
40
|
+
state: In Progress
|
|
41
|
+
points: 2
|
|
42
|
+
opened: 2026-10-20
|
|
43
|
+
- key: ENG-13
|
|
44
|
+
summary: Fix login redirect
|
|
45
|
+
state: Todo
|
|
46
|
+
points: 1
|
|
47
|
+
opened: 2026-10-21
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
# cadence.yaml, at the repository root
|
|
52
|
+
plan:
|
|
53
|
+
path: tracker/tickets.yaml
|
|
54
|
+
lots: tickets # the root key holding the list
|
|
55
|
+
fields: # cadence's field: the key in your file
|
|
56
|
+
id: key
|
|
57
|
+
title: summary
|
|
58
|
+
status: state
|
|
59
|
+
estimate: points
|
|
60
|
+
created: opened
|
|
61
|
+
statuses:
|
|
62
|
+
todo: [Todo]
|
|
63
|
+
doing: [In Progress]
|
|
64
|
+
done: [Done]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
All the keys (`add`, `files`, `ignore`, sub-tasks, `repos`…) are in [docs/raf.md](raf.md#a-plan-elsewhere-or-in-another-format).
|
|
68
|
+
|
|
69
|
+
**The subject of the plan commits.** A wave commits the plan itself (the lot started, the review recorded). By default they still read `plan: L12 démarré (orchestrate …)`, which a commitlint `config-conventional` hook refuses: `plan` is not one of its types (GitHub issue #15). Set `plan.commit` in `cadence.yaml` to change the subject of all of them, and `cadence orchestrate --dry-run` runs each one through your real `commit-msg` hook before the wave starts:
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
plan:
|
|
73
|
+
path: tracker/tickets.yaml
|
|
74
|
+
commit: 'chore({lot}): {action} (orchestrate {vague})'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`{lot}` is required, `{action}` and `{vague}` are optional; the full rules are in [docs/raf.md](raf.md#the-subject-of-the-plan-commits-plancommit-l343). The commits of the work itself are `type(<lot>): …` (`feat(ENG-12): …`), which is Conventional Commits already; a convention that wants something else is stated in the project's `CLAUDE.md`, which every session reads.
|
|
78
|
+
|
|
79
|
+
**Your existing docs.** `docs.sync` tells cadence which of your documents must change when some code changes, and `raf check` reports a lot that left them behind. It adds no document of its own: the README, the `docs/` pages and the CHANGELOG you already have are the ones it watches.
|
|
80
|
+
|
|
81
|
+
```yaml
|
|
82
|
+
docs:
|
|
83
|
+
sync:
|
|
84
|
+
- paths: [src/**, '!**/*.test.ts']
|
|
85
|
+
docs: [README.md, docs/usage.md]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
See [Documentation that follows the code](news.md#documentation-that-follows-the-code).
|
|
89
|
+
|
|
90
|
+
## What it adds
|
|
91
|
+
|
|
92
|
+
- **Orchestrated waves with an automatic review.** A lead picks the lots; each one goes to a fresh session that writes the tests first, commits and stops. A second, fresh session then reviews the commits against the lot and its notes before the lot can be closed. A red test suite or a missing document is sent back for correction, not found in the pull request. [docs/orchestrate.md](orchestrate.md), [docs/quickstart.md](quickstart.md).
|
|
93
|
+
- **A deliver that verifies the effect.** `cadence deliver` waits for the CI of the pushed commit, runs your deploy commands, then replays the checks you declared (a URL that answers, a version that matches, a command that succeeds). A delivery is done when those checks pass, not when the pipeline says "success". [docs/deliver.md](deliver.md).
|
|
94
|
+
|
|
95
|
+
Start small: a read-only plan and `plan.commit` are enough to see a wave run in a repository that keeps its tracker, its `HANDOFF.md`, its ADRs and its commit convention.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylad/cadence",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.1",
|
|
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",
|
|
@@ -44,6 +44,12 @@
|
|
|
44
44
|
"docs/fonctionnement-lead-dark.svg",
|
|
45
45
|
"docs/lead-workflow-light.svg",
|
|
46
46
|
"docs/lead-workflow-dark.svg",
|
|
47
|
+
"docs/quickstart.md",
|
|
48
|
+
"docs/quickstart-light.svg",
|
|
49
|
+
"docs/quickstart-dark.svg",
|
|
50
|
+
"docs/with-your-tools.md",
|
|
51
|
+
"docs/with-your-tools-light.svg",
|
|
52
|
+
"docs/with-your-tools-dark.svg",
|
|
47
53
|
"docs/cycle-vague-light.svg",
|
|
48
54
|
"docs/cycle-vague-dark.svg",
|
|
49
55
|
"LICENSE"
|