@sylad/cadence 1.38.1 → 1.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -3
- package/dist/cli.js +4 -2
- package/dist/config-set.js +2 -0
- package/dist/cost.js +40 -0
- package/dist/memory-index.js +113 -0
- package/dist/orchestrate/ajout.js +87 -2
- package/dist/orchestrate/command.js +44 -49
- package/dist/orchestrate/continue.js +33 -0
- package/dist/orchestrate/cout.js +46 -0
- package/dist/orchestrate/cycle.js +10 -7
- package/dist/orchestrate/deja-traitee.js +76 -0
- package/dist/orchestrate/pool.js +1 -1
- package/dist/orchestrate/table.js +35 -14
- package/dist/perf.js +2 -15
- package/dist/release.js +18 -3
- package/dist/session.js +8 -0
- package/docs/concepts-livraison-dark.svg +32 -0
- package/docs/concepts-livraison-light.svg +32 -0
- package/docs/concepts-lot-dark.svg +52 -0
- package/docs/concepts-lot-light.svg +52 -0
- package/docs/concepts-vague-dark.svg +39 -0
- package/docs/concepts-vague-light.svg +39 -0
- package/docs/concepts-worktrees-dark.svg +45 -0
- package/docs/concepts-worktrees-light.svg +45 -0
- package/docs/concepts.md +159 -0
- package/docs/fonctionnement.md +2 -0
- package/docs/lead.md +3 -1
- package/docs/orchestrate.md +22 -6
- package/docs/overview.md +1 -1
- package/docs/perf.md +2 -0
- package/docs/releasing.md +4 -0
- package/docs/session.md +32 -0
- package/docs/whats-new.md +6 -0
- package/package.json +10 -1
- package/skills/lead/SKILL.md +6 -0
- package/skills/session-close/SKILL.md +6 -0
|
@@ -7,6 +7,8 @@ import { gitRoot } from '../git.js';
|
|
|
7
7
|
import { findProjects, isProject } from '../lead.js';
|
|
8
8
|
import { RafError } from '../plan.js';
|
|
9
9
|
import { lotBudget, projectEnv } from './command.js';
|
|
10
|
+
import { readyFree } from '../ready-free.js';
|
|
11
|
+
import { lotFinished, lotKey } from './state.js';
|
|
10
12
|
import { specScreen } from './spec.js';
|
|
11
13
|
import { STOP_REQUESTED } from './cycle.js';
|
|
12
14
|
/** `--until 18:00` : l'heure d'aujourd'hui (date de `now`) à partir de laquelle la vague ne tire plus de lot. */
|
|
@@ -153,3 +155,34 @@ export function stopReason(f) {
|
|
|
153
155
|
return 'budget épuisé';
|
|
154
156
|
return null;
|
|
155
157
|
}
|
|
158
|
+
/** Lots rendus ou en échec de suite, en comptant depuis le dernier lot de la vague. */
|
|
159
|
+
export function handedBackStreak(all) {
|
|
160
|
+
let n = 0;
|
|
161
|
+
for (const l of [...all].reverse()) {
|
|
162
|
+
if (l.status === 'handed-back' || l.status === 'failed')
|
|
163
|
+
n++;
|
|
164
|
+
else
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
return n;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* L527 : les lots que le créneau libre d'une vague `--continue` peut recevoir, dans l'ordre de priorité : ceux du plan que `candidates` tire (hors lots de la vague) et dont aucune ressource
|
|
171
|
+
* n'est prise (`readyFree` : lots en cours, vagues vivantes dont celle-ci). Vide quand une borne de `stopReason` joue (arrêt demandé, limite d'usage, question, deux lots rendus de suite, `--until`, budget).
|
|
172
|
+
* Le budget que les lots qui jouent encore n'ont pas dépensé reste réservé : le lot suivant doit tenir dans le reste (`room`, que `AddFeed.fill` décompte de chaque lot qu'il prend, comme les ressources `uses`).
|
|
173
|
+
*/
|
|
174
|
+
export function fillTargets(f) {
|
|
175
|
+
const reserved = f.all.filter((l) => !lotFinished(l)).reduce((n, l) => n + Math.max(0, (l.budget ?? 0) - l.steps.reduce((t, s) => t + (s.tokens?.counted ?? 0), 0)), 0);
|
|
176
|
+
const remaining = f.budget.limit - f.budget.consumed - reserved;
|
|
177
|
+
const why = stopReason({ now: f.io.now(), until: f.args.untilAt, quotaHit: f.quotaHit, questions: f.all.filter((l) => l.status === 'question').length, streak: handedBackStreak(f.all), interrupted: f.incident, stopRequested: f.store.control().stopAfterCurrent, remaining });
|
|
178
|
+
if (why)
|
|
179
|
+
return { targets: [], room: 0 };
|
|
180
|
+
const dirs = isProject(f.launch) ? [f.launch] : findProjects(f.launch);
|
|
181
|
+
const libres = readyFree(dirs, f.home);
|
|
182
|
+
const exclude = new Set(f.all.map((l) => lotKey(l.project, l.lot)));
|
|
183
|
+
const targets = candidates(f.launch, { priority: f.args.priority ?? readPriority(f.launch), exclude, remaining }).flatMap((c) => {
|
|
184
|
+
const repo = gitRoot(c.dir);
|
|
185
|
+
return repo && libres.get(c.dir)?.some((l) => l.id === c.lot.id) ? [{ project: c.project, projectDir: c.dir, repo, lot: c.lot.id, model: 'sonnet', uses: c.lot.uses ?? [] }] : [];
|
|
186
|
+
});
|
|
187
|
+
return { targets, room: remaining };
|
|
188
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { readPerfConfig } from '../config.js';
|
|
3
|
+
import { COUNTED_INPUT_SHARE, euros, stepEuros, sumEuros, waveEuros } from '../cost.js';
|
|
4
|
+
import { RafError } from '../plan.js';
|
|
5
|
+
/**
|
|
6
|
+
* La grille `perf.prices` du cadence.yaml du dossier de lancement (L529), la même que lit `cadence perf`. Une grille mal formée n'interdit ni une simulation
|
|
7
|
+
* ni un tableau (perdre le tableau d'une vague payée serait pire que d'en retirer les euros) : `warn` reçoit pourquoi, et la grille est absente.
|
|
8
|
+
*/
|
|
9
|
+
export function launchPrices(launch, warn) {
|
|
10
|
+
try {
|
|
11
|
+
return readPerfConfig(join(launch, 'cadence.yaml'));
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
if (!(e instanceof RafError))
|
|
15
|
+
throw e;
|
|
16
|
+
warn(`coût en euros n/d : ${e.message}`);
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const unknownText = (n) => `n/d pour ${n} lot${n > 1 ? 's' : ''}, modèle sans prix`;
|
|
21
|
+
/**
|
|
22
|
+
* L'estimation en euros de `--dry-run` : une ligne par lot puis une pour la vague. Le budget du lot (entrée + écriture de cache + sortie, mesuré au p90)
|
|
23
|
+
* est chiffré au modèle d'écriture du lot, `COUNTED_INPUT_SHARE` en entrée : la lecture de cache et les étapes d'un autre modèle (revue) n'y sont pas.
|
|
24
|
+
* Vide sans grille.
|
|
25
|
+
*/
|
|
26
|
+
export function estimateLines(lots, launch, warn) {
|
|
27
|
+
const prices = launchPrices(launch, warn);
|
|
28
|
+
if (!prices)
|
|
29
|
+
return [];
|
|
30
|
+
const per = lots.map((l) => {
|
|
31
|
+
const e = stepEuros({ model: l.model, counted: l.budget ?? 0 }, prices);
|
|
32
|
+
return typeof e === 'number' ? e : null;
|
|
33
|
+
});
|
|
34
|
+
const out = lots.map((l, i) => `${l.project}:${l.lot} — estimation du lot : ${per[i] === null ? `n/d (${l.model} sans prix dans perf.prices)` : `${euros(per[i])} (budget de ${l.budget ?? 0} tokens au prix ${l.model}, ${Math.round(COUNTED_INPUT_SHARE * 100)} % en entrée)`}`);
|
|
35
|
+
const sum = waveEuros(per);
|
|
36
|
+
out.push(`estimation de la vague : ${euros(sum.total)}${sum.unknown ? ` (${unknownText(sum.unknown)})` : ''} — base : le budget de chaque lot, mesuré au p90 comme lui`);
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
/** Le coût réel de `renderTable` (L529) : l'en-tête et les cellules de la colonne `€` (une par lot) et la fin de la ligne de la vague ; tout est vide sans grille. */
|
|
40
|
+
export function costView(lots, prices) {
|
|
41
|
+
if (!prices)
|
|
42
|
+
return { head: [], cells: lots.map(() => []), total: '' };
|
|
43
|
+
const per = lots.map((l) => sumEuros(l.steps.map((s) => ({ model: s.model, ...(s.tokens ? { usage: s.tokens } : {}) })), prices));
|
|
44
|
+
const sum = waveEuros(per);
|
|
45
|
+
return { head: ['€'], cells: per.map((e) => [e === null ? 'n/d' : euros(e)]), total: ` — ${euros(sum.total)}${sum.unknown ? ` (${unknownText(sum.unknown)})` : ''}` };
|
|
46
|
+
}
|
|
@@ -1300,18 +1300,21 @@ function summarize(rep, head, repoHeads) {
|
|
|
1300
1300
|
const majeurs = Math.max(rep.majeurs, count('majeur'));
|
|
1301
1301
|
return { conforme: bloquants === 0 && majeurs === 0, bloquants, majeurs, mineurs: Math.max(rep.mineurs, count('mineur')), verdict: rep.verdict.trim(), sousTaches: rep.sousTaches, nonVerifie: rep.nonVerifie, head, ...(repoHeads ? { repoHeads } : {}) };
|
|
1302
1302
|
}
|
|
1303
|
-
function propose(c, text) {
|
|
1303
|
+
function propose(c, text, head) {
|
|
1304
1304
|
if (!c.lot.proposals.includes(text))
|
|
1305
1305
|
c.lot.proposals.push(text);
|
|
1306
|
+
if (head)
|
|
1307
|
+
(c.lot.proposalHeads ??= {})[text] ??= head;
|
|
1306
1308
|
}
|
|
1307
1309
|
const minorLine = (source, k) => `[mineur ${source}] ${k.fichier ? `${k.fichier}${k.ligne ? `:${k.ligne}` : ''} — ` : ''}${k.texte}`;
|
|
1308
1310
|
const withoutLine = (line) => line.replace(/^(\[mineur \w+\] .*?):\d+ — /, '$1 — ');
|
|
1309
|
-
function addProposals(c, rep, source, withMinors = true) {
|
|
1311
|
+
function addProposals(c, rep, source, head, withMinors = true) {
|
|
1310
1312
|
if (withMinors)
|
|
1311
1313
|
for (const k of rep.constats.filter((k) => k.gravite === 'mineur'))
|
|
1312
1314
|
propose(c, minorLine(source, k));
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
+
// La première revue qui émet la proposition en garde la date : une revue suivante qui la redit ne la rajeunit pas.
|
|
1316
|
+
for (const t of rep.sousTaches.map((t) => `[sous-tâche ${source}] ${t}`))
|
|
1317
|
+
propose(c, t, head);
|
|
1315
1318
|
}
|
|
1316
1319
|
function minorConstats(rep) {
|
|
1317
1320
|
return rep.constats.filter((k) => k.gravite === 'mineur').map((k) => ({ source: 'code', gravite: k.gravite, fichier: k.fichier, ligne: k.ligne, texte: k.texte }));
|
|
@@ -1782,7 +1785,7 @@ async function reviewSpecStep(c, r) {
|
|
|
1782
1785
|
const summary = summarize(rep, done.after.head ?? '');
|
|
1783
1786
|
const passes = (l[r.kind]?.passes ?? 0) + 1;
|
|
1784
1787
|
// Fiche : les mineurs sont des notes du lot (design § 4). Maquette : inchangé (propositions du tableau final).
|
|
1785
|
-
addProposals(c, rep, r.kind, r.kind !== 'fiche');
|
|
1788
|
+
addProposals(c, rep, r.kind, summary.head, r.kind !== 'fiche');
|
|
1786
1789
|
// Un lot rendu sur le refus d'écrire ses notes (R26) rejoue cette revue à la reprise : le compte des passes n'est posé qu'une fois les notes écrites.
|
|
1787
1790
|
if (r.kind === 'fiche' && (await noteMinors(c, rep, `la ${r.label}`)))
|
|
1788
1791
|
return;
|
|
@@ -2515,7 +2518,7 @@ async function reviewStep(c, kind) {
|
|
|
2515
2518
|
const summary = summarize(rep, done.after.head ?? '', done.others.length ? Object.fromEntries(done.others.map((o) => [o.repo.rel, o.after.head ?? ''])) : undefined);
|
|
2516
2519
|
if (kind === 'ux') {
|
|
2517
2520
|
l.ux = summary;
|
|
2518
|
-
addProposals(c, rep, 'ux');
|
|
2521
|
+
addProposals(c, rep, 'ux', summary.head);
|
|
2519
2522
|
l.uxVerdict = summary.verdict;
|
|
2520
2523
|
l.constats = blockingConstats(rep, 'ux');
|
|
2521
2524
|
save(c);
|
|
@@ -2569,7 +2572,7 @@ async function reviewStep(c, kind) {
|
|
|
2569
2572
|
l.proposals = left;
|
|
2570
2573
|
l.minorLines = [];
|
|
2571
2574
|
}
|
|
2572
|
-
addProposals(c, rep, 'code', !minorPass);
|
|
2575
|
+
addProposals(c, rep, 'code', summary.head, !minorPass);
|
|
2573
2576
|
if (summary.conforme)
|
|
2574
2577
|
(l.reviewCounts ??= []).push(0); // une revue conforme compte pour 0 dans la comparaison de la convergence (L205)
|
|
2575
2578
|
if (minorPass) {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { changedFiles, isAncestor, resolveCommit } from '../git.js';
|
|
2
|
+
/** Début d'une proposition de sous-tâche d'une revue (`addProposals`) : les seules marquées, un mineur étant déjà une ligne de fichier précise. */
|
|
3
|
+
const SUBTASK = '[sous-tâche ';
|
|
4
|
+
/** Les fichiers que le texte d'une proposition cite (`src/a.ts`, `src/a.ts:42`, `` `docs/z.md` ``), dans l'ordre, sans doublon ; les adresses web et les nombres (`1.5`) n'en sont pas. */
|
|
5
|
+
export function citedFiles(text) {
|
|
6
|
+
const out = [];
|
|
7
|
+
for (const raw of text.split(/[\s`'"“”«»(),;*[\]<>]+/)) {
|
|
8
|
+
const tok = raw.replace(/(:\d+(-\d+)?)+$/, '').replace(/[.:!?]+$/, '').replace(/^(\.\/)+/, '');
|
|
9
|
+
if (!tok || tok.includes('://') || !/^[\p{L}\p{N}_@.\-/]+\.[A-Za-z][A-Za-z0-9]+$/u.test(tok) || out.includes(tok))
|
|
10
|
+
continue;
|
|
11
|
+
out.push(tok);
|
|
12
|
+
}
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* La lecture de git : parmi `commits` (sha du lot, du plus ancien au plus récent), ceux qui ne sont pas dans l'histoire de la revue
|
|
17
|
+
* `head`, puis le premier qui modifie `file` (chemin entier ou fin de chemin). Un `head` que le dépôt ne connaît pas, ou une
|
|
18
|
+
* lecture impossible, ne marque rien : mieux vaut une proposition sans marque qu'une marque fausse.
|
|
19
|
+
*/
|
|
20
|
+
export function touchedByLotCommits(commits) {
|
|
21
|
+
// Une lecture par lot et par commit pour tout le rendu : le tableau pose la question pour chaque couple (proposition, fichier cité).
|
|
22
|
+
const lotShas = new Map();
|
|
23
|
+
const files = new Map();
|
|
24
|
+
const filesOf = (repo, c) => {
|
|
25
|
+
const key = `${repo}\0${c}`;
|
|
26
|
+
if (!files.has(key)) {
|
|
27
|
+
try {
|
|
28
|
+
files.set(key, changedFiles(repo, c));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
files.set(key, null);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return files.get(key);
|
|
35
|
+
};
|
|
36
|
+
return (l, head, file) => {
|
|
37
|
+
if (!resolveCommit(l.repo, head))
|
|
38
|
+
return null;
|
|
39
|
+
if (!lotShas.has(l))
|
|
40
|
+
lotShas.set(l, commits(l));
|
|
41
|
+
for (const c of lotShas.get(l)) {
|
|
42
|
+
if (isAncestor(l.repo, c, head))
|
|
43
|
+
continue;
|
|
44
|
+
const changed = filesOf(l.repo, c);
|
|
45
|
+
if (!changed)
|
|
46
|
+
return null;
|
|
47
|
+
if (changed.some((f) => f === file || f.endsWith(`/${file}`)))
|
|
48
|
+
return c.slice(0, 7);
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* La ligne de proposition rendue au lead (L422) : une sous-tâche proposée dont un fichier cité a été modifié par un commit du lot
|
|
55
|
+
* postérieur à la revue qui l'a émise (`l.proposalHeads`) porte « — peut-être déjà traitée (<sha>) » ; les autres lignes sont inchangées.
|
|
56
|
+
* 28-10 : L230 ouvert sur des propositions déjà corrigées par la passe de correction, 5 passes perdues.
|
|
57
|
+
*/
|
|
58
|
+
export function proposalLine(l, proposal, touched) {
|
|
59
|
+
const head = l.proposalHeads?.[proposal];
|
|
60
|
+
if (!proposal.startsWith(SUBTASK) || !head)
|
|
61
|
+
return proposal;
|
|
62
|
+
for (const file of citedFiles(proposal)) {
|
|
63
|
+
let sha = null;
|
|
64
|
+
try {
|
|
65
|
+
sha = touched(l, head, file);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (sha)
|
|
71
|
+
return `${proposal} — peut-être déjà traitée (${sha})`;
|
|
72
|
+
}
|
|
73
|
+
return proposal;
|
|
74
|
+
}
|
|
75
|
+
/** La lecture à employer : celle que l'appelant donne (tests), sinon git sur les commits du lot (`lotCommits` de l'appelant, sinon `fromGit` : ceux des étapes). */
|
|
76
|
+
export const touchedReader = (deps, fromGit) => deps.touchedSince ?? touchedByLotCommits(deps.lotCommits ?? fromGit);
|
package/dist/orchestrate/pool.js
CHANGED
|
@@ -93,7 +93,7 @@ export async function runPool(ctxs, slots = SLOTS, known = [], onSettled = () =>
|
|
|
93
93
|
};
|
|
94
94
|
for (;;) {
|
|
95
95
|
admitted.length = 0;
|
|
96
|
-
for (const c of (await answers?.take(canRun)) ?? []) {
|
|
96
|
+
for (const c of (await answers?.take(canRun, Math.max(0, slots - running.size - pending.filter((q) => !blocked(q)).length))) ?? []) {
|
|
97
97
|
live.add(c);
|
|
98
98
|
pending.push([c]);
|
|
99
99
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { lotWork } from '../audit.js';
|
|
2
2
|
import { planLoader } from '../config.js';
|
|
3
|
+
import { costView } from './cout.js';
|
|
4
|
+
export { estimateLines, launchPrices } from './cout.js';
|
|
3
5
|
import { gitRoot, isAncestor } from '../git.js';
|
|
4
6
|
import { repoWork } from '../repos.js';
|
|
5
7
|
import { quotaReset } from './result.js';
|
|
8
|
+
import { proposalLine, touchedReader } from './deja-traitee.js';
|
|
6
9
|
const k = (n) => `${Math.round(n / 1000)} k`;
|
|
7
10
|
const m = (n) => `${(n / 1_000_000).toFixed(1).replace('.', ',')} M`;
|
|
8
11
|
export const LABEL = {
|
|
@@ -81,6 +84,15 @@ function gitCommitsOf(l) {
|
|
|
81
84
|
return [];
|
|
82
85
|
}
|
|
83
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Les commits que les étapes du lot ont notés (entrées `<sha> <sujet>`, du plus ancien au plus récent), à défaut ceux que git lui attribue :
|
|
89
|
+
* les sha de la revue (`proposalHeads`) et ceux-là sont du même état de la branche, alors que git, lu depuis le HEAD du dépôt, ne voit pas
|
|
90
|
+
* les commits d'un lot non intégré et ne retrouve, après l'intégration par rebase, que des commits réécrits.
|
|
91
|
+
*/
|
|
92
|
+
function reviewedCommitsOf(l) {
|
|
93
|
+
const noted = [...new Set(l.steps.flatMap((s) => s.commits ?? []).filter((c) => !c.startsWith('[')).map((c) => c.split(' ')[0]))];
|
|
94
|
+
return noted.length ? noted : gitCommitsOf(l);
|
|
95
|
+
}
|
|
84
96
|
const sameCommit = (a, b) => a.length >= 4 && b.length >= 4 && (a.startsWith(b) || b.startsWith(a));
|
|
85
97
|
/**
|
|
86
98
|
* Sha des commits du lot dans son dépôt : ceux que git attribue au lot (une session en échec, au rapport illisible ou coupée
|
|
@@ -204,7 +216,7 @@ function signalLines(l) {
|
|
|
204
216
|
return out;
|
|
205
217
|
}
|
|
206
218
|
/** Le détail d'un lot (L441) : ce que le tableau compact de `--status` n'imprime pas et que `--status --detail <lot>` donne. */
|
|
207
|
-
function detailLines(l) {
|
|
219
|
+
function detailLines(l, touched) {
|
|
208
220
|
const out = [];
|
|
209
221
|
if (l.goal)
|
|
210
222
|
out.push(`${l.project}:${l.lot} — objectif donné : ${l.goal}`);
|
|
@@ -226,7 +238,7 @@ function detailLines(l) {
|
|
|
226
238
|
for (const h of l.hypotheses ?? [])
|
|
227
239
|
out.push(`${l.project}:${l.lot} — hypothèse de la fiche : ${h}`);
|
|
228
240
|
for (const p of l.proposals)
|
|
229
|
-
out.push(`${l.project}:${l.lot} — proposé : ${p}`);
|
|
241
|
+
out.push(`${l.project}:${l.lot} — proposé : ${proposalLine(l, p, touched)}`);
|
|
230
242
|
if (l.uxVerdict && l.visible)
|
|
231
243
|
out.push(`${l.project}:${l.lot} — verdict UX pour « raf ux » : ${l.uxVerdict}`);
|
|
232
244
|
return out;
|
|
@@ -235,7 +247,7 @@ function detailLines(l) {
|
|
|
235
247
|
* `--status --detail <lot>` (L441) : les lignes de détail d'un lot de la vague, lues dans l'état de la vague (le fichier de chaque lot).
|
|
236
248
|
* `sel` : `projet:lot` ou le seul `lot` s'il est unique dans la vague. Lot inconnu ou ambigu : lève.
|
|
237
249
|
*/
|
|
238
|
-
export function renderDetail(lots, sel) {
|
|
250
|
+
export function renderDetail(lots, sel, deps = {}) {
|
|
239
251
|
const hit = lots.filter((l) => `${l.project}:${l.lot}` === sel);
|
|
240
252
|
const found = hit.length ? hit : sel.includes(':') ? [] : lots.filter((l) => l.lot === sel);
|
|
241
253
|
if (found.length === 0)
|
|
@@ -243,19 +255,34 @@ export function renderDetail(lots, sel) {
|
|
|
243
255
|
if (found.length > 1)
|
|
244
256
|
throw new Error(`lot ambigu : ${sel} (${found.map((l) => `${l.project}:${l.lot}`).join(', ')}) : donner projet:lot`);
|
|
245
257
|
const l = found[0];
|
|
246
|
-
const lines = detailLines(l);
|
|
258
|
+
const lines = detailLines(l, touchedReader(deps, reviewedCommitsOf));
|
|
247
259
|
return lines.length ? lines : [`${l.project}:${l.lot} — aucun détail (ni objectif, capture, choix, proposition, ni verdict UX)`];
|
|
248
260
|
}
|
|
261
|
+
/** Les lignes de signal et de détail de chaque lot, ajoutées à `out` ; rend les lots qui ont un détail. Compact : les lignes de détail ne sont pas imprimées, seul leur nombre compte, et la marque L422 n'en change pas le nombre : aucune lecture git. */
|
|
262
|
+
function perLotLines(lots, out, deps) {
|
|
263
|
+
const touched = deps.compact ? () => null : touchedReader(deps, reviewedCommitsOf);
|
|
264
|
+
const withDetail = [];
|
|
265
|
+
for (const l of lots) {
|
|
266
|
+
out.push(...signalLines(l));
|
|
267
|
+
const lines = detailLines(l, touched);
|
|
268
|
+
if (lines.length > 0)
|
|
269
|
+
withDetail.push(`${l.project}:${l.lot}`);
|
|
270
|
+
if (!deps.compact)
|
|
271
|
+
out.push(...lines);
|
|
272
|
+
}
|
|
273
|
+
return withDetail;
|
|
274
|
+
}
|
|
249
275
|
/** Le tableau de fin de vague (ou de `--status`), une ligne par lot puis le total et les questions. `compact` (L441, `--status`) : sans le détail des lots, une seule ligne renvoie à `--status --detail`. */
|
|
250
276
|
export function renderTable(wave, lots, deps = {}) {
|
|
251
|
-
const
|
|
252
|
-
const
|
|
277
|
+
const cost = costView(lots, deps.prices);
|
|
278
|
+
const rows = lots.map((l, i) => [`${l.project}:${l.lot}`, stateText(l, wave, deps.now ?? Date.now()), String(l.pass), review(l), ux(l), String(commits(l)), k(counted(l)), `${minutes(l)} min`, ...cost.cells[i], String(l.suiteRuns ?? '—')]);
|
|
279
|
+
const head = ['lot', 'état', 'passes', 'revue', 'UX', 'commits', 'tokens', 'durée', ...cost.head, 'suites'];
|
|
253
280
|
const widths = head.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
254
281
|
const line = (r) => r.map((c, i) => c.padEnd(widths[i])).join(' ').trimEnd();
|
|
255
282
|
const out = [line(head), ...rows.map(line)];
|
|
256
283
|
const by = (s) => lots.filter((l) => l.status === s).length;
|
|
257
284
|
const tail = [`${by('ready')} prêt(s)`, by('question') ? `${by('question')} question(s)` : '', by('handed-back') + by('failed') ? `${by('handed-back') + by('failed')} rendu(s) au lead` : '', by('suspended') ? `${by('suspended')} suspendu(s)` : ''].filter(Boolean);
|
|
258
|
-
out.push(`vague ${wave.id} : ${k(wave.consumed)} / ${m(wave.budget)} comptés (lecture de cache ${m(wave.cacheRead)}) — ${tail.join(', ')} — ${wave.status}`);
|
|
285
|
+
out.push(`vague ${wave.id} : ${k(wave.consumed)} / ${m(wave.budget)} comptés (lecture de cache ${m(wave.cacheRead)})${cost.total} — ${tail.join(', ')} — ${wave.status}`);
|
|
259
286
|
for (const l of lots.filter((x) => x.status === 'question')) {
|
|
260
287
|
out.push(`question : ${l.project}:${l.lot} — « ${l.questions.join(' / ')} » (${resumeAnswerHint(wave.id, l)})`);
|
|
261
288
|
}
|
|
@@ -267,13 +294,7 @@ export function renderTable(wave, lots, deps = {}) {
|
|
|
267
294
|
for (const d of deliverable(lots, deps.isAncestor ?? isAncestor, deps.lotCommits ?? gitCommitsOf)) {
|
|
268
295
|
out.push(`${d.ready.project}:${d.ready.lot} — livrable jusqu'à ${d.sha} (dernier commit de ${d.ready.lot}, sous ${d.under.lot} ${d.under.status === 'handed-back' || d.under.status === 'failed' ? 'rendu' : LABEL[d.under.status]}) : git push origin ${d.sha}:main && cadence deliver --sha ${d.sha}`);
|
|
269
296
|
}
|
|
270
|
-
|
|
271
|
-
out.push(...signalLines(l));
|
|
272
|
-
if (deps.compact)
|
|
273
|
-
continue;
|
|
274
|
-
out.push(...detailLines(l));
|
|
275
|
-
}
|
|
276
|
-
const withDetail = lots.filter((l) => detailLines(l).length > 0).map((l) => `${l.project}:${l.lot}`);
|
|
297
|
+
const withDetail = perLotLines(lots, out, deps);
|
|
277
298
|
if (deps.compact && withDetail.length)
|
|
278
299
|
out.push(`détail (objectif, captures, base de tests, choix, propositions, verdict UX) : cadence orchestrate --status --detail <projet:lot> — ${withDetail.join(', ')}`);
|
|
279
300
|
return out;
|
package/dist/perf.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join, relative, sep } from 'node:path';
|
|
2
2
|
import { planLoader } from './config.js';
|
|
3
|
+
import { euros, stepEuros } from './cost.js';
|
|
3
4
|
import { bugCodeOf, CAUSES, isReview as isReviewKind, sousCausesRanked } from './corrections.js';
|
|
4
5
|
import { addDays, diffDays, isDay, toDay } from './dates.js';
|
|
5
6
|
import { gitCommonDir } from './git.js';
|
|
@@ -50,20 +51,6 @@ function planLot(plans, project, lot) {
|
|
|
50
51
|
/** Rang d'affichage d'une sorte d'étape : l'ordre du cycle, les sortes inconnues après. */
|
|
51
52
|
const KIND_ORDER = ['spec', 'maquette', 'fiche', 'precheck', 'implement', 'probe', 'review', 'review-small', 'ux', 'fix', 'accept'];
|
|
52
53
|
const kindRank = (kind) => (KIND_ORDER.includes(kind) ? KIND_ORDER.indexOf(kind) : KIND_ORDER.length);
|
|
53
|
-
/** Part de `counted` rangée en entrée quand une étape ne porte que ce compteur (journal ancien) : le reste est en sortie (R8 b). */
|
|
54
|
-
const COUNTED_INPUT_SHARE = 0.9;
|
|
55
|
-
/** Coût en euros d'une étape (R8) ; le nom du modèle sans prix quand il en faudrait un. */
|
|
56
|
-
function stepEuros(step, prices) {
|
|
57
|
-
if (step.model === 'local')
|
|
58
|
-
return 0;
|
|
59
|
-
const usage = step.usage ?? (step.counted === undefined ? null : { input: COUNTED_INPUT_SHARE * step.counted, cacheWrite: 0, cacheRead: 0, output: (1 - COUNTED_INPUT_SHARE) * step.counted });
|
|
60
|
-
if (!usage)
|
|
61
|
-
return 0;
|
|
62
|
-
const price = step.model === 'sonnet' || step.model === 'opus' || step.model === 'haiku' ? prices[step.model] : undefined;
|
|
63
|
-
if (!price)
|
|
64
|
-
return { missing: step.model === 'sonnet' || step.model === 'opus' || step.model === 'haiku' ? step.model : 'inconnu' };
|
|
65
|
-
return ((usage.input + usage.cacheWrite) * price.input + usage.output * price.output + usage.cacheRead * price.cacheRead) / 1_000_000;
|
|
66
|
-
}
|
|
67
54
|
const median = (xs) => {
|
|
68
55
|
if (xs.length === 0)
|
|
69
56
|
return null;
|
|
@@ -569,7 +556,7 @@ function value(v, kind) {
|
|
|
569
556
|
if (kind === 'min')
|
|
570
557
|
return `${num(v)} min`;
|
|
571
558
|
if (kind === 'eur')
|
|
572
|
-
return
|
|
559
|
+
return euros(v);
|
|
573
560
|
return num(v);
|
|
574
561
|
}
|
|
575
562
|
const signed = (c) => `${c >= 0 ? '+' : '−'}${Math.round(100 * Math.abs(c))} %`;
|
package/dist/release.js
CHANGED
|
@@ -184,15 +184,30 @@ function whatsNewIssues(text, version) {
|
|
|
184
184
|
const at = hits.findIndex((h) => h.v === version);
|
|
185
185
|
if (at < 0) {
|
|
186
186
|
issues.push(`${WHATS_NEW} : ${form} ${absent} — l'écrire ${where}`);
|
|
187
|
-
return;
|
|
187
|
+
return -1;
|
|
188
188
|
}
|
|
189
189
|
if (at > 0)
|
|
190
190
|
issues.push(`${WHATS_NEW} : ${form} ${misplaced} (après la version ${hits[at - 1].v} ; la plus récente va en premier) — la déplacer ${where}`);
|
|
191
191
|
if (max !== undefined && hits[at].l.length > max)
|
|
192
192
|
issues.push(`${WHATS_NEW} : ${form} trop longue (${hits[at].l.length} caractères, ${max} au plus)`);
|
|
193
|
+
return hits[at].i;
|
|
193
194
|
};
|
|
194
|
-
check('ligne de « In two lines »', 'manquante', 'mal placée', /^- \*\*(\d+\.\d+\.\d+)\*\*:/, 0, split, 'en tête de la liste', SHORT_LINE_MAX);
|
|
195
|
+
const own = check('ligne de « In two lines »', 'manquante', 'mal placée', /^- \*\*(\d+\.\d+\.\d+)\*\*:/, 0, split, 'en tête de la liste', SHORT_LINE_MAX);
|
|
195
196
|
check('paragraphe long', 'manquant', 'mal placé', /^\*\*(\d+\.\d+\.\d+)\*\*:/, split, lines.length, 'juste après l’introduction de « Version by version »');
|
|
197
|
+
// Le test « deux lignes » du dépôt lit toute la section « In two lines », jusqu'au prochain titre « ## », et toute ligne `- **x.y.z**` (deux-points ou non) :
|
|
198
|
+
// les autres lignes que celle de la version qui sort comptent (1.22.0, 28-10 : un paragraphe long y était tombé, la publication a échoué).
|
|
199
|
+
const start = Math.max(0, lines.findIndex((l) => /^##\s+In two lines\s*$/.test(l)));
|
|
200
|
+
const next = lines.findIndex((l, i) => i > start && l.startsWith('## '));
|
|
201
|
+
const end = next < 0 ? lines.length : next;
|
|
202
|
+
lines.slice(start, end).forEach((l, k) => {
|
|
203
|
+
const i = start + k;
|
|
204
|
+
const bullet = /^- \*\*(\d+\.\d+\.\d+)\*\*/.exec(l);
|
|
205
|
+
if (bullet && i !== own && l.length > SHORT_LINE_MAX)
|
|
206
|
+
issues.push(`${WHATS_NEW} : « In two lines » : la ligne de la version ${bullet[1]} fait ${l.length} caractères, ${SHORT_LINE_MAX} au plus — la raccourcir (le détail va dans « Version by version »)`);
|
|
207
|
+
const long = /^\*\*(\d+\.\d+\.\d+)\*\*:/.exec(l);
|
|
208
|
+
if (long)
|
|
209
|
+
issues.push(`${WHATS_NEW} : « In two lines » : le paragraphe long de la version ${long[1]} n’y a pas sa place (ligne ${i + 1}) — le mettre dans « Version by version »`);
|
|
210
|
+
});
|
|
196
211
|
return issues;
|
|
197
212
|
}
|
|
198
213
|
/**
|
|
@@ -256,7 +271,7 @@ export function release(rest, dryRun, root, today, out) {
|
|
|
256
271
|
: ' le commit de version n\'est pas prêt : écrire ces formes (à la main ou par une session), puis suivre les étapes ci-dessous sans relancer la commande (elle changerait encore la version)');
|
|
257
272
|
}
|
|
258
273
|
}
|
|
259
|
-
out('étapes du lead, dans l\'ordre (la commande n\'en joue aucune) :');
|
|
274
|
+
out('étapes du lead, dans l\'ordre (la commande n\'en joue aucune ; chacune se lit par son code de sortie : jamais de pipe (`| tail`, `| grep`) derrière une commande dont le code de sortie compte, il lui substitue celui du filtre) :');
|
|
260
275
|
for (const s of steps(p, files, branch))
|
|
261
276
|
out(s);
|
|
262
277
|
return ready ? 0 : 1;
|
package/dist/session.js
CHANGED
|
@@ -146,8 +146,14 @@ export function sessionStart(ctx, opts) {
|
|
|
146
146
|
writeSessionMark(ctx.state, { kind: 'start', at: (ctx.now ?? new Date()).toISOString() });
|
|
147
147
|
return 0;
|
|
148
148
|
}
|
|
149
|
+
/** La section « Mémoire » (L442), calculée avant tout rapport ; vide sans mesure demandée. */
|
|
150
|
+
function memoryLines(ctx) {
|
|
151
|
+
return ctx.memory?.() ?? [];
|
|
152
|
+
}
|
|
149
153
|
export function sessionClose(ctx, opts) {
|
|
150
154
|
const { plan, out, today } = ctx;
|
|
155
|
+
// Mesuré avant tout rapport : une clé memory.indexMaxBytes invalide arrête la clôture sans rien imprimer.
|
|
156
|
+
const memory = memoryLines(ctx);
|
|
151
157
|
out(`${plan.project} — clôture du ${today}`);
|
|
152
158
|
const window = closeWindow(ctx, opts.since);
|
|
153
159
|
const recent = period(ctx, window.since);
|
|
@@ -169,6 +175,8 @@ export function sessionClose(ctx, opts) {
|
|
|
169
175
|
const lock = lockStatus(ctx.shared);
|
|
170
176
|
section(out, 'Dépôt', [repo.line, ...(lock ? [lock.line] : [])]);
|
|
171
177
|
section(out, 'Faits propres au projet', projectFacts(ctx, window.since));
|
|
178
|
+
// Comme le nettoyage : un avertissement qui propose, hors des points ouverts ; l'index n'est jamais écrit ici.
|
|
179
|
+
memory.forEach((l) => out(l));
|
|
172
180
|
// Une proposition, pas une condition : rien n'est supprimé ici, le skill demande l'accord. Le
|
|
173
181
|
// nettoyage ne change jamais le verdict et ne fait jamais tomber la clôture.
|
|
174
182
|
if (ctx.clean?.patterns.length) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 300" width="1100" height="300" role="img" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif" font-size="13" fill="#e6edf3">
|
|
2
|
+
<title>Delivery up to a sha</title>
|
|
3
|
+
<desc>Commits stack on main in order; the deliverable sha is the last commit of the highest ready lot with no unfinished lot below it, so a handed-back lot blocks the commits above it.</desc>
|
|
4
|
+
<defs><marker id="arr" 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="1100" height="300" fill="#0d1117"/>
|
|
6
|
+
<text x="20" y="30" text-anchor="start" font-size="11.5" font-weight="600" fill="#8b949e">main, oldest on the left</text>
|
|
7
|
+
<rect x="40" y="60" width="150" height="60" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
8
|
+
<text x="115" y="86" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">a1 · L1</text>
|
|
9
|
+
<text x="115" y="103" text-anchor="middle" font-size="11.5" fill="#8b949e">ready</text>
|
|
10
|
+
<polyline points="190,90 238,90" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/>
|
|
11
|
+
<rect x="240" y="60" width="150" height="60" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
12
|
+
<text x="315" y="86" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">a2 · L1</text>
|
|
13
|
+
<text x="315" y="103" text-anchor="middle" font-size="11.5" fill="#8b949e">ready</text>
|
|
14
|
+
<polyline points="390,90 438,90" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/>
|
|
15
|
+
<rect x="440" y="60" width="150" height="60" rx="6" fill="#0c2d6b" stroke="#58a6ff" stroke-width="2"/>
|
|
16
|
+
<text x="515" y="86" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">b1 · L2</text>
|
|
17
|
+
<text x="515" y="103" text-anchor="middle" font-size="11.5" fill="#8b949e">ready</text>
|
|
18
|
+
<polyline points="590,90 638,90" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/>
|
|
19
|
+
<rect x="640" y="60" width="150" height="60" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5" stroke-dasharray="6 4"/>
|
|
20
|
+
<text x="715" y="86" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">c1 · L3</text>
|
|
21
|
+
<text x="715" y="103" text-anchor="middle" font-size="11.5" fill="#8b949e">handed back</text>
|
|
22
|
+
<polyline points="790,90 838,90" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/>
|
|
23
|
+
<rect x="840" y="60" width="150" height="60" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5" stroke-dasharray="6 4"/>
|
|
24
|
+
<text x="915" y="86" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">d1 · L4</text>
|
|
25
|
+
<text x="915" y="103" text-anchor="middle" font-size="11.5" fill="#8b949e">ready</text>
|
|
26
|
+
<rect x="40" y="136" width="550" height="4" fill="#58a6ff"/>
|
|
27
|
+
<text x="315" y="160" text-anchor="middle" font-size="11.5" fill="#e6edf3">delivered together</text>
|
|
28
|
+
<text x="315" y="205" text-anchor="middle" font-size="13" font-weight="600" fill="#e6edf3">« livrable jusqu'à b1 »</text>
|
|
29
|
+
<text x="315" y="227" text-anchor="middle" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#8b949e">git push origin b1:main && cadence deliver --sha b1</text>
|
|
30
|
+
<text x="840" y="160" text-anchor="middle" font-size="11.5" fill="#8b949e">blocked by L3: not pushed, not delivered</text>
|
|
31
|
+
<text x="550" y="270" text-anchor="middle" font-size="11.5" fill="#8b949e">Pushing HEAD would also put the unreviewed lot on main; the CI builds only the tip of a push.</text>
|
|
32
|
+
</svg>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 300" width="1100" height="300" role="img" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif" font-size="13" fill="#1f2328">
|
|
2
|
+
<title>Delivery up to a sha</title>
|
|
3
|
+
<desc>Commits stack on main in order; the deliverable sha is the last commit of the highest ready lot with no unfinished lot below it, so a handed-back lot blocks the commits above it.</desc>
|
|
4
|
+
<defs><marker id="arr" 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="1100" height="300" fill="#ffffff"/>
|
|
6
|
+
<text x="20" y="30" text-anchor="start" font-size="11.5" font-weight="600" fill="#57606a">main, oldest on the left</text>
|
|
7
|
+
<rect x="40" y="60" width="150" height="60" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
|
|
8
|
+
<text x="115" y="86" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">a1 · L1</text>
|
|
9
|
+
<text x="115" y="103" text-anchor="middle" font-size="11.5" fill="#57606a">ready</text>
|
|
10
|
+
<polyline points="190,90 238,90" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr)"/>
|
|
11
|
+
<rect x="240" y="60" width="150" height="60" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
|
|
12
|
+
<text x="315" y="86" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">a2 · L1</text>
|
|
13
|
+
<text x="315" y="103" text-anchor="middle" font-size="11.5" fill="#57606a">ready</text>
|
|
14
|
+
<polyline points="390,90 438,90" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr)"/>
|
|
15
|
+
<rect x="440" y="60" width="150" height="60" rx="6" fill="#ddf4ff" stroke="#0969da" stroke-width="2"/>
|
|
16
|
+
<text x="515" y="86" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">b1 · L2</text>
|
|
17
|
+
<text x="515" y="103" text-anchor="middle" font-size="11.5" fill="#57606a">ready</text>
|
|
18
|
+
<polyline points="590,90 638,90" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr)"/>
|
|
19
|
+
<rect x="640" y="60" width="150" height="60" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5" stroke-dasharray="6 4"/>
|
|
20
|
+
<text x="715" y="86" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">c1 · L3</text>
|
|
21
|
+
<text x="715" y="103" text-anchor="middle" font-size="11.5" fill="#57606a">handed back</text>
|
|
22
|
+
<polyline points="790,90 838,90" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr)"/>
|
|
23
|
+
<rect x="840" y="60" width="150" height="60" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5" stroke-dasharray="6 4"/>
|
|
24
|
+
<text x="915" y="86" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">d1 · L4</text>
|
|
25
|
+
<text x="915" y="103" text-anchor="middle" font-size="11.5" fill="#57606a">ready</text>
|
|
26
|
+
<rect x="40" y="136" width="550" height="4" fill="#0969da"/>
|
|
27
|
+
<text x="315" y="160" text-anchor="middle" font-size="11.5" fill="#1f2328">delivered together</text>
|
|
28
|
+
<text x="315" y="205" text-anchor="middle" font-size="13" font-weight="600" fill="#1f2328">« livrable jusqu'à b1 »</text>
|
|
29
|
+
<text x="315" y="227" text-anchor="middle" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#57606a">git push origin b1:main && cadence deliver --sha b1</text>
|
|
30
|
+
<text x="840" y="160" text-anchor="middle" font-size="11.5" fill="#57606a">blocked by L3: not pushed, not delivered</text>
|
|
31
|
+
<text x="550" y="270" text-anchor="middle" font-size="11.5" fill="#57606a">Pushing HEAD would also put the unreviewed lot on main; the CI builds only the tip of a push.</text>
|
|
32
|
+
</svg>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 300" width="1100" height="300" role="img" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif" font-size="13" fill="#e6edf3">
|
|
2
|
+
<title>Journey of a lot</title>
|
|
3
|
+
<desc>A lot goes through spec, code, review, correction when the review is not compliant, UX if the lot is visible, then ready; below each step, who does it.</desc>
|
|
4
|
+
<defs><marker id="arr" 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="1100" height="300" fill="#0d1117"/>
|
|
6
|
+
<text x="20" y="26" text-anchor="start" font-size="11.5" font-weight="600" fill="#8b949e">STEP</text>
|
|
7
|
+
<text x="20" y="168" text-anchor="start" font-size="11.5" font-weight="600" fill="#8b949e">WHO</text>
|
|
8
|
+
<rect x="20" y="40" width="160" height="60" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5" stroke-dasharray="6 4"/>
|
|
9
|
+
<text x="100" y="66" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">spec</text>
|
|
10
|
+
<text x="100" y="83" text-anchor="middle" font-size="11.5" fill="#8b949e">if screen or spec: true</text>
|
|
11
|
+
<rect x="200" y="40" width="160" height="60" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
12
|
+
<text x="280" y="66" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">code</text>
|
|
13
|
+
<text x="280" y="83" text-anchor="middle" font-size="11.5" fill="#8b949e">test first</text>
|
|
14
|
+
<rect x="380" y="40" width="160" height="60" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
|
|
15
|
+
<text x="460" y="66" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">review</text>
|
|
16
|
+
<text x="460" y="83" text-anchor="middle" font-size="11.5" fill="#8b949e">code</text>
|
|
17
|
+
<rect x="560" y="40" width="160" height="60" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5" stroke-dasharray="6 4"/>
|
|
18
|
+
<text x="640" y="66" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">correction</text>
|
|
19
|
+
<text x="640" y="83" text-anchor="middle" font-size="11.5" fill="#8b949e">if not compliant</text>
|
|
20
|
+
<rect x="740" y="40" width="160" height="60" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5" stroke-dasharray="6 4"/>
|
|
21
|
+
<text x="820" y="66" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">UX</text>
|
|
22
|
+
<text x="820" y="83" text-anchor="middle" font-size="11.5" fill="#8b949e">if visible</text>
|
|
23
|
+
<rect x="920" y="40" width="160" height="60" rx="6" fill="#0c2d6b" stroke="#58a6ff" stroke-width="2"/>
|
|
24
|
+
<text x="1000" y="66" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">ready</text>
|
|
25
|
+
<text x="1000" y="83" text-anchor="middle" font-size="11.5" fill="#8b949e">integrated (worktree mode)</text>
|
|
26
|
+
<polyline points="180,70 198,70" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/>
|
|
27
|
+
<polyline points="360,70 378,70" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/>
|
|
28
|
+
<polyline points="540,70 558,70" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/>
|
|
29
|
+
<polyline points="720,70 738,70" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/>
|
|
30
|
+
<polyline points="900,70 918,70" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/>
|
|
31
|
+
<polyline points="640,40 640,22 460,22 460,38" fill="none" stroke="#8b949e" stroke-width="1.5" stroke-dasharray="5 3" marker-end="url(#arr)"/>
|
|
32
|
+
<text x="550" y="16" text-anchor="middle" font-size="11.5" fill="#8b949e">back to the review after each correction</text>
|
|
33
|
+
<rect x="20" y="180" width="160" height="56" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1"/>
|
|
34
|
+
<text x="100" y="204" text-anchor="middle" font-size="12.5" font-weight="600" fill="#e6edf3">spec-writer</text>
|
|
35
|
+
<text x="100" y="222" text-anchor="middle" font-size="11.5" fill="#8b949e">then reviewer</text>
|
|
36
|
+
<rect x="200" y="180" width="160" height="56" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1"/>
|
|
37
|
+
<text x="280" y="204" text-anchor="middle" font-size="12.5" font-weight="600" fill="#e6edf3">fresh session</text>
|
|
38
|
+
<text x="280" y="222" text-anchor="middle" font-size="11.5" fill="#8b949e">of the program</text>
|
|
39
|
+
<rect x="380" y="180" width="160" height="56" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1"/>
|
|
40
|
+
<text x="460" y="204" text-anchor="middle" font-size="12.5" font-weight="600" fill="#e6edf3">code-reviewer</text>
|
|
41
|
+
<text x="460" y="222" text-anchor="middle" font-size="11.5" fill="#8b949e">agent</text>
|
|
42
|
+
<rect x="560" y="180" width="160" height="56" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1"/>
|
|
43
|
+
<text x="640" y="204" text-anchor="middle" font-size="12.5" font-weight="600" fill="#e6edf3">fresh session</text>
|
|
44
|
+
<text x="640" y="222" text-anchor="middle" font-size="11.5" fill="#8b949e">2 passes, 3 if it converges</text>
|
|
45
|
+
<rect x="740" y="180" width="160" height="56" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1"/>
|
|
46
|
+
<text x="820" y="204" text-anchor="middle" font-size="12.5" font-weight="600" fill="#e6edf3">ux-reviewer</text>
|
|
47
|
+
<text x="820" y="222" text-anchor="middle" font-size="11.5" fill="#8b949e">then the human</text>
|
|
48
|
+
<rect x="920" y="180" width="160" height="56" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1"/>
|
|
49
|
+
<text x="1000" y="204" text-anchor="middle" font-size="12.5" font-weight="600" fill="#e6edf3">the lead</text>
|
|
50
|
+
<text x="1000" y="222" text-anchor="middle" font-size="11.5" fill="#8b949e">re-checks, raf done</text>
|
|
51
|
+
<text x="550" y="270" text-anchor="middle" font-size="11.5" fill="#8b949e">Dashed: a conditional step. The program chains the sessions; the verdicts of the review and of the UX pass are recorded in the plan.</text>
|
|
52
|
+
</svg>
|