@sylad/cadence 1.12.1 → 1.14.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 +3 -0
- package/agents/reference-planner.md +26 -0
- package/bin/cadence.js +8 -1
- package/dist/cli.js +197 -22
- package/dist/config-set.js +252 -0
- package/dist/config.js +49 -9
- package/dist/corrections.js +2 -0
- package/dist/git.js +42 -1
- package/dist/lead.js +5 -1
- package/dist/orchestrate/accept.js +63 -1
- package/dist/orchestrate/baseline.js +39 -3
- package/dist/orchestrate/briefs.js +1 -1
- package/dist/orchestrate/changelog-merge.js +40 -1
- package/dist/orchestrate/command.js +120 -19
- package/dist/orchestrate/continue.js +5 -2
- package/dist/orchestrate/cycle.js +318 -74
- package/dist/orchestrate/decisions.js +33 -0
- package/dist/orchestrate/guard.js +16 -0
- package/dist/orchestrate/integrate.js +26 -3
- package/dist/orchestrate/launch.js +4 -1
- package/dist/orchestrate/state.js +1 -1
- package/dist/orchestrate/workers.js +71 -0
- package/dist/orchestrate/worktree.js +15 -4
- package/dist/perf.js +12 -2
- package/dist/plan-commit.js +88 -0
- package/dist/plan.js +41 -3
- package/dist/ref/analyse.js +34 -4
- package/dist/ref/command.js +2 -1
- package/dist/ref/coverage.js +136 -0
- package/dist/ref/diff.js +99 -15
- package/dist/release.js +249 -0
- package/dist/session.js +7 -1
- package/dist/stale-notes.js +22 -0
- package/dist/verify-gate.js +138 -0
- package/docs/deliver.md +37 -0
- package/docs/fonctionnement.md +3 -1
- package/docs/lead.md +18 -1
- package/docs/orchestrate.md +71 -13
- package/docs/overview.md +5 -2
- package/docs/perf.md +4 -1
- package/docs/raf.md +49 -1
- package/docs/ref.md +61 -3
- package/docs/releasing.md +43 -2
- package/docs/session.md +8 -0
- package/docs/skills.md +1 -0
- package/docs/spec.md +2 -0
- package/docs/stabilite.md +2 -0
- package/docs/whats-new.md +9 -0
- package/package.json +3 -3
- package/skills/lead/SKILL.md +6 -4
- package/skills/orchestrate/SKILL.md +1 -1
- package/skills/session-close/SKILL.md +2 -0
- package/skills/session-start/SKILL.md +2 -1
- package/templates/orchestrate/fix-docs.md +1 -0
- package/templates/orchestrate/fix-minors.md +1 -0
- package/templates/orchestrate/fix.md +2 -0
- package/templates/orchestrate/implement.md +4 -0
|
@@ -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.14.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.14.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,6 +7,8 @@ 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)).
|
|
11
|
+
|
|
10
12
|
```sh
|
|
11
13
|
npm install -g @sylad/cadence
|
|
12
14
|
raf init
|
|
@@ -36,6 +38,7 @@ Details: [docs/skills.md](docs/skills.md) and [docs/cadence-hud.md](docs/cadence
|
|
|
36
38
|
- [docs/orchestrate.md](docs/orchestrate.md): waves of sessions and their budgets.
|
|
37
39
|
- [docs/deliver.md](docs/deliver.md): wait for the CI, deploy, verify the effect.
|
|
38
40
|
- [docs/spec.md](docs/spec.md): screen and back-end sheets.
|
|
41
|
+
- [docs/ref.md](docs/ref.md): reference documents (specs, norms, mock-ups) and the coverage of their elements by the lots.
|
|
39
42
|
- [docs/lead.md](docs/lead.md): the lead workflow.
|
|
40
43
|
- [docs/overview.md](docs/overview.md): the longer introduction and the map of every page.
|
|
41
44
|
- [docs/fonctionnement.md](docs/fonctionnement.md): how it all works, in four figures.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reference-planner
|
|
3
|
+
description: Reference document coverage planner. Reads an inventory (`inventaire.json` of `ref analyse --double` or `ref diff --out`, or `lectures/a.json`, the reading `ref analyse` writes by default) and the project's plan, and PROPOSES lots that cover the elements nobody covers yet — each proposal cites the ids it covers, says whether it is feasible or blocked (naming the missing datum or service) and what it depends on. It only proposes, it creates no lot, edits no file and writes no exclusion, and the lead decides. Starts from `cadence ref coverage`, which lists the gaps. Used by the lead, not by hand.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You plan the coverage of one reference document. You read; you never write. The lot is not yours to create, the plan is not yours to edit, and an element is not yours to exclude: you propose, the lead decides.
|
|
8
|
+
|
|
9
|
+
Follow the brief you are given. It names the inventory (`inventaire.json`) and the plan. The method:
|
|
10
|
+
|
|
11
|
+
1. **Run `cadence ref coverage <inventaire.json>` first.** Its `✗` lines are the work: elements no done lot covers and no written exclusion justifies. A lot (any status but `dropped`) that cites an id already covers it in the plan's eyes; only a **done** lot counts for the command. An element a todo or doing lot already cites is not a gap to plan again: say so, name the lot.
|
|
12
|
+
2. **Read the inventory and the plan.** The inventory gives, per element, its type, page, zone, text and the elements it refers to (`composant.ecran`, `etat.cible`, `statut.couvre`). The plan (`.cadence/raf.yaml`, the legacy `docs/plan/raf.yaml`, or the file named by `plan:` in `cadence.yaml`) gives the lots that exist, their titles and what they depend on. Read the project's CLAUDE.md and the code of the screens or modules the elements belong to: a proposal that ignores what exists duplicates it.
|
|
13
|
+
3. **Group the gaps into lots of one session each** (an estimate of 1 or less): one screen, one zone of a screen, one table, one message template. Never one lot per element, never one lot for the whole document. An element goes into exactly one proposed lot.
|
|
14
|
+
4. **For each proposed lot, say:**
|
|
15
|
+
- `titre`: what the lot delivers, in the project's words;
|
|
16
|
+
- `ids`: the ids it covers, copied from the inventory, never retyped from memory (the lot will quote them, and `cadence ref coverage` finds them by that quotation);
|
|
17
|
+
- `faisable`: `true` when everything the lot needs exists today; `false` when it is **blocked**, and then `bloque_par` names precisely the missing datum or service (« the `GET /api/x` endpoint does not exist », « no data source for the wind barbs », « the licence of the font is unknown »), never « missing information »;
|
|
18
|
+
- `depend_de`: the lots it waits for, an existing id of the plan (`L12`) or the title of another proposal;
|
|
19
|
+
- `raison`: one sentence on why these elements belong together.
|
|
20
|
+
5. **A `silence` of the inventory is a question for a human**, not a lot: list it under `questions` with its id. Never answer it.
|
|
21
|
+
6. **An element that should not be built** (a decoration of the document, a text of the mock-up that is not the product) is not excluded by you: list it under `exclusions_proposees` with its id and the reason, the lead writes it in the `exclusions` key of the inventory if they agree.
|
|
22
|
+
7. **Never invent an id.** An id that is not in the inventory is an error of your proposal. Before you report, check that every `✗` id of `cadence ref coverage` is in a proposal, in `exclusions_proposees`, in `questions`, or named in `deja_cites` as already cited by an existing lot.
|
|
23
|
+
|
|
24
|
+
- You are read-only: no `raf add|start|done|note|review|ux`, no edit of the plan, of the inventory or of any file, no commit, no push, no deliver, no subagent. The only command you run is `cadence ref coverage` (and read-only commands such as `git log`, `grep`).
|
|
25
|
+
- A proposal with no id, or an id twice, is not a proposal.
|
|
26
|
+
- Give the report in the structured output the brief asks for: `lots` (each `titre`, `ids`, `faisable`, `bloque_par`, `depend_de`, `raison`), `exclusions_proposees` (`id`, `raison`), `deja_cites` (`id`, `lot`), `questions`, `nonVerifie`, `resume`.
|
package/bin/cadence.js
CHANGED
|
@@ -13,7 +13,7 @@ const io = {
|
|
|
13
13
|
|
|
14
14
|
if (tool === 'raf') {
|
|
15
15
|
process.exitCode = await run(args, io);
|
|
16
|
-
} else if (['news', 'session', 'deliver', 'verify', 'skills', 'orchestrate', 'lead', 'corrections', 'perf', 'spec', 'ref'].includes(tool)) {
|
|
16
|
+
} else if (['news', 'session', 'deliver', 'verify', 'skills', 'orchestrate', 'lead', 'corrections', 'perf', 'spec', 'ref', 'config', 'release'].includes(tool)) {
|
|
17
17
|
process.exitCode = await run([tool, ...args], io);
|
|
18
18
|
} else if (tool === '--version' || tool === '-v') {
|
|
19
19
|
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
@@ -39,6 +39,9 @@ if (tool === 'raf') {
|
|
|
39
39
|
cadence ref measure <slug> <page> <page | x,y | x,y,l,h> [--quoi couleurs|contraste|boites|texte] [--largeur 1440] [--json]
|
|
40
40
|
mesure une zone d'une page rendue : couleurs, contraste WCAG 2.2, boîtes, mots OCR et taille de police estimée,
|
|
41
41
|
ramenés à 1440 px de large, tolérance déclarée
|
|
42
|
+
cadence release patch|minor|major [--dry-run]
|
|
43
|
+
prépare le commit de version (fichiers de version, CHANGELOG) sans rien commiter ni publier ;
|
|
44
|
+
imprime les étapes du lead dans l'ordre et vérifie docs/whats-new.md
|
|
42
45
|
cadence verify [--retry secondes] [--sha rév]
|
|
43
46
|
rejoue les vérifications d'effet (deliver.verify) hors livraison, une passe, en parallèle ;
|
|
44
47
|
code 0 tout vert, 1 un effet rouge, 2 rien à vérifier ; « session start » la lance aussi
|
|
@@ -52,6 +55,10 @@ if (tool === 'raf') {
|
|
|
52
55
|
cadence lead tour [dossier] [--idle 3] [--json]
|
|
53
56
|
le tableau du lead, sans modèle : une ligne par sous-dossier qui a un plan, ou la ligne du dossier courant
|
|
54
57
|
s'il est lui-même un projet ; une première ligne « cadence.yaml · ✗ erreur » si priority: est illisible
|
|
58
|
+
cadence config set <clé> <valeur> --all [--dry-run] [--depth n]
|
|
59
|
+
pose la même clé dans le cadence.yaml de chaque projet du dossier (ceux de lead tour), commentaires et ordre gardés,
|
|
60
|
+
avec un lot de config qui la cite et un commit à chemin explicite par projet ; ne pousse pas ;
|
|
61
|
+
s'arrête sur une config qui ne se relit pas
|
|
55
62
|
cadence corrections [dossier] [--classify [--model m]] [--cache fichier] [--sample n] [--until date] [--since date] [--json]
|
|
56
63
|
la mesure des passes de correction des vagues (dossier/.cadence/runs) : lots à ≥1 et ≥2 corrections,
|
|
57
64
|
par projet, visibles ou non, passes par cause ; --classify les classe par Ollama (hors quota)
|
package/dist/cli.js
CHANGED
|
@@ -9,16 +9,19 @@ import { short } from './check.js';
|
|
|
9
9
|
import { isDay, toDay } from './dates.js';
|
|
10
10
|
import { deliver, parseDeliverConfig, realDeps, shellQuote } from './deliver.js';
|
|
11
11
|
import { defaultTarget, effectLines, realCheckDeps, verifyCommand } from './verify.js';
|
|
12
|
+
import { verifyGate } from './verify-gate.js';
|
|
12
13
|
import { ganttData, renderGantt } from './gantt.js';
|
|
13
|
-
import {
|
|
14
|
+
import { PLAN_ACTIONS, planCommitMessage } from './plan-commit.js';
|
|
15
|
+
import { dirtyPaths, gitRoot, isTracked, readCommits, matchesHead, resolveCommit, stage, stagedFiles, syncIndexWithHead } from './git.js';
|
|
14
16
|
import { installHook, pendingCommitMessage, REFUSED } from './hook.js';
|
|
15
17
|
import { citedRefs, linkCommits } from './link.js';
|
|
16
18
|
import { buildNews, loadEntries, newEntry, newsData, newsIssues, publicTitleTooLong, stampEntries } from './news.js';
|
|
17
|
-
import { invalidResource, Plan, RafError, SCREENS, STATUSES } from './plan.js';
|
|
19
|
+
import { invalidResource, Plan, RafError, SCREENS, STATUSES, setPlanNotice } from './plan.js';
|
|
18
20
|
import { dueLine, isRecurring, recurringByDue } from './recurring.js';
|
|
19
21
|
import { schedule } from './schedule.js';
|
|
20
22
|
import { AGENTS_DIR, installAgents, installSkills, SKILLS_DIR } from './skills.js';
|
|
21
|
-
import {
|
|
23
|
+
import { configSet } from './config-set.js';
|
|
24
|
+
import { findProjects, readDepth, leadTour, readLeadUx, tourFreeLine, tourLine, tourNoTestLines, tourWorktreeLines } from './lead.js';
|
|
22
25
|
import { configuredCap, expiredHoldLines, liveLines, orchestrate, realOrchestrateDeps } from './orchestrate/command.js';
|
|
23
26
|
import { addHold, releaseHold } from './orchestrate/registry.js';
|
|
24
27
|
import { activeLock, REPO_LOCK } from './orchestrate/lock.js';
|
|
@@ -26,8 +29,10 @@ import { cadenceHome, liveWaves, openLotsIn, openWavesIn, waveLotsIn } from './o
|
|
|
26
29
|
import { blockingLots, LABEL, resumeCommand } from './orchestrate/table.js';
|
|
27
30
|
import { classifyAll, measure, ollamaClassifier, readCache, readJournals, readRuns, renderReport, sampleKeys, serializeCache } from './corrections.js';
|
|
28
31
|
import { computePerf, perfHeader, perfWindows, readPlans, readProjectDeliveries, readProjectFailedEntries, renderPerf } from './perf.js';
|
|
29
|
-
import { findAcceptation, parseAcceptation, resealAcceptation } from './orchestrate/accept.js';
|
|
32
|
+
import { findAcceptation, parseAcceptation, redAcceptations, resealAcceptation, resealForGreen, runAcceptation, setVert } from './orchestrate/accept.js';
|
|
30
33
|
import { refCommand } from './ref/command.js';
|
|
34
|
+
import { coverageCommand } from './ref/coverage.js';
|
|
35
|
+
import { release } from './release.js';
|
|
31
36
|
import { applyMigration, describeMigration, planMigration, unhandledError } from './migrate.js';
|
|
32
37
|
import { checkFiche, deriveQa, readFiche, summary } from './spec.js';
|
|
33
38
|
import { readHudContext } from './hud-context.js';
|
|
@@ -59,6 +64,7 @@ const HELP = `raf — plan « reste à faire » versionné dans le dépôt, reli
|
|
|
59
64
|
raf ignore <sha> | "<sujet exact>" [--reason texte] acquitte un commit sans lot (ou citant un id inconnu) sans réécrire l'historique ; raf check --ignored les liste
|
|
60
65
|
raf docs-ok <lot> --reason texte acquitte l'écart docs.sync d'un lot (documentation en retard) avec la raison pour laquelle la documentation reste telle quelle ; lu par raf check et par orchestrate, qui ne rend jamais prêt un lot avec un écart ouvert
|
|
61
66
|
raf gantt [-o .cadence/gantt.html]
|
|
67
|
+
cadence config set <clé> <valeur> --all [--dry-run] [--depth n] pose la même clé dans le cadence.yaml de chaque projet du dossier (ceux de lead tour), commentaires et ordre gardés, un lot de config qui la cite (raf add + start) et un commit à chemin explicite par projet ; ne pousse pas ; liste ce qui est fait et sauté (dépôt sale, déjà posée, plan en lecture seule) ; s'arrête sur une config qui ne se relit pas
|
|
62
68
|
raf migrate [--dry-run] déplace docs/plan/raf.yaml vers .cadence/raf.yaml (git mv) et réécrit les lecteurs du chemin dans le dépôt ; --dry-run les liste sans rien toucher ; refus tant qu'un lecteur n'est pas traité (migrate.keep de cadence.yaml pour ceux qui ne lisent pas le plan) ; rejouable
|
|
63
69
|
raf hook install (pre-commit et post-commit ; cadence.yaml hook.autostart: warn|refuse|start)
|
|
64
70
|
cadence orchestrate <projet>:<lot>[@modèle]… [--budget 2M] [--goal "…"] [--dry-run] [--wave nom] [--continue [--until HH:MM] [--priority a,b]] une session claude neuve par étape ; --status [vague] [--watch [--interval s]] ; --resume [vague | --wave vague] [--budget 1M] [--budget projet:lot=+300k] [--answer projet:lot "réponse"] [--drop projet:lot] ; --drop projet:lot [--wave vague] ; --stop-after-current [--wave vague] ; --waits [--since AAAA-MM-JJ]
|
|
@@ -72,9 +78,12 @@ const HELP = `raf — plan « reste à faire » versionné dans le dépôt, reli
|
|
|
72
78
|
cadence ref diff <lecture-a.json> <lecture-b.json> [--out inventaire.json] [--seuil 30] réconcilie deux lectures du même document (même slug et même empreinte) : concordant → un élément lu ou mesuré, divergent → incertain avec les deux versions, seul (une lecture) → incertain en attente du troisième regard ; silences réunis ; au-delà de 30 % d'éléments non appariés (--seuil, en pour cent) code 1 et rien d'écrit ; --out écrit l'inventaire
|
|
73
79
|
cadence ref measure <slug> <page> <page | x,y | x,y,l,h> [--quoi couleurs|contraste|boites|texte] [--largeur 1440] [--json] mesure une zone d'une page rendue (pixels de la page) : couleurs (point, moyenne, dominantes), contraste WCAG 2.2, boîtes, mots OCR tesseract et taille de police estimée ; valeurs ramenées à 1440 px de large, tolérance déclarée
|
|
74
80
|
cadence ref check <inventaire.json | lecture.json> valide un inventaire ou une lecture (schéma version 1, modèle templates/inventaire.json) : une ligne « ✗ chemin : raison » par écart puis « N écart(s) » sur la sortie standard (code 1), le résumé par type sinon (code 0) ; fichier absent, vide ou illisible : code 2 ; n'appelle aucun outil système
|
|
81
|
+
cadence ref coverage <inventaire.json> couverture d'un document de référence, sans modèle : les éléments qu'aucun lot fait ne couvre (lot qui cite l'id) et qu'aucune exclusion écrite avec sa raison (clé « exclusions » de l'inventaire) ne justifie, une ligne ✗ chacun ; code 1 s'il en reste, 0 sinon, 2 pour un appel mal formé ou un fichier qui n'est pas un inventaire valide
|
|
75
82
|
cadence ref eval <attendu.json | dossier> <lecture.json | dossier> [--json] note une lecture contre l'inventaire attendu (vérité connue) : rappel et précision par type, faux lu (éléments sûrs et faux), pièges trouvés, tokens par page (usage.tokens de la lecture) ; lecture d'un autre document refusée (code 2) ; sur dossiers, un cas par sous-dossier (attendu.json / lecture.json)
|
|
76
83
|
cadence ref corpus <dossier> [--sans-rendu] écrit le corpus synthétique à vérité connue (7 pièges : texte ambigu levé par une figure, figure qui contredit, table en image, gabarit BUFR, contrôle translucide, page Proposal, scan bruité) : par cas document.html et attendu.json, puis document.pdf et page-<n>.png (1700×2200) rendus par Playwright (CADENCE_PLAYWRIGHT = dossier du paquet) ; dossier existant ou Playwright introuvable : code 2
|
|
84
|
+
cadence release patch|minor|major [--dry-run] prépare le commit de version (package.json, package-lock.json, plugin.json, marketplace.json, CHANGELOG.md) sans commiter ni publier ; imprime les étapes du lead et vérifie docs/whats-new.md : refus sur un arbre sale, un [Unreleased] vide ou une version déjà taguée
|
|
77
85
|
cadence verify [--retry s] [--sha rév] rejoue deliver.verify hors livraison : 0 vert, 1 effet rouge, 2 rien à vérifier
|
|
86
|
+
cadence verify --gate --sha rév [--project dossier] rejoue la porte du projet (orchestrate.build puis orchestrate.test) au sha dans un worktree jetable aux dépendances reliées (orchestrate.worktreeDeps), puis le retire : 0 vert, 1 rouge avec les tests en échec, 2 rien à rejouer
|
|
78
87
|
raf news new <lot…> [--title t] | list | check | stamp | build [-o dossier] (aussi « cadence news … »)
|
|
79
88
|
|
|
80
89
|
Un commit appartient à un lot quand son message cite l'identifiant : « feat(L3): … », « L3/t1 ».
|
|
@@ -109,6 +118,7 @@ function legacyPlanLine(root, legacy) {
|
|
|
109
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}`;
|
|
110
119
|
}
|
|
111
120
|
export function run(argv, io) {
|
|
121
|
+
setPlanNotice(io.err);
|
|
112
122
|
try {
|
|
113
123
|
// orchestrate a ses propres options (--status et --resume prennent une valeur facultative, --answer en prend deux).
|
|
114
124
|
if (argv[0] === 'orchestrate') {
|
|
@@ -168,6 +178,7 @@ function dispatch(argv, io) {
|
|
|
168
178
|
'dry-run': { type: 'boolean' },
|
|
169
179
|
sha: { type: 'string' },
|
|
170
180
|
retry: { type: 'string' },
|
|
181
|
+
gate: { type: 'boolean' },
|
|
171
182
|
clear: { type: 'boolean' },
|
|
172
183
|
ignored: { type: 'boolean' },
|
|
173
184
|
notes: { type: 'boolean' },
|
|
@@ -194,7 +205,7 @@ function dispatch(argv, io) {
|
|
|
194
205
|
io.out(HELP);
|
|
195
206
|
return 0;
|
|
196
207
|
}
|
|
197
|
-
if (command !== 'session' && !(command === 'lead' && !values.all) && (values.all || values.depth !== undefined))
|
|
208
|
+
if (command !== 'session' && command !== 'config' && !(command === 'lead' && !values.all) && (values.all || values.depth !== undefined))
|
|
198
209
|
throw new RafError(`${values.all ? '--all' : '--depth'} ne va qu'avec session start ou session close`);
|
|
199
210
|
const today = io.env.RAF_TODAY ?? toDay(io.now());
|
|
200
211
|
if (!isDay(today))
|
|
@@ -263,12 +274,29 @@ function dispatch(argv, io) {
|
|
|
263
274
|
}
|
|
264
275
|
return 0;
|
|
265
276
|
}
|
|
277
|
+
// Poser une clé dans le cadence.yaml de chaque projet du dossier (L342) : ne lit ni le plan ni le cadence.yaml du dossier courant.
|
|
278
|
+
if (command === 'config') {
|
|
279
|
+
if (rest[0] !== 'set' || rest.length !== 3)
|
|
280
|
+
throw new RafError('usage : cadence config set <clé> <valeur> --all [--dry-run] [--depth n]');
|
|
281
|
+
if (!values.all)
|
|
282
|
+
throw new RafError('config set demande --all : la commande s\'applique aux projets du dossier courant (lancer la commande dans le projet visé pour un seul)');
|
|
283
|
+
if (values.depth !== undefined && !(Number.isInteger(Number(values.depth)) && Number(values.depth) >= 1))
|
|
284
|
+
throw new RafError(`--depth invalide : ${values.depth} (un entier ≥ 1)`);
|
|
285
|
+
const parent = resolve(io.cwd);
|
|
286
|
+
const result = configSet(parent, rest[1], rest[2], { dryRun: values['dry-run'], depth: values.depth === undefined ? readDepth(parent) : Number(values.depth) }, today);
|
|
287
|
+
for (const l of result.lines)
|
|
288
|
+
io.out(l);
|
|
289
|
+
return result.code;
|
|
290
|
+
}
|
|
266
291
|
if (command === 'corrections')
|
|
267
292
|
return corrections(rest, values, io);
|
|
268
293
|
if (command === 'perf')
|
|
269
294
|
return perf(rest, values, today, io);
|
|
270
|
-
if (command === 'ref')
|
|
295
|
+
if (command === 'ref' && rest[0] !== 'coverage')
|
|
271
296
|
return refCommand(rest, values, root, io);
|
|
297
|
+
// Prépare le commit de version : écrit les fichiers de version, ne commite ni ne publie rien (L341).
|
|
298
|
+
if (command === 'release')
|
|
299
|
+
return release(rest, !!values['dry-run'], root, today, io.out);
|
|
272
300
|
// Déplacer le plan ne lit pas le plan : pas d'avertissement « ancien emplacement » (L284).
|
|
273
301
|
if (command === 'migrate') {
|
|
274
302
|
if (rest.length > 0)
|
|
@@ -288,6 +316,7 @@ function dispatch(argv, io) {
|
|
|
288
316
|
if (command === 'spec' && (rest[0] === 'check' || rest[0] === 'qa'))
|
|
289
317
|
return spec(rest, io);
|
|
290
318
|
const reseal = command === 'spec' && rest[0] === 'reseal';
|
|
319
|
+
const green = command === 'spec' && rest[0] === 'green';
|
|
291
320
|
// --all balaie les sous-dossiers : il ne lit ni le plan ni le cadence.yaml du dossier courant.
|
|
292
321
|
if (command === 'session' && (values.all || values.depth !== undefined))
|
|
293
322
|
return sessionAll(rest, values, io);
|
|
@@ -304,6 +333,9 @@ function dispatch(argv, io) {
|
|
|
304
333
|
if (fallback?.legacy && !installing)
|
|
305
334
|
(io.notice ?? io.err)(legacyPlanLine(root, fallback.legacy));
|
|
306
335
|
const loadPlan = () => Plan.load(planPath, { ...planConfig?.settings, config: configPath });
|
|
336
|
+
// `ref coverage` lit le plan (les lots faits qui citent les éléments) : il passe après le choix du plan, les autres `ref` n'y touchent pas (L367).
|
|
337
|
+
if (command === 'ref')
|
|
338
|
+
return coverageCommand(rest.slice(1), loadPlan, io, { repo: root, specDir: readOrchestrateConfig(configPath).spec.dir });
|
|
307
339
|
const newsDir = resolve(io.cwd, values.dir ?? join(root, 'docs/nouveautes'));
|
|
308
340
|
// Limite DÉCLARÉE (news.publicTitleMax) : sans clé, seul « raf check » avertit ; avec, la pose d'un titre public est refusée.
|
|
309
341
|
const refuseLongTitle = (text, hint = '') => {
|
|
@@ -387,6 +419,24 @@ function dispatch(argv, io) {
|
|
|
387
419
|
refuseLongTitle(pub, lot?.public ? '' : ' — titre de la Nouveauté reprise par le site : corriger l\'entrée ou poser raf public');
|
|
388
420
|
}
|
|
389
421
|
// Le plan ne lit pas git : lui dire combien de commits du lot sa revue ne couvre pas quand elle est exigée.
|
|
422
|
+
// Un lot clos avec une acceptation à vert: false bloque les vagues suivantes du dépôt (verrou des tests d'acceptation, L366).
|
|
423
|
+
if (status === 'done' && !rest[0].includes('/')) {
|
|
424
|
+
const repo = gitRoot(root) ?? root;
|
|
425
|
+
let dir = null;
|
|
426
|
+
try {
|
|
427
|
+
dir = readOrchestrateConfig(configPath).spec.dir;
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
// un cadence.yaml illisible ne doit pas empêcher de clore un lot : `cadence check` en parle
|
|
431
|
+
}
|
|
432
|
+
const red = dir === null ? undefined : redAcceptations(repo, dir).find((a) => a.lot === rest[0]);
|
|
433
|
+
if (red) {
|
|
434
|
+
const text = `${rest[0]} : ${red.path} est à vert: false — clos ainsi, le lot bloquerait les vagues suivantes du dépôt (verrou des tests d'acceptation) ; cadence spec green ${rest[0]} lance la commande d'acceptation et pose vert: true`;
|
|
435
|
+
if (!values.force)
|
|
436
|
+
throw new RafError(`${text} (--force pour passer outre)`);
|
|
437
|
+
io.err(`avertissement : ${text}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
390
440
|
const unreviewed = status === 'done' && plan.reviewSince && !plan.readonly && !values.force ? unreviewedWork(plan, root, rest[0]) : 0;
|
|
391
441
|
plan.setStatus(rest[0], status, today, { force: values.force, unreviewed });
|
|
392
442
|
if (command === 'drop' && values.reason)
|
|
@@ -469,6 +519,8 @@ function dispatch(argv, io) {
|
|
|
469
519
|
return 0;
|
|
470
520
|
}
|
|
471
521
|
case 'spec': {
|
|
522
|
+
if (green)
|
|
523
|
+
return specGreen(rest.slice(1), { root, loadPlan, configPath, today }, io);
|
|
472
524
|
if (reseal)
|
|
473
525
|
return specReseal(rest.slice(1), values.reason, values.porteur, { root, loadPlan, configPath, today }, io);
|
|
474
526
|
if (rest.length !== 2 || (rest[1] !== 'on' && rest[1] !== 'off'))
|
|
@@ -680,6 +732,8 @@ function dispatch(argv, io) {
|
|
|
680
732
|
return deliver(ctx, realDeps(root));
|
|
681
733
|
}
|
|
682
734
|
case 'verify': {
|
|
735
|
+
if (values.gate)
|
|
736
|
+
return verifyGateCommand(values, io);
|
|
683
737
|
if (!gitRoot(io.cwd))
|
|
684
738
|
throw new RafError('verify : à lancer dans un dépôt git');
|
|
685
739
|
if (!existsSync(configPath))
|
|
@@ -849,12 +903,23 @@ function preCommit(load, mode, root, today, io) {
|
|
|
849
903
|
io.err(`raf: plan modifié non commité : démarrage automatique sauté (hook.autostart: start), raf start ${todo.join(' ')} à la main`);
|
|
850
904
|
return 0;
|
|
851
905
|
}
|
|
852
|
-
for (const id of todo)
|
|
906
|
+
for (const id of todo)
|
|
853
907
|
plan.setStatus(id, 'doing', today, {});
|
|
854
|
-
|
|
908
|
+
try {
|
|
909
|
+
plan.save();
|
|
855
910
|
}
|
|
856
|
-
|
|
911
|
+
catch (e) {
|
|
912
|
+
if (!(e instanceof RafError))
|
|
913
|
+
throw e;
|
|
914
|
+
// plan.onWrite en échec (L346) : le plan est resté tel quel, le commit part sans lui et le dit.
|
|
915
|
+
io.err(`raf: démarrage automatique refusé : ${e.message} ; raf start ${todo.join(' ')} à la main`);
|
|
916
|
+
return 0;
|
|
917
|
+
}
|
|
918
|
+
for (const id of todo)
|
|
919
|
+
io.err(`raf: ${id} était todo — raf start ${id} fait par le hook, plan inclus dans ce commit`);
|
|
857
920
|
stage(root, file);
|
|
921
|
+
for (const f of plan.produced)
|
|
922
|
+
stage(root, f);
|
|
858
923
|
}
|
|
859
924
|
catch {
|
|
860
925
|
// Un hook ne doit jamais gêner un commit, hors refus.
|
|
@@ -991,7 +1056,7 @@ const git = (cwd, args) => execFileSync('git', args, { cwd, encoding: 'utf8', st
|
|
|
991
1056
|
/** Chemin à coller dans un shell : entre apostrophes dès qu'il contient autre chose que des caractères sûrs. */
|
|
992
1057
|
const shellPath = (p) => (/^[\w@%+=:,./-]+$/.test(p) ? p : `'${p.replace(/'/g, "'\\''")}'`);
|
|
993
1058
|
/** Le refus de `spec reseal` devant des vagues non terminées (L238) : chaque vague (vivante ou arrêtée), son dossier de lancement, ses lots, et pour chaque vague arrêtée UNE commande à lancer telle quelle (un `--drop` par lot ouvert du dépôt, y compris ceux en file qui ne sont pas en cours au plan : `--resume` relance toute la vague avec les lots non retirés, et un lot en file reprendrait le verrou, L239). */
|
|
994
|
-
function openWavesRefusal(open) {
|
|
1059
|
+
function openWavesRefusal(open, command) {
|
|
995
1060
|
const lines = open.map((w) => {
|
|
996
1061
|
const where = [w.live ? 'vivante' : 'arrêtée', ...(w.live && w.pid !== undefined ? [`pid ${w.pid}`] : []), ...(w.launchDir ? [`lancée depuis ${w.launchDir}`] : [])].join(', ');
|
|
997
1062
|
return ` - vague ${w.wave} (${where}) : ${w.lots.length ? [...w.lots.map((l) => `${l.key} (${l.status})`), ...w.others.map((l) => `${l.key} (${l.status}, pas en cours au plan)`)].join(', ') : 'lots illisibles'}`;
|
|
@@ -1000,9 +1065,9 @@ function openWavesRefusal(open) {
|
|
|
1000
1065
|
? ` vague ${w.wave} : orchestration en cours, à attendre (le verrou du dépôt tombe dès que tous ses lots y sont prêts ou rendus)`
|
|
1001
1066
|
: ` cd ${shellPath(w.launchDir)} && ${resumeCommand(w.wave, ...[...w.lots, ...w.others].flatMap((l) => ['--drop', l.key]))}`);
|
|
1002
1067
|
return [
|
|
1003
|
-
|
|
1068
|
+
`${command} : refusé — ${open.length > 1 ? `${open.length} vagues non terminées gardent` : 'une vague non terminée garde'} ouvert au moins un lot en cours de ce dépôt (${command === 'spec green' ? 'le commit de spec green posé' : 'un rescellé commité'} maintenant lui serait rendu comme « premier commit fautif » à la reprise de la vague) :`,
|
|
1004
1069
|
...lines,
|
|
1005
|
-
|
|
1070
|
+
`Marche à suivre : clore chacun de ces lots dans sa vague (attendre les vivantes, retirer les lots des arrêtées), puis relancer ${command === 'spec green' ? 'spec green' : 'le rescellé'} :`,
|
|
1006
1071
|
...how,
|
|
1007
1072
|
].join('\n');
|
|
1008
1073
|
}
|
|
@@ -1028,12 +1093,86 @@ function withHolder(open, holder, repo) {
|
|
|
1028
1093
|
const keys = openLotsIn(cadenceHome(), holder.pid, repo) ?? [];
|
|
1029
1094
|
return [{ wave: holder.wave, launchDir: reg?.cwd ?? '', live: true, pid: holder.pid, lots: keys.map((key) => ({ key, status: 'ouvert' })), others: [] }, ...open];
|
|
1030
1095
|
}
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1096
|
+
/**
|
|
1097
|
+
* `cadence spec green <lot>` (L366) : un lot mené ou clos à la main (raf done) dont l'acceptation est restée à `vert: false` bloque toutes les vagues suivantes du dépôt.
|
|
1098
|
+
* Lance la commande dédiée (`orchestrate.accept`) comme le cycle ; verte, rescelle les tests mis au point depuis le commit rouge (`resealForGreen`), pose `vert: true` et commite le seul fichier, en citant le lot. Rouge : rien n'est changé, code 1.
|
|
1099
|
+
*/
|
|
1100
|
+
function specGreen(args, ctx, io) {
|
|
1101
|
+
if (args.length !== 1)
|
|
1102
|
+
throw new RafError('usage : cadence spec green <lot>');
|
|
1034
1103
|
if (io.env.CADENCE_ORCHESTRATED)
|
|
1035
|
-
throw new RafError(`spec
|
|
1036
|
-
|
|
1104
|
+
throw new RafError(`spec green refusé pendant une vague orchestrée (${io.env.CADENCE_ORCHESTRATED}) : c'est le travail du lead, hors session`);
|
|
1105
|
+
refuseWhileWaveHolds(ctx, 'spec green');
|
|
1106
|
+
const lotId = args[0];
|
|
1107
|
+
const plan = ctx.loadPlan();
|
|
1108
|
+
const lot = plan.lot(lotId);
|
|
1109
|
+
if (lot.status === 'todo')
|
|
1110
|
+
throw new RafError(`${lotId} n'est pas commencé (todo) : il n'a rien livré dont l'acceptation puisse passer au vert`);
|
|
1111
|
+
const repo = gitRoot(ctx.root) ?? ctx.root;
|
|
1112
|
+
const config = readOrchestrateConfig(ctx.configPath);
|
|
1113
|
+
if (!config.accept)
|
|
1114
|
+
throw new RafError(`orchestrate.accept absent de ${ctx.configPath} : sans la commande dédiée, il n'y a rien à lancer pour ${lotId}`);
|
|
1115
|
+
const json = findAcceptation(repo, config.spec.dir, lotId);
|
|
1116
|
+
if (!json)
|
|
1117
|
+
throw new RafError(`${lotId} : aucune acceptation sous ${config.spec.dir} (le lot n'a pas passé l'étape accept)`);
|
|
1118
|
+
const doc = parseAcceptation(readFileSync(join(repo, json), 'utf8'));
|
|
1119
|
+
if (typeof doc === 'string')
|
|
1120
|
+
throw new RafError(`${json} : ${doc}`);
|
|
1121
|
+
if (doc.vert) {
|
|
1122
|
+
io.out(`${lotId} : ${json} est déjà vert, rien à faire`);
|
|
1123
|
+
return 0;
|
|
1124
|
+
}
|
|
1125
|
+
const open = unfinishedWaves(ctx.root, repo, plan);
|
|
1126
|
+
if (open.length)
|
|
1127
|
+
throw new RafError(openWavesRefusal(open, 'spec green'));
|
|
1128
|
+
const dirty = git(repo, ['status', '--porcelain', '-z', '--', ...doc.fichiers]).split('\0').filter(Boolean).map((l) => l.slice(3));
|
|
1129
|
+
if (dirty.length)
|
|
1130
|
+
throw new RafError(`${lotId} : test(s) listé(s) modifié(s) sans être commités : ${dirty.join(', ')} — les commiter (en citant ${lotId}) avant de poser vert: true`);
|
|
1131
|
+
const { runs, orphans } = runAcceptation(repo, json, config.accept);
|
|
1132
|
+
if (orphans.length)
|
|
1133
|
+
throw new RafError(`fichiers listés dans ${json} sous la racine d'aucune commande de orchestrate.accept : ${orphans.join(', ')}`);
|
|
1134
|
+
const failed = runs.filter((r) => r.code !== 0);
|
|
1135
|
+
if (failed.length) {
|
|
1136
|
+
io.err(`${lotId} : tests d'acceptation encore rouges (${failed.map((r) => `${r.command}, code ${r.code}`).join(' ; ')}), ${json} reste à vert: false`);
|
|
1137
|
+
for (const r of failed)
|
|
1138
|
+
io.err(r.output.trim().split('\n').slice(-40).join('\n'));
|
|
1139
|
+
return 1;
|
|
1140
|
+
}
|
|
1141
|
+
const before = readFileSync(join(repo, json), 'utf8');
|
|
1142
|
+
let resceles;
|
|
1143
|
+
try {
|
|
1144
|
+
resceles = resealForGreen(repo, json, ctx.today);
|
|
1145
|
+
}
|
|
1146
|
+
catch (e) {
|
|
1147
|
+
throw new RafError(`${lotId} : ${e.message}`);
|
|
1148
|
+
}
|
|
1149
|
+
setVert(repo, json);
|
|
1150
|
+
try {
|
|
1151
|
+
git(repo, ['add', '--', json]);
|
|
1152
|
+
// Le sujet reste court et fixe (le gabarit `plan.commit` du projet s'il y en a un, comme tout commit écrit par cadence, L343) ; les tests rescellés, sans borne, vont dans le corps, une ligne chacun.
|
|
1153
|
+
const template = readPlanConfig(join(repo, 'cadence.yaml'), repo)?.commit;
|
|
1154
|
+
const subject = template ? planCommitMessage(template, { lot: lotId, action: PLAN_ACTIONS.specGreen }) : `chore(${lotId}): ${PLAN_ACTIONS.specGreen}`;
|
|
1155
|
+
const body = resceles.length ? ['empreintes rescellées (tests mis au point depuis le commit rouge) :', ...resceles.map((f) => `- ${f}`)].join('\n') : null;
|
|
1156
|
+
git(repo, ['commit', '-q', '-m', subject, ...(body ? ['-m', body] : []), '--', json]);
|
|
1157
|
+
}
|
|
1158
|
+
catch (e) {
|
|
1159
|
+
// Atomique, comme le rescellé : un commit refusé (hook pre-commit, ...) laisse le fichier tel qu'avant.
|
|
1160
|
+
writeFileSync(join(repo, json), before);
|
|
1161
|
+
try {
|
|
1162
|
+
git(repo, ['reset', '-q', '--', json]);
|
|
1163
|
+
}
|
|
1164
|
+
catch {
|
|
1165
|
+
// rien d'indexé : rien à défaire
|
|
1166
|
+
}
|
|
1167
|
+
const out = e;
|
|
1168
|
+
throw new RafError(`spec green : le commit a échoué, ${json} est revenu tel qu'avant la commande — ${(out.stderr || out.stdout || out.message).trim().split('\n').slice(-5).join(' | ')}`);
|
|
1169
|
+
}
|
|
1170
|
+
io.out(`${lotId} : ${json} → vert: true, commit ${git(repo, ['rev-parse', '--short', 'HEAD']).trim()}${resceles.length ? ` ; empreintes rescellées (tests mis au point depuis le commit rouge) : ${resceles.join(', ')}` : ''}`);
|
|
1171
|
+
return 0;
|
|
1172
|
+
}
|
|
1173
|
+
/** Un commit du lead en pleine passe ferait signaler au lot suivant un écart qui n'est pas le sien : tant qu'une vague tient le verrou du dépôt, `spec reseal` et `spec green` refusent. */
|
|
1174
|
+
function refuseWhileWaveHolds(ctx, command) {
|
|
1175
|
+
// Comme `deliver` : la vague tient le verrou du dépôt tant qu'un de ses lots y travaille (état illisible : on refuse).
|
|
1037
1176
|
// Le refus est UN message : la vague vivante qui tient le verrou (à attendre) et les vagues arrêtées du dépôt, avec leurs dossiers et leurs commandes (L238).
|
|
1038
1177
|
const orchestrating = waveHolding(ctx.root);
|
|
1039
1178
|
if (orchestrating) {
|
|
@@ -1045,8 +1184,15 @@ function specReseal(args, reason, porteur, ctx, io) {
|
|
|
1045
1184
|
catch {
|
|
1046
1185
|
// plan illisible : le verrou seul motive le refus, la vague qui le tient est nommée
|
|
1047
1186
|
}
|
|
1048
|
-
throw new RafError(openWavesRefusal(withHolder(open, orchestrating, repo)));
|
|
1187
|
+
throw new RafError(openWavesRefusal(withHolder(open, orchestrating, repo), command));
|
|
1049
1188
|
}
|
|
1189
|
+
}
|
|
1190
|
+
function specReseal(args, reason, porteur, ctx, io) {
|
|
1191
|
+
if (args.length !== 2 || !reason?.trim())
|
|
1192
|
+
throw new RafError('usage : cadence spec reseal <lot> <test> --reason "pourquoi le test contredit la fiche" [--porteur <lot en cours>, pour un lot livré]');
|
|
1193
|
+
if (io.env.CADENCE_ORCHESTRATED)
|
|
1194
|
+
throw new RafError(`spec reseal refusé pendant une vague orchestrée (${io.env.CADENCE_ORCHESTRATED}) : c'est le travail du lead, hors session`);
|
|
1195
|
+
refuseWhileWaveHolds(ctx, 'spec reseal');
|
|
1050
1196
|
const [lotId, test] = args;
|
|
1051
1197
|
const plan = ctx.loadPlan();
|
|
1052
1198
|
const lot = plan.lot(lotId);
|
|
@@ -1075,7 +1221,7 @@ function specReseal(args, reason, porteur, ctx, io) {
|
|
|
1075
1221
|
// Le verrou du rescellé couvre tous les lots ouverts du dépôt, pas seulement le porteur : on refuse dès qu'un lot DE CE DÉPÔT, encore en cours au plan, reste ouvert dans une vague non terminée.
|
|
1076
1222
|
const open = unfinishedWaves(ctx.root, repo, plan);
|
|
1077
1223
|
if (open.length)
|
|
1078
|
-
throw new RafError(openWavesRefusal(open));
|
|
1224
|
+
throw new RafError(openWavesRefusal(open, 'spec reseal'));
|
|
1079
1225
|
const before = { json: readFileSync(join(repo, json), 'utf8'), test: existsSync(join(repo, test)) ? readFileSync(join(repo, test), 'utf8') : '' };
|
|
1080
1226
|
let r;
|
|
1081
1227
|
try {
|
|
@@ -1109,14 +1255,23 @@ function specReseal(args, reason, porteur, ctx, io) {
|
|
|
1109
1255
|
return 0;
|
|
1110
1256
|
}
|
|
1111
1257
|
plan.note(cite, `test d'acceptation rescellé${livre ? ` (lot livré ${lotId}, ` : ' ('}${norm}, ${r.ficheAmendee ? `fiche amendée en ${r.ficheAmendee}` : 'fiche inchangée'}) : ${reason.trim()}`, ctx.today);
|
|
1112
|
-
|
|
1258
|
+
try {
|
|
1259
|
+
plan.save();
|
|
1260
|
+
}
|
|
1261
|
+
catch (e) {
|
|
1262
|
+
// Le rescellé est commité (sha) : seul le plan est refusé (plan.onWrite en échec), il reste inchangé et la note n'est pas écrite ; dire « écriture refusée » seul laisserait croire que rien n'a été fait.
|
|
1263
|
+
throw new RafError(`spec reseal : le rescellé a eu lieu (commit ${sha}) mais la note du lot n’a pas été écrite, le plan est inchangé — ${e.message} — relancer la commande dit « rien à resceller » : ajouter la note à la main (raf note ${cite}) une fois le plan réparé`);
|
|
1264
|
+
}
|
|
1113
1265
|
// La note du lot rejoint le dépôt dans la foulée (un dépôt sale arrêterait la vague suivante) ; un plan hors du dépôt ou non suivi reste à la main du lead.
|
|
1114
1266
|
const planRel = relative(repo, plan.path);
|
|
1267
|
+
const produced = plan.produced;
|
|
1115
1268
|
const tracked = !planRel.startsWith('..') && git(repo, ['ls-files', '--', planRel]).trim() !== '';
|
|
1116
1269
|
if (tracked) {
|
|
1117
1270
|
try {
|
|
1118
|
-
|
|
1119
|
-
|
|
1271
|
+
// Les fichiers de plan.onWrite (L346) partent avec la note : la commande a tourné à l'écriture du plan.
|
|
1272
|
+
const withPlan = [planRel, ...(produced.length ? dirtyPaths(repo, produced) : [])];
|
|
1273
|
+
git(repo, ['add', '--', ...withPlan]);
|
|
1274
|
+
git(repo, ['commit', '-q', '-m', planCommitMessage(readPlanConfig(join(repo, 'cadence.yaml'), repo)?.commit, { lot: cite, action: PLAN_ACTIONS.resealed(norm) }), '--', ...withPlan]);
|
|
1120
1275
|
}
|
|
1121
1276
|
catch (e) {
|
|
1122
1277
|
// Le rescellé est fait (commit ${sha}) : on ne le défait pas, on dit où en est la note, au lieu d'une pile d'appels.
|
|
@@ -1273,6 +1428,26 @@ function perf(rest, values, today, io) {
|
|
|
1273
1428
|
return 1;
|
|
1274
1429
|
}
|
|
1275
1430
|
}
|
|
1431
|
+
/** `verify --gate --sha rév [--project dossier]` (L348) : la porte du projet rejouée au sha ; `--project` est un sous-dossier du dossier courant qui a son propre dépôt. */
|
|
1432
|
+
async function verifyGateCommand(values, io) {
|
|
1433
|
+
if (values.sha === undefined)
|
|
1434
|
+
throw new RafError('verify --gate : --sha obligatoire (le sha à re-vérifier, jamais la tête mouvante d\'une vague)');
|
|
1435
|
+
if (values.retry !== undefined)
|
|
1436
|
+
throw new RafError('verify --gate : --retry ne va qu\'à verify seul (deliver.verify)');
|
|
1437
|
+
const dir = values.project === undefined ? io.cwd : resolve(io.cwd, values.project);
|
|
1438
|
+
if (values.project !== undefined && !existsSync(dir))
|
|
1439
|
+
throw new RafError(`verify --gate : --project ${values.project} : dossier introuvable sous ${io.cwd}`);
|
|
1440
|
+
const top = gitRoot(dir);
|
|
1441
|
+
if (!top)
|
|
1442
|
+
throw new RafError('verify --gate : à lancer dans un dépôt git (ou --project vers le dossier d\'un projet)');
|
|
1443
|
+
if (values.project !== undefined && realpathSync(top) !== realpathSync(dir))
|
|
1444
|
+
throw new RafError(`verify --gate : ${values.project} n'a pas son propre dépôt git (dépôt parent : ${top})`);
|
|
1445
|
+
const configPath = resolve(io.cwd, values.config ?? join(top, 'cadence.yaml'));
|
|
1446
|
+
const sha = resolveCommit(top, values.sha);
|
|
1447
|
+
if (!sha)
|
|
1448
|
+
throw new RafError(`verify --gate : --sha ${values.sha} : commit introuvable dans ${top}`);
|
|
1449
|
+
return verifyGate({ repo: top, sha, config: readOrchestrateConfig(configPath), out: io.out });
|
|
1450
|
+
}
|
|
1276
1451
|
/** `session start|close --all [--depth n]` : la commande jouée dans chaque projet sous le dossier courant, une section par projet. */
|
|
1277
1452
|
async function sessionAll(rest, values, io) {
|
|
1278
1453
|
const sub = rest[0];
|