@sylad/cadence 1.39.0 → 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/memory-index.js +113 -0
- package/dist/orchestrate/cycle.js +10 -7
- package/dist/orchestrate/deja-traitee.js +76 -0
- package/dist/orchestrate/table.js +29 -11
- 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 +4 -0
- package/docs/overview.md +1 -1
- package/docs/session.md +32 -0
- package/docs/whats-new.md +3 -0
- package/package.json +10 -1
- package/skills/lead/SKILL.md +6 -0
- package/skills/session-close/SKILL.md +6 -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.40.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": "1.
|
|
4
|
+
"version": "1.40.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
|
@@ -4,6 +4,7 @@ A small working method that lives in your repository: a versioned plan linked to
|
|
|
4
4
|
waves of fresh AI sessions driven by a program, and deliveries proved by their effect.
|
|
5
5
|
It is for solo developers and AI-assisted sessions who lose track of *what is left to do* and *what each commit was for*:
|
|
6
6
|
cadence keeps both in plain files next to the code, and [Claude Code](https://claude.com/claude-code) skills turn it into rituals.
|
|
7
|
+
New to the words (lot, wave, hold…)? [docs/concepts.md](docs/concepts.md) explains each notion, with four figures.
|
|
7
8
|
|
|
8
9
|
## Start
|
|
9
10
|
|
|
@@ -15,8 +16,7 @@ raf init
|
|
|
15
16
|
raf add "My first lot" --estimate 1
|
|
16
17
|
```
|
|
17
18
|
|
|
18
|
-
Then `raf start L1`, commit with `feat(L1): …`, `raf now` to see where you are, `raf done L1` when
|
|
19
|
-
`raf -v` prints the installed version. Every command is in [docs/raf.md](docs/raf.md).
|
|
19
|
+
Then `raf start L1`, commit with `feat(L1): …`, `raf now` to see where you are, `raf done L1` when finished. Every command: [docs/raf.md](docs/raf.md).
|
|
20
20
|
|
|
21
21
|
## Lead several projects in 10 minutes
|
|
22
22
|
|
|
@@ -53,7 +53,7 @@ Details: [docs/skills.md](docs/skills.md) and [docs/cadence-hud.md](docs/cadence
|
|
|
53
53
|
- [docs/deliver.md](docs/deliver.md): wait for the CI, deploy, verify the effect; [docs/remote-gate.md](docs/remote-gate.md): the gate on another machine.
|
|
54
54
|
- [docs/spec.md](docs/spec.md): screen and back-end sheets.
|
|
55
55
|
- [docs/ref.md](docs/ref.md): reference documents (specs, norms, mock-ups) and the coverage of their elements by the lots.
|
|
56
|
-
- [docs/lead.md](docs/lead.md): the lead workflow,
|
|
56
|
+
- [docs/lead.md](docs/lead.md): the lead workflow, the `livreur` agent, and [the memory index warning](docs/session.md#memory-index-size-l442) (L442).
|
|
57
57
|
- [docs/overview.md](docs/overview.md): the longer introduction and the map of every page; [docs/fonctionnement.md](docs/fonctionnement.md): how it works.
|
|
58
58
|
- [docs/stabilite.md](docs/stabilite.md): what is guaranteed from 1.0.0; [docs/qualite.md](docs/qualite.md): the code-quality ratchet.
|
|
59
59
|
- [docs/whats-new.md](docs/whats-new.md) and [CHANGELOG.md](CHANGELOG.md): what changed.
|
package/dist/cli.js
CHANGED
|
@@ -24,6 +24,7 @@ import { schedule } from './schedule.js';
|
|
|
24
24
|
import { AGENTS_DIR, installAgents, installSkills, SKILLS_DIR } from './skills.js';
|
|
25
25
|
import { configSet } from './config-set.js';
|
|
26
26
|
import { movePriority } from './priority.js';
|
|
27
|
+
import { closeMemoryLines, tourMemoryLines } from './memory-index.js';
|
|
27
28
|
import { findProjects, isProject, readDepth, leadTour, TITLE_MAX, truncate, readLeadUx, tourFreeLine, readLeadContextThreshold, findLeadDir, tourLine, tourNoTestLines, tourWorktreeLines } from './lead.js';
|
|
28
29
|
import { configuredCap, expiredHoldLines, slotsLine, orchestrate, realOrchestrateDeps } from './orchestrate/command.js';
|
|
29
30
|
import { addHold, releaseHold } from './orchestrate/registry.js';
|
|
@@ -270,7 +271,8 @@ function dispatch(argv, io) {
|
|
|
270
271
|
for (const r of rows)
|
|
271
272
|
io.out(tourLine(r));
|
|
272
273
|
// les worktrees de lot que des vagues ont conservés : aucun n'est oublié en silence (L279, R19)
|
|
273
|
-
|
|
274
|
+
// … puis l'index MEMORY.md du dossier, s'il pèse trop (L442) : le lead propose le regroupement, rien n'est écrit
|
|
275
|
+
for (const l of [...tourWorktreeLines(rows, io.now().getTime()), ...tourMemoryLines(io.env, parent)])
|
|
274
276
|
io.out(l);
|
|
275
277
|
// un projet sans orchestrate.test : la vague ne lancera pas la suite (L327)
|
|
276
278
|
for (const l of tourNoTestLines(rows))
|
|
@@ -779,7 +781,7 @@ function dispatch(argv, io) {
|
|
|
779
781
|
const sconf = rest[0] === 'start' || rest[0] === 'close' ? readSessionConfig(configPath) : {};
|
|
780
782
|
const facts = rest[0] === 'start' || rest[0] === 'close' ? sconf[rest[0]] : undefined;
|
|
781
783
|
const clean = rest[0] === 'close' && sconf.clean ? { patterns: sconf.clean, days: sconf.cleanDays } : undefined;
|
|
782
|
-
const sctx = { plan: loadPlan(), root, newsDir, state: stateDir(root), shared: sharedStateDir(root), today, now: io.now(), out: io.out, facts, clean };
|
|
784
|
+
const sctx = { plan: loadPlan(), root, newsDir, state: stateDir(root), shared: sharedStateDir(root), today, now: io.now(), out: io.out, facts, clean, memory: closeMemoryLines(io.env, io.cwd, configPath) };
|
|
783
785
|
if (rest[0] !== 'start')
|
|
784
786
|
return session(rest, sctx, values);
|
|
785
787
|
// La reprise rejoue les vérifications d'effet (un essai, borné) : un effet rouge d'hier se lit avec les faits du matin.
|
package/dist/config-set.js
CHANGED
|
@@ -7,6 +7,7 @@ import { defaultPlan, readDocsConfig, readHookConfig, readMigrateConfig, readNew
|
|
|
7
7
|
import { parseDeliverConfig } from './deliver.js';
|
|
8
8
|
import { commitPaths, dirtyPaths, gitRoot, restorePaths } from './git.js';
|
|
9
9
|
import { findProjects, isProject, readDepth, readLeadContextThreshold, readLeadUx } from './lead.js';
|
|
10
|
+
import { readMemoryIndexMax } from './memory-index.js';
|
|
10
11
|
import { readPriority } from './orchestrate/continue.js';
|
|
11
12
|
import { Plan, RafError } from './plan.js';
|
|
12
13
|
const KEY = /^[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)*$/;
|
|
@@ -25,6 +26,7 @@ export function validateConfig(file, root) {
|
|
|
25
26
|
readDocsConfig(file);
|
|
26
27
|
readReferencesConfig(file);
|
|
27
28
|
readMigrateConfig(file);
|
|
29
|
+
readMemoryIndexMax(file);
|
|
28
30
|
readLeadUx(dirname(file));
|
|
29
31
|
readLeadContextThreshold(dirname(file));
|
|
30
32
|
readDepth(dirname(file));
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parse } from 'yaml';
|
|
4
|
+
import { RafError } from './plan.js';
|
|
5
|
+
import { claudeConfigDir, projectLogDir } from './orchestrate/launch.js';
|
|
6
|
+
/** Taille (octets) au-delà de laquelle l'index MEMORY.md est signalé quand `memory.indexMaxBytes` n'est pas posé (L442). */
|
|
7
|
+
const MEMORY_INDEX_MAX_BYTES = 12_000;
|
|
8
|
+
/** L'index de la mémoire de Claude Code du dossier `dir` (celui où la session est lancée, jamais la racine git). */
|
|
9
|
+
function memoryIndexPath(env, dir) {
|
|
10
|
+
return join(projectLogDir(claudeConfigDir(env), dir), 'memory', 'MEMORY.md');
|
|
11
|
+
}
|
|
12
|
+
// « - », puis au plus « ** », « [ » et « ⚠ », puis un jour « jj-mm » : le nom du fichier lié ne compte pas.
|
|
13
|
+
const DATED = /^- (?:\*\*)?\[?(?:⚠️?\s*)?(?:0[1-9]|[12]\d|3[01])-(?:0[1-9]|1[0-2])(?!\d)/;
|
|
14
|
+
/** Les entrées datées d'un index : leur nombre et leur poids en octets (fin de ligne comprise). */
|
|
15
|
+
function datedEntries(text) {
|
|
16
|
+
let count = 0;
|
|
17
|
+
let bytes = 0;
|
|
18
|
+
const lines = text.replace(/^\uFEFF/, '').split('\n');
|
|
19
|
+
for (const [i, line] of lines.entries()) {
|
|
20
|
+
const bare = line.replace(/\r$/, '');
|
|
21
|
+
if (!DATED.test(bare))
|
|
22
|
+
continue;
|
|
23
|
+
count++;
|
|
24
|
+
// la fin de ligne ne compte que si elle existe : la dernière ligne peut ne pas en avoir
|
|
25
|
+
bytes += Buffer.byteLength(line) + (i < lines.length - 1 ? 1 : 0);
|
|
26
|
+
}
|
|
27
|
+
return { count, bytes };
|
|
28
|
+
}
|
|
29
|
+
/** Mesure l'index : absent ou au plus égal au seuil = `ok` ; une lecture impossible se dit au lieu de lever. Ne modifie rien. */
|
|
30
|
+
function checkMemoryIndex(env, dir, max) {
|
|
31
|
+
let buf;
|
|
32
|
+
try {
|
|
33
|
+
buf = readFileSync(memoryIndexPath(env, dir));
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
const code = e.code;
|
|
37
|
+
return code === 'ENOENT' ? { kind: 'ok' } : { kind: 'unreadable', cause: e.message.split('\n')[0] };
|
|
38
|
+
}
|
|
39
|
+
if (buf.length <= max)
|
|
40
|
+
return { kind: 'ok' };
|
|
41
|
+
return { kind: 'over', size: buf.length, max, ...datedEntries(buf.toString('utf8')) };
|
|
42
|
+
}
|
|
43
|
+
const NO_DATED = "aucune entrée datée à regrouper dans l'index MEMORY.md";
|
|
44
|
+
/** Les lignes de la section « Mémoire » de `session close`, précédée d'une ligne vide (aucune quand l'index est sain). */
|
|
45
|
+
function closeLines(c) {
|
|
46
|
+
if (c.kind === 'ok')
|
|
47
|
+
return [];
|
|
48
|
+
if (c.kind === 'unreadable')
|
|
49
|
+
return [`\nMémoire : l'index MEMORY.md est illisible (${c.cause}), sa taille n'est pas contrôlée`];
|
|
50
|
+
return [
|
|
51
|
+
`\nMémoire : l'index MEMORY.md pèse ${c.size} octets (seuil ${c.max})`,
|
|
52
|
+
c.count === 0
|
|
53
|
+
? ` ${NO_DATED}`
|
|
54
|
+
: ` ${c.count} entrée(s) datée(s) (${c.bytes} octets) à regrouper sous une ligne vers un index secondaire — le skill propose, l'humain décide`,
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
function tourLines(c) {
|
|
58
|
+
if (c.kind === 'ok')
|
|
59
|
+
return [];
|
|
60
|
+
if (c.kind === 'unreadable')
|
|
61
|
+
return [`mémoire · MEMORY.md illisible (${c.cause})`];
|
|
62
|
+
const tail = c.count === 0 ? 'aucune entrée datée à regrouper' : `${c.count} entrée(s) datée(s) à regrouper (cadence session close détaille)`;
|
|
63
|
+
return [`mémoire · MEMORY.md ${c.size} octets > seuil ${c.max} · ${tail}`];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Clé `memory.indexMaxBytes` de cadence.yaml : seuil, en octets, de l'avertissement ; `undefined` quand elle est absente.
|
|
67
|
+
* Une valeur qui n'est pas un entier ≥ 1 est refusée.
|
|
68
|
+
*/
|
|
69
|
+
export function readMemoryIndexMax(file) {
|
|
70
|
+
if (!existsSync(file))
|
|
71
|
+
return undefined;
|
|
72
|
+
let raw;
|
|
73
|
+
try {
|
|
74
|
+
raw = parse(readFileSync(file, 'utf8'));
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
throw new RafError(`${file} illisible : ${e.message.split('\n')[0]}`);
|
|
78
|
+
}
|
|
79
|
+
const m = raw?.memory;
|
|
80
|
+
if (m == null)
|
|
81
|
+
return undefined;
|
|
82
|
+
if (typeof m !== 'object' || Array.isArray(m))
|
|
83
|
+
throw new RafError(`${file} : memory doit être un objet { indexMaxBytes }`);
|
|
84
|
+
for (const k of Object.keys(m))
|
|
85
|
+
if (k !== 'indexMaxBytes')
|
|
86
|
+
throw new RafError(`${file} : memory.${k} inconnu (attendu : indexMaxBytes)`);
|
|
87
|
+
const v = m.indexMaxBytes;
|
|
88
|
+
if (v === undefined)
|
|
89
|
+
return undefined;
|
|
90
|
+
if (typeof v !== 'number' || !Number.isInteger(v) || v < 1)
|
|
91
|
+
throw new RafError(`${file} : memory.indexMaxBytes doit être un entier ≥ 1 (octets), pas ${typeof v === 'object' ? JSON.stringify(v) : String(v)}`);
|
|
92
|
+
return v;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Pour `session close` : le contexte de la mesure, relu à l'appel (une clé invalide lève alors avant tout rapport). Le seuil vient
|
|
96
|
+
* du `cadence.yaml` que la commande lit.
|
|
97
|
+
*/
|
|
98
|
+
export function closeMemoryLines(env, dir, configFile) {
|
|
99
|
+
return () => closeLines(checkMemoryIndex(env, dir, readMemoryIndexMax(configFile) ?? MEMORY_INDEX_MAX_BYTES));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Pour `lead tour` : la ligne « mémoire » du dossier parent, avec le seuil de son cadence.yaml. Une clé invalide n'arrête pas le tour : une ligne d'erreur
|
|
103
|
+
* remplace la ligne « mémoire » ; un cadence.yaml illisible a déjà sa ligne d'erreur en tête de tour et n'en ajoute pas ici.
|
|
104
|
+
*/
|
|
105
|
+
export function tourMemoryLines(env, parent) {
|
|
106
|
+
try {
|
|
107
|
+
return tourLines(checkMemoryIndex(env, parent, readMemoryIndexMax(join(parent, 'cadence.yaml')) ?? MEMORY_INDEX_MAX_BYTES));
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
const message = e.message;
|
|
111
|
+
return / : memory/.test(message) ? [`cadence.yaml · ✗ erreur : ${message.replace(/^.*cadence\.yaml : /, '')}`] : [];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -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);
|
|
@@ -5,6 +5,7 @@ export { estimateLines, launchPrices } from './cout.js';
|
|
|
5
5
|
import { gitRoot, isAncestor } from '../git.js';
|
|
6
6
|
import { repoWork } from '../repos.js';
|
|
7
7
|
import { quotaReset } from './result.js';
|
|
8
|
+
import { proposalLine, touchedReader } from './deja-traitee.js';
|
|
8
9
|
const k = (n) => `${Math.round(n / 1000)} k`;
|
|
9
10
|
const m = (n) => `${(n / 1_000_000).toFixed(1).replace('.', ',')} M`;
|
|
10
11
|
export const LABEL = {
|
|
@@ -83,6 +84,15 @@ function gitCommitsOf(l) {
|
|
|
83
84
|
return [];
|
|
84
85
|
}
|
|
85
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
|
+
}
|
|
86
96
|
const sameCommit = (a, b) => a.length >= 4 && b.length >= 4 && (a.startsWith(b) || b.startsWith(a));
|
|
87
97
|
/**
|
|
88
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
|
|
@@ -206,7 +216,7 @@ function signalLines(l) {
|
|
|
206
216
|
return out;
|
|
207
217
|
}
|
|
208
218
|
/** Le détail d'un lot (L441) : ce que le tableau compact de `--status` n'imprime pas et que `--status --detail <lot>` donne. */
|
|
209
|
-
function detailLines(l) {
|
|
219
|
+
function detailLines(l, touched) {
|
|
210
220
|
const out = [];
|
|
211
221
|
if (l.goal)
|
|
212
222
|
out.push(`${l.project}:${l.lot} — objectif donné : ${l.goal}`);
|
|
@@ -228,7 +238,7 @@ function detailLines(l) {
|
|
|
228
238
|
for (const h of l.hypotheses ?? [])
|
|
229
239
|
out.push(`${l.project}:${l.lot} — hypothèse de la fiche : ${h}`);
|
|
230
240
|
for (const p of l.proposals)
|
|
231
|
-
out.push(`${l.project}:${l.lot} — proposé : ${p}`);
|
|
241
|
+
out.push(`${l.project}:${l.lot} — proposé : ${proposalLine(l, p, touched)}`);
|
|
232
242
|
if (l.uxVerdict && l.visible)
|
|
233
243
|
out.push(`${l.project}:${l.lot} — verdict UX pour « raf ux » : ${l.uxVerdict}`);
|
|
234
244
|
return out;
|
|
@@ -237,7 +247,7 @@ function detailLines(l) {
|
|
|
237
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).
|
|
238
248
|
* `sel` : `projet:lot` ou le seul `lot` s'il est unique dans la vague. Lot inconnu ou ambigu : lève.
|
|
239
249
|
*/
|
|
240
|
-
export function renderDetail(lots, sel) {
|
|
250
|
+
export function renderDetail(lots, sel, deps = {}) {
|
|
241
251
|
const hit = lots.filter((l) => `${l.project}:${l.lot}` === sel);
|
|
242
252
|
const found = hit.length ? hit : sel.includes(':') ? [] : lots.filter((l) => l.lot === sel);
|
|
243
253
|
if (found.length === 0)
|
|
@@ -245,9 +255,23 @@ export function renderDetail(lots, sel) {
|
|
|
245
255
|
if (found.length > 1)
|
|
246
256
|
throw new Error(`lot ambigu : ${sel} (${found.map((l) => `${l.project}:${l.lot}`).join(', ')}) : donner projet:lot`);
|
|
247
257
|
const l = found[0];
|
|
248
|
-
const lines = detailLines(l);
|
|
258
|
+
const lines = detailLines(l, touchedReader(deps, reviewedCommitsOf));
|
|
249
259
|
return lines.length ? lines : [`${l.project}:${l.lot} — aucun détail (ni objectif, capture, choix, proposition, ni verdict UX)`];
|
|
250
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
|
+
}
|
|
251
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`. */
|
|
252
276
|
export function renderTable(wave, lots, deps = {}) {
|
|
253
277
|
const cost = costView(lots, deps.prices);
|
|
@@ -270,13 +294,7 @@ export function renderTable(wave, lots, deps = {}) {
|
|
|
270
294
|
for (const d of deliverable(lots, deps.isAncestor ?? isAncestor, deps.lotCommits ?? gitCommitsOf)) {
|
|
271
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}`);
|
|
272
296
|
}
|
|
273
|
-
|
|
274
|
-
out.push(...signalLines(l));
|
|
275
|
-
if (deps.compact)
|
|
276
|
-
continue;
|
|
277
|
-
out.push(...detailLines(l));
|
|
278
|
-
}
|
|
279
|
-
const withDetail = lots.filter((l) => detailLines(l).length > 0).map((l) => `${l.project}:${l.lot}`);
|
|
297
|
+
const withDetail = perLotLines(lots, out, deps);
|
|
280
298
|
if (deps.compact && withDetail.length)
|
|
281
299
|
out.push(`détail (objectif, captures, base de tests, choix, propositions, verdict UX) : cadence orchestrate --status --detail <projet:lot> — ${withDetail.join(', ')}`);
|
|
282
300
|
return out;
|
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>
|