@sylad/cadence 0.28.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@
8
8
  {
9
9
  "name": "cadence",
10
10
  "description": "Session start and close rituals driven by a versioned plan (raf), and deliveries proven by their effect. Needs the cadence CLI (npm i -g @sylad/cadence).",
11
- "version": "0.28.0",
11
+ "version": "1.0.0",
12
12
  "source": "./",
13
13
  "author": {
14
14
  "name": "Sylvain Ladoire"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cadence",
3
3
  "description": "A repo-native working method: session start and close rituals driven by a versioned plan (raf), deliveries proven by their effect, and three reviewer agents (UX, code, QA).",
4
- "version": "0.28.0",
4
+ "version": "1.0.0",
5
5
  "author": { "name": "Sylvain Ladoire" },
6
6
  "homepage": "https://github.com/Sylad/cadence",
7
7
  "repository": "https://github.com/Sylad/cadence",
package/README.md CHANGED
@@ -24,7 +24,7 @@ Code prompt that shows the session's context, quota, cost and the orchestrate wa
24
24
 
25
25
  ## What's new
26
26
 
27
- **0.28.0**: `orchestrate.precheck: local` runs the « deliverable already present? » pre-check of `cadence orchestrate` on the local model (`claude-local`, Ollama), off the Anthropic quota, Sonnet staying the default and the fallback (L146); the `qa-reviewer` agent walks only the pages a lot touched, with a **Scope** line in its report (L123); the sub-tasks a review closes are also read in a lot's neighbouring repositories (L156) and a commit's sub-task list is read with the plan's left guard (L155); an unstable test is fixed (L90). **0.27.1**: `cadence orchestrate` no longer leaves a session's child processes alive: a dev server started in its own process group, or orphaned by a shell that exits (`nohup srv &`), is killed with the session through its process tree and its `CADENCE_SESSION` mark, a tmux server, `screen`, `gpg-agent` or `dirmngr` started on demand being spared; a session that exits with code 0 while a descendant holds its output open is no longer reported as « délai dépassé » (L83); the `ux-reviewer` and `qa-reviewer` agents carry the briefs' Playwright rule outside `cadence orchestrate` (L84). **0.27.0**: a compliant review of `cadence orchestrate` settles the open sub-tasks that commits of the lot cite (`feat(L3/t2): …`, comma lists included), closed in the same plan commit as the verdict or, on a read-only plan, returned as `[sous-tâche clore]` proposals, so `raf done` no longer refuses a lot over sub-tasks whose work is done; on a foreign-format plan the ids are read as the plan reads them (L82); the test suite stays green with two full suites running in parallel (L150). **0.26.0**: `cadence orchestrate` raises the floor of a lot's budget from 200 k to 450 k tokens, enough to pay a write pass, its review, one fix pass and the short review that follows, so a lot of 0.5 or 1 day is no longer handed back at its second or third pass; a `--continue` wave draws fewer lots as a result (L128). **0.25.0**: the project line of the plans' progress in the `cadence-hud` band names the lots in progress after their count, three at most then `…` (L153, `cadence-hud` 0.4.2); the progress follows the folder the session was launched from, so a `cd` into a sub-project no longer shrinks the band, and the session context keeps the `session.start` folder (L154). **0.24.0**: `cadence orchestrate --drop <project:lot>` and `--stop-after-current` steer a live wave from another terminal, `--resume --drop` removes a lot before replaying a stopped wave (L79); the final table of a wave says « livrable jusqu'à <sha> » for a ready lot stacked under a lot handed back, with `git push origin <sha>:main && cadence deliver --sha <sha>` (L76); a version commit (version fields, `CHANGELOG.md`, the README's « What's new ») no longer asks for a new code review (L72). **0.23.0**: the progress bar of each project in the `cadence-hud` band is coloured by the share of lots done, red below 33 %, orange below 66 %, green beyond (L152, `cadence-hud` 0.4.1); `cadence deliver` under a wave lock refuses only when a lot of the wave still works in that very repository, a linked worktree included (L127). **0.22.0**: `cadence orchestrate --continue` draws the next ready lot of the plan itself, in the declared priority, until the budget, the time window or a question stops it (L147); `cadence session context` prints the context of the session, which the `cadence-hud` band publishes, and the `lead` skill chains lots without the human while it stays under 60 % (L148); the band shows the progress of each project's plan, and `cadence lead tour --json` gains `progress` (L149, `cadence-hud` 0.4.0); the final review of a lot is always played, the lot's own budget bounding the writing passes only, with 65 k tokens reserved for it (L145); the cause « tests rouges après … » names the pass whose tests were red (L151). **0.21.0**: `cadence orchestrate --status` and `cadence lead tour` print the free session slots, which the `lead` skill reads (L141); `orchestrate.effort` sets the effort level of each pass of a wave (L137); `docs.sync` makes `raf check`, `lead tour` and the review brief report a document that does not follow the code (L143); `docs.articles` opens, after a green `cadence deliver`, a lot `Article <project> à rafraîchir` in the neighbouring repository (L144). **0.20.0**: `cadence-hud` no longer misleads once the work is over: `/hud cmd` lists the background commands it counts (id, tool, origin, age, name), any other argument answers `argument inconnu` instead of hiding the band, commands with no end seen for an hour or no record go to a grey `N cmd sans fin vue`, a finished subagent takes its commands with it, two races that left a `1 cmd` counted forever are closed, and a finished wave shows grey without its budget then disappears after 30 minutes (L142); the `qa-reviewer` and `ux-reviewer` agents are read-only for real, their frontmatter listing Read, Grep, Glob, Bash and the Playwright tools (L140). **0.19.1**: `cadence-hud`'s `N cmd` counter goes back down when a Monitor expires, when the background commands a subagent started end with it, and at session start or plugin reload (L135); its `tool.call` and `prompt.submit` guard hooks carry a `.catch` on registration, which clears the `claude plugin validate` warning (L136). **0.19.0**: `cadence orchestrate` lifts a repository's lock and `pre-push` guard as soon as every lot of the wave that touches it is finished, so `cadence deliver` works there while the wave continues elsewhere (L132); a review that leaves a repository dirty hands back that lot only, suspends the lots still queued in that repository and keeps the other repositories running, its verdict reported instead of lost (L133); `cadence session close` re-run without a `session start` keeps the window since the last opening (L131); `cadence-hud` draws the lot rows of a wave in aligned columns on every surface (L134). **0.18.0**: `cadence session next` prints what it records (issue #6); `cadence session close` covers the period since the last `session start` (or the last close) and says so in its title, `--since` still wins (issue #7). **0.17.0**: `cadence-hud` shows an `N cmd` segment next to the agents, the background commands of the session (Bash run in the background and Monitor, subagents' included), tracked from the tool results and ended by the task's notification or `TaskStop`. **0.16.0**: a lot can declare **neighbouring repositories** (`repos:` key of the lot, `{ path, cite }` for a repository shared between projects): `cadence orchestrate` refuses, locks and guards them, gives them to every session as `--add-dir`, and `raf commits`, `raf show` and the review verdict read the commits that cite the lot there too. **0.15.0**: `hook.autostart` in `cadence.yaml` (`warn` by default, `refuse` or `start`): a pre-commit hook that refuses, or starts in the same commit, a lot still todo that the commit cites; `cadence session start|close --all [--depth n]`, one section per project under a folder. **0.14.0**: `cadence lead tour`, the lead's morning table of every project in a folder, printed by a program (one line per project: lots in progress, gaps, last notes, next ready lot, repository) in place of one subagent per project; the `lead` skill calls it. **0.13.1**: `cadence-hud` takes back the segments that fit once a big one has dropped (reset times, then the per-model consumption, now short: `opus 1.2M sonnet 800k`); `CADENCE_HUD_AMBIGUOUS=1` for a terminal that draws `▰ ▱ ⚙ │ ↻` in one cell. **0.13.0**: `cadence orchestrate --status --watch` (a wave's table redrawn in the terminal, which
27
+ **1.0.0**: the first major release, a stable contract: the commands and options, the keys of `cadence.yaml`, the plan file and the names of the skills and agents follow semantic versioning, and the README's *Stability* section lists what is guaranteed and what is not (L164). **0.28.0**: `orchestrate.precheck: local` runs the « deliverable already present? » pre-check of `cadence orchestrate` on the local model (`claude-local`, Ollama), off the Anthropic quota, Sonnet staying the default and the fallback (L146); the `qa-reviewer` agent walks only the pages a lot touched, with a **Scope** line in its report (L123); the sub-tasks a review closes are also read in a lot's neighbouring repositories (L156) and a commit's sub-task list is read with the plan's left guard (L155); an unstable test is fixed (L90). **0.27.1**: `cadence orchestrate` no longer leaves a session's child processes alive: a dev server started in its own process group, or orphaned by a shell that exits (`nohup srv &`), is killed with the session through its process tree and its `CADENCE_SESSION` mark, a tmux server, `screen`, `gpg-agent` or `dirmngr` started on demand being spared; a session that exits with code 0 while a descendant holds its output open is no longer reported as « délai dépassé » (L83); the `ux-reviewer` and `qa-reviewer` agents carry the briefs' Playwright rule outside `cadence orchestrate` (L84). **0.27.0**: a compliant review of `cadence orchestrate` settles the open sub-tasks that commits of the lot cite (`feat(L3/t2): …`, comma lists included), closed in the same plan commit as the verdict or, on a read-only plan, returned as `[sous-tâche clore]` proposals, so `raf done` no longer refuses a lot over sub-tasks whose work is done; on a foreign-format plan the ids are read as the plan reads them (L82); the test suite stays green with two full suites running in parallel (L150). **0.26.0**: `cadence orchestrate` raises the floor of a lot's budget from 200 k to 450 k tokens, enough to pay a write pass, its review, one fix pass and the short review that follows, so a lot of 0.5 or 1 day is no longer handed back at its second or third pass; a `--continue` wave draws fewer lots as a result (L128). **0.25.0**: the project line of the plans' progress in the `cadence-hud` band names the lots in progress after their count, three at most then `…` (L153, `cadence-hud` 0.4.2); the progress follows the folder the session was launched from, so a `cd` into a sub-project no longer shrinks the band, and the session context keeps the `session.start` folder (L154). **0.24.0**: `cadence orchestrate --drop <project:lot>` and `--stop-after-current` steer a live wave from another terminal, `--resume --drop` removes a lot before replaying a stopped wave (L79); the final table of a wave says « livrable jusqu'à <sha> » for a ready lot stacked under a lot handed back, with `git push origin <sha>:main && cadence deliver --sha <sha>` (L76); a version commit (version fields, `CHANGELOG.md`, the README's « What's new ») no longer asks for a new code review (L72). **0.23.0**: the progress bar of each project in the `cadence-hud` band is coloured by the share of lots done, red below 33 %, orange below 66 %, green beyond (L152, `cadence-hud` 0.4.1); `cadence deliver` under a wave lock refuses only when a lot of the wave still works in that very repository, a linked worktree included (L127). **0.22.0**: `cadence orchestrate --continue` draws the next ready lot of the plan itself, in the declared priority, until the budget, the time window or a question stops it (L147); `cadence session context` prints the context of the session, which the `cadence-hud` band publishes, and the `lead` skill chains lots without the human while it stays under 60 % (L148); the band shows the progress of each project's plan, and `cadence lead tour --json` gains `progress` (L149, `cadence-hud` 0.4.0); the final review of a lot is always played, the lot's own budget bounding the writing passes only, with 65 k tokens reserved for it (L145); the cause « tests rouges après … » names the pass whose tests were red (L151). **0.21.0**: `cadence orchestrate --status` and `cadence lead tour` print the free session slots, which the `lead` skill reads (L141); `orchestrate.effort` sets the effort level of each pass of a wave (L137); `docs.sync` makes `raf check`, `lead tour` and the review brief report a document that does not follow the code (L143); `docs.articles` opens, after a green `cadence deliver`, a lot `Article <project> à rafraîchir` in the neighbouring repository (L144). **0.20.0**: `cadence-hud` no longer misleads once the work is over: `/hud cmd` lists the background commands it counts (id, tool, origin, age, name), any other argument answers `argument inconnu` instead of hiding the band, commands with no end seen for an hour or no record go to a grey `N cmd sans fin vue`, a finished subagent takes its commands with it, two races that left a `1 cmd` counted forever are closed, and a finished wave shows grey without its budget then disappears after 30 minutes (L142); the `qa-reviewer` and `ux-reviewer` agents are read-only for real, their frontmatter listing Read, Grep, Glob, Bash and the Playwright tools (L140). **0.19.1**: `cadence-hud`'s `N cmd` counter goes back down when a Monitor expires, when the background commands a subagent started end with it, and at session start or plugin reload (L135); its `tool.call` and `prompt.submit` guard hooks carry a `.catch` on registration, which clears the `claude plugin validate` warning (L136). **0.19.0**: `cadence orchestrate` lifts a repository's lock and `pre-push` guard as soon as every lot of the wave that touches it is finished, so `cadence deliver` works there while the wave continues elsewhere (L132); a review that leaves a repository dirty hands back that lot only, suspends the lots still queued in that repository and keeps the other repositories running, its verdict reported instead of lost (L133); `cadence session close` re-run without a `session start` keeps the window since the last opening (L131); `cadence-hud` draws the lot rows of a wave in aligned columns on every surface (L134). **0.18.0**: `cadence session next` prints what it records (issue #6); `cadence session close` covers the period since the last `session start` (or the last close) and says so in its title, `--since` still wins (issue #7). **0.17.0**: `cadence-hud` shows an `N cmd` segment next to the agents, the background commands of the session (Bash run in the background and Monitor, subagents' included), tracked from the tool results and ended by the task's notification or `TaskStop`. **0.16.0**: a lot can declare **neighbouring repositories** (`repos:` key of the lot, `{ path, cite }` for a repository shared between projects): `cadence orchestrate` refuses, locks and guards them, gives them to every session as `--add-dir`, and `raf commits`, `raf show` and the review verdict read the commits that cite the lot there too. **0.15.0**: `hook.autostart` in `cadence.yaml` (`warn` by default, `refuse` or `start`): a pre-commit hook that refuses, or starts in the same commit, a lot still todo that the commit cites; `cadence session start|close --all [--depth n]`, one section per project under a folder. **0.14.0**: `cadence lead tour`, the lead's morning table of every project in a folder, printed by a program (one line per project: lots in progress, gaps, last notes, next ready lot, repository) in place of one subagent per project; the `lead` skill calls it. **0.13.1**: `cadence-hud` takes back the segments that fit once a big one has dropped (reset times, then the per-model consumption, now short: `opus 1.2M sonnet 800k`); `CADENCE_HUD_AMBIGUOUS=1` for a terminal that draws `▰ ▱ ⚙ │ ↻` in one cell. **0.13.0**: `cadence orchestrate --status --watch` (a wave's table redrawn in the terminal, which
28
28
  stops with the wave) and the lead skill that names the wave and follows its journal; lot ids with `/`
29
29
  (sub-tasks of a read-only plan) accepted by `orchestrate`; `cadence-hud`'s first line measured in
30
30
  terminal cells and kept to ASCII; `publish.yml` on the v5 actions. **0.12.0**: the `cadence-hud` plugin (a status band above the Claude Code prompt),
@@ -1083,8 +1083,7 @@ a time (above); `Agent`, `git push`, `cadence deliver`, `raf done|review|ux` are
1083
1083
  temporary `pre-push` hook, installed for the duration of the wave and removed at its end (or, for a repository, as soon as all the lots that touch it are finished), refuses any push
1084
1084
  from a session (`CADENCE_ORCHESTRATED` is in their environment; a repository that already has another
1085
1085
  `pre-push` hook is refused before anything starts — `pushurl` is never touched); after every session the
1086
- upstream ref and `git ls-remote` are compared with the "before", and a review that changed `HEAD` or the
1087
- tree (screenshots left at the root, a commit) is an incident of that lot only: the lot is handed back to the lead, the lots of the other repositories carry on while the lots still queued behind it in the same repository are suspended (resumable, nothing started on a dirty tree), and a verdict the review had already produced is reported in the outcome and the lot's warnings (never recorded in the plan). A push or a removed guard hook still stops the wave. `raf done|ux|review` and `cadence deliver` refuse when
1086
+ upstream ref and `git ls-remote` are compared with the "before", and a read-only step that left only UNTRACKED files (screenshots written at the root by a script with a relative path) is not an incident: the files are moved to `.cadence/runs/<wave>/<project>--<lot>/stray/` (kept in their tree; for a neighbour repository, under `stray/<its relative path, `/` replaced by `_`>/`, e.g. `stray/.._gitops/`), a warning naming them goes to the lot's warnings and the wave log, the verdict is kept and the lot continues (L162; a move that is impossible, or an untracked file that vanished during the step, stays an incident); a review that changed `HEAD` or a tracked file (a commit, a modified file) is an incident of that lot only: the lot is handed back to the lead, the lots of the other repositories carry on while the lots still queued behind it in the same repository are suspended (resumable, nothing started on a dirty tree), and a verdict the review had already produced is reported in the outcome and the lot's warnings (never recorded in the plan). A push or a removed guard hook still stops the wave. `raf done|ux|review` and `cadence deliver` refuse when
1088
1087
  `CADENCE_ORCHESTRATED` is set.
1089
1088
 
1090
1089
  **Budget**: the wave counts input + cache writes + output tokens (default 2 M); cache reads are kept and
@@ -1375,13 +1374,15 @@ A version exists in three places and is published in two; a release does all of
1375
1374
  in that commit, not before the review. A commit that touches any other file
1376
1375
  — or another field of a manifest, or a dependency in `package-lock.json` — is work like any other and has
1377
1376
  to be reviewed.
1378
- 2. `git tag v<version> && git push origin main v<version>` — the tag starts `.github/workflows/publish.yml`,
1377
+ 2. Run the plugin evaluation, `npm run eval:plugin` (billed, see [Evaluating the plugin](#evaluating-the-plugin-before-a-release)),
1378
+ on that version commit; a case below the threshold stops the release until you have read it in the report.
1379
+ 3. `git tag v<version> && git push origin main v<version>` — the tag starts `.github/workflows/publish.yml`,
1379
1380
  which publishes to npm through Trusted Publishing (OIDC, no token stored anywhere): it checks the tag
1380
1381
  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
1381
1382
  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
1382
1383
  once on npmjs.com (package settings → Trusted Publisher → GitHub Actions, `Sylad/cadence`, `publish.yml`).
1383
- 3. Watch the run: `gh run watch` (or `gh run list --workflow publish.yml`).
1384
- 4. Check the effect: `npm view @sylad/cadence version` answers the new version.
1384
+ 4. Watch the run: `gh run watch` (or `gh run list --workflow publish.yml`).
1385
+ 5. Check the effect: `npm view @sylad/cadence version` answers the new version.
1385
1386
  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):
1386
1387
  a version already on npm skips `npm publish`, and a GitHub release that is missing is created (`--verify-tag`)
1387
1388
  while an existing one is left alone. If the package is on npm but the release is still missing, re-run the job;
@@ -1390,7 +1391,61 @@ A version exists in three places and is published in two; a release does all of
1390
1391
  The Claude Code plugin is read from the repository, so pushing `main` is what updates it; npm is what
1391
1392
  `npx @sylad/cadence` and a global install read, and only the tag publishes there. A missing tag, or a red
1392
1393
  publish run, leaves npm behind without any other error — 0.3.0 and 0.4.0 were never published — hence
1393
- step 4.
1394
+ step 5.
1395
+
1396
+ ### Evaluating the plugin (before a release)
1397
+
1398
+ `evals/` holds an evaluation suite for the plugin itself, run by `claude plugin eval` (Claude Code 2.1.263 and later). Each case is
1399
+ a folder with a `prompt.md` and `graders/*.md`, and replays a trap found by hand: `raf-done-sans-revue` (`raf done` refused on a lot whose
1400
+ commits have no code review, with no `--force` to get round it), `deliver-sha` (`git push origin <sha>:main` then
1401
+ `cadence deliver --sha <sha>` for a pushed commit that is not `HEAD`), `orchestrate-id-avec-slash` (`maritime-atlas:Q4/accueil-4-ux12@haiku`
1402
+ keeps its `/`) and `livrer-sh-sans-bloc` (a `./livrer.sh` without a `deliver:` block in `cadence.yaml` is not
1403
+ run by `cadence deliver`). Every case also runs a **baseline without the plugin**, so the report gives the score of each arm and the
1404
+ difference: a trap the baseline already avoids proves nothing about the plugin. The cases are read-only questions (tools `Read`, `Glob`, `Grep`,
1405
+ `Skill`, no scaffold script), each graded by a regular expression and by a model-judged criterion.
1406
+
1407
+ ```sh
1408
+ npm run eval:plugin # claude plugin eval . --max-cost-usd 5 --no-publish; 4 cases × 3 runs × 2 arms = 24 runs
1409
+ claude plugin eval . --case deliver-sha --runs 1 --trust-plugin --no-publish --max-cost-usd 1 # one case, one run (the first run asks to trust the plugin)
1410
+ ```
1411
+
1412
+ **Every run is billed** (a full `claude` session on your own credential, plus the judge): the suite is run at the release, once the
1413
+ version commit is ready, never in `npm test`, in `prepublishOnly` or in CI, and `--max-cost-usd 5` aborts a runaway. A case below
1414
+ the threshold (1.0 by default, `--threshold`) makes the command exit 1: read the case in the report before deciding it is the plugin
1415
+ and not the wording of the case. `npm test` only checks that the
1416
+ files are well formed (`test/plugin-evals.test.ts`); `evals/` is not part of the npm package.
1417
+
1418
+ ## Stability
1419
+
1420
+ From 1.0.0 cadence follows [semantic versioning](https://semver.org): what is listed under
1421
+ *Guaranteed* only changes incompatibly in a major version; a minor or a patch never breaks it.
1422
+
1423
+ **Guaranteed**
1424
+
1425
+ - **Commands and options** of `raf` and `cadence` (`cadence raf …` is `raf …`): `init`, `add`, `start`, `done`, `drop`,
1426
+ `note`, `did`, `public`, `ux`, `review`, `commits`, `show`, `list`, `now`, `ignore`, `check`, `gantt`, `hook`;
1427
+ `cadence news`, `session` (`start`, `close`, `next`, `context`), `lead tour`, `deliver`, `verify`, `orchestrate`,
1428
+ `skills`; their options and exit codes as this README documents them. A new command or option is a minor change.
1429
+ - **The keys of `cadence.yaml`**: `plan`, `news`, `hook`, `session`, `deliver`, `orchestrate`, `docs`, `qa`, `priority`
1430
+ and the keys each of them takes. A key is not renamed nor removed, nor does its meaning change; a new key is a minor change.
1431
+ - **The plan file** (`docs/plan/raf.yaml`): `project`, `prefix`, `since`, `ignore`, `lots` (with their
1432
+ fields, `tasks`, `notes`, `repos`) and `acknowledged`, and the plan formats `plan:` in `cadence.yaml` can read.
1433
+ The `version` key is kept in the file but ignored (`src/plan.ts` does not read it): it is not a schema version.
1434
+ - **The names of the skills** (`session-start`, `session-close`, `deliver`, `lead`) **and of the agents**
1435
+ (`ux-reviewer`, `code-reviewer`, `qa-reviewer`).
1436
+
1437
+ **Not guaranteed** (may change in any release)
1438
+
1439
+ - The **text of the outputs**: wording, layout, order of the lines and columns of every report, table and message
1440
+ (the `--json` outputs and the exit codes are part of the commands above). In a `--json` output a new field is a
1441
+ minor change, so consumers must tolerate unknown fields; removing or renaming a field is a major change.
1442
+ - The **`precheck-reader`** agent: internal to `cadence orchestrate`, not meant to be called by hand.
1443
+ - The **`cadence-hud` band**: its segments, colours and layout; it is versioned on its own.
1444
+ - The **wave journals** under `.cadence/runs` and the files `cadence orchestrate` keeps there.
1445
+ - The wording of the briefs and of the instructions inside the skills and the agents.
1446
+
1447
+ There is no schema version and no migration: a file written by an earlier release keeps working, and a key a
1448
+ release adds is optional.
1394
1449
 
1395
1450
  ## License
1396
1451
 
@@ -2,7 +2,7 @@ import { execFileSync, spawn } from 'node:child_process';
2
2
  import { toolBinOf, withoutLaunchVars } from './snapshot.js';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { dirname, join, relative } from 'node:path';
5
- import { writeFileSync } from 'node:fs';
5
+ import { copyFileSync, existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
6
6
  import { coveredTasks, lotCommits, lotWork } from '../audit.js';
7
7
  import { readDocsConfig } from '../config.js';
8
8
  import { docSyncBrief, docSyncGaps, filesOf } from '../docsync.js';
@@ -447,20 +447,91 @@ async function session(c, kind, local = false) {
447
447
  w.saveWave();
448
448
  return stop(c, 'failed', `incident : ${w.incident}`);
449
449
  }
450
+ // Étape en lecture seule qui n'a laissé que des fichiers NON SUIVIS (captures d'un script en chemin relatif, L162) : déplacés dans le dossier du lot, avertissement, le verdict est gardé.
451
+ if (!write && b.head === a.head && b.tracked.join() === a.tracked.join() && b.untracked.join() !== a.untracked.join() && b.untracked.every((f) => a.untracked.includes(f))) {
452
+ const fresh = a.untracked.filter((f) => !b.untracked.includes(f)).map(unquotePath);
453
+ const stray = join(dir, 'stray');
454
+ const to = where ? join(stray, relative(l.repo, path).replace(/[\\/]/g, '_')) : stray;
455
+ const moved = moveStray(path, fresh, to);
456
+ if (moved.length) {
457
+ const text = `${kind} : ${moved.length} fichier(s) non suivi(s) laissé(s)${where} par une étape en lecture seule, déplacé(s) dans ${to} : ${moved.join(', ')}`;
458
+ c.lot.warnings.push(text);
459
+ w.log(`${lotKey(l.project, l.lot)} · ${text}`);
460
+ }
461
+ if (moved.length === fresh.length)
462
+ continue;
463
+ // Déplacement partiel : l'incident ne nomme que ce qui reste dans le dépôt.
464
+ return handBack(c, kind, res.structured, step, where, path, b, a, fresh.filter((f) => !moved.includes(f)));
465
+ }
450
466
  if (!write && (b.head !== a.head || b.tracked.join() !== a.tracked.join() || b.untracked.join() !== a.untracked.join())) {
451
- // Incident du LOT (L133) : la revue a laissé des traces (captures, commit), ni push ni garde supprimée — les lots des autres dépôts continuent.
452
- step.status = 'failed';
453
- step.cause = `le dépôt${where} a changé pendant une revue`;
454
- const traces = [...trackedPaths(a).filter((f) => !trackedPaths(b).includes(f)), ...a.untracked.filter((f) => !b.untracked.includes(f))];
455
- (w.dirtyRepos ??= new Map()).set(path, lotKey(l.project, l.lot));
456
- const what = traces.length ? ` (${traces.join(', ')})` : b.head !== a.head ? ' (commit)' : '';
457
- return stop(c, 'handed-back', `incident : ${kind} de ${lotKey(l.project, l.lot)} a modifié le dépôt${where}${what}${lostVerdict(c, kind, res.structured)}`);
467
+ return handBack(c, kind, res.structured, step, where, path, b, a);
458
468
  }
459
469
  }
460
470
  step.status = 'ok';
461
471
  save(c);
462
472
  return { step, report: res.structured, before, after, others };
463
473
  }
474
+ /** Incident du LOT (L133) : la revue a laissé des traces (captures, commit), ni push ni garde supprimée — les lots des autres dépôts continuent. */
475
+ function handBack(c, kind, report, step, where, path, b, a, left) {
476
+ const l = c.lot;
477
+ const w = c.wave;
478
+ step.status = 'failed';
479
+ step.cause = `le dépôt${where} a changé pendant une revue`;
480
+ const traces = left ?? [...trackedPaths(a).filter((f) => !trackedPaths(b).includes(f)), ...a.untracked.filter((f) => !b.untracked.includes(f))];
481
+ (w.dirtyRepos ??= new Map()).set(path, lotKey(l.project, l.lot));
482
+ const what = traces.length ? ` (${traces.join(', ')})` : b.head !== a.head ? ' (commit)' : '';
483
+ return stop(c, 'handed-back', `incident : ${kind} de ${lotKey(l.project, l.lot)} a modifié le dépôt${where}${what}${lostVerdict(c, kind, report)}`);
484
+ }
485
+ /** Décode un chemin de `git status --porcelain` : entre guillemets, avec les échappements du C-quoting (`\303\251`, `\"`, `\\`, `\t`…). */
486
+ function unquotePath(f) {
487
+ if (!/^".*"$/s.test(f))
488
+ return f;
489
+ const bytes = [];
490
+ const esc = { a: 7, b: 8, t: 9, n: 10, v: 11, f: 12, r: 13 };
491
+ const body = f.slice(1, -1);
492
+ for (let i = 0; i < body.length; i++) {
493
+ const ch = body[i];
494
+ if (ch !== '\\') {
495
+ bytes.push(...Buffer.from(ch));
496
+ continue;
497
+ }
498
+ const oct = /^[0-7]{3}/.exec(body.slice(i + 1));
499
+ if (oct) {
500
+ bytes.push(parseInt(oct[0], 8));
501
+ i += 3;
502
+ }
503
+ else {
504
+ const n = body[++i];
505
+ bytes.push(...(n in esc ? [esc[n]] : Buffer.from(n)));
506
+ }
507
+ }
508
+ return Buffer.from(bytes).toString('utf8');
509
+ }
510
+ /** Déplace des fichiers non suivis (chemins relatifs au dépôt, décodés) sous `to`, arborescence gardée ; renvoie ceux qui l'ont été (s'arrête au premier échec). */
511
+ function moveStray(repo, files, to) {
512
+ const moved = [];
513
+ for (const rel of files) {
514
+ try {
515
+ const from = join(repo, rel);
516
+ let dest = join(to, rel);
517
+ for (let i = 1; existsSync(dest); i++)
518
+ dest = join(to, `${rel}.${i}`);
519
+ mkdirSync(dirname(dest), { recursive: true });
520
+ try {
521
+ renameSync(from, dest);
522
+ }
523
+ catch {
524
+ copyFileSync(from, dest); // autre volume
525
+ rmSync(from);
526
+ }
527
+ moved.push(rel);
528
+ }
529
+ catch {
530
+ break;
531
+ }
532
+ }
533
+ return moved;
534
+ }
464
535
  /** Verdict d'une revue interrompue par un incident du lot : rapporté au lead plutôt que perdu (L133). Rien n'est enregistré dans le plan. */
465
536
  function lostVerdict(c, kind, report) {
466
537
  if (kind === 'precheck')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sylad/cadence",
3
- "version": "0.28.0",
3
+ "version": "1.0.0",
4
4
  "description": "A small, repo-native working method: a versioned plan linked to your commits, a changelog with screenshots, session rituals and deliveries proven by their effect.",
5
5
  "license": "MIT",
6
6
  "author": "Sylvain Ladoire",
@@ -26,6 +26,7 @@
26
26
  "build": "tsc -p tsconfig.build.json",
27
27
  "test": "vitest run",
28
28
  "typecheck": "tsc --noEmit",
29
+ "eval:plugin": "claude plugin eval . --max-cost-usd 5 --no-publish",
29
30
  "prepare": "npm run build",
30
31
  "prepublishOnly": "npm run typecheck && npm test"
31
32
  },