@sylad/cadence 1.32.0 → 1.34.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 +1 -1
- package/dist/articles.js +1 -1
- package/dist/audit.js +6 -6
- package/dist/config.js +8 -9
- package/dist/corrections.js +9 -10
- package/dist/dates.js +1 -1
- package/dist/deliver.js +2 -2
- package/dist/hud-context.js +5 -5
- package/dist/lead.js +5 -5
- package/dist/link.js +1 -1
- package/dist/news.js +1 -1
- package/dist/orchestrate/briefs.js +1 -0
- package/dist/orchestrate/command.js +6 -6
- package/dist/orchestrate/cycle.js +52 -87
- package/dist/orchestrate/precheck.js +79 -0
- package/dist/orchestrate/probe.js +124 -0
- package/dist/orchestrate/schemas.js +10 -1
- package/dist/perf.js +1 -1
- package/dist/plan-commit.js +4 -4
- package/dist/plan.js +3 -3
- package/dist/proc.js +1 -1
- package/dist/ref/add.js +2 -2
- package/dist/ref/analyse.js +2 -2
- package/dist/ref/coverage.js +2 -2
- package/dist/ref/diff.js +1 -1
- package/dist/ref/measure.js +4 -4
- package/dist/ref/pdf.js +1 -1
- package/dist/ref/regard.js +2 -2
- package/dist/ref/store.js +1 -1
- package/dist/ref/tools.js +1 -1
- package/dist/release.js +4 -4
- package/dist/verify.js +2 -2
- package/dist/yaml-keys.js +6 -0
- package/docs/cadence-hud.md +1 -1
- package/docs/orchestrate.md +21 -2
- package/docs/qualite.md +2 -0
- package/docs/whats-new.md +9 -0
- package/package.json +1 -1
- package/templates/orchestrate/probe.md +20 -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.34.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.34.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
|
@@ -48,7 +48,7 @@ Details: [docs/skills.md](docs/skills.md) and [docs/cadence-hud.md](docs/cadence
|
|
|
48
48
|
## Where to read more
|
|
49
49
|
|
|
50
50
|
- [docs/raf.md](docs/raf.md): the plan, its commands and its file.
|
|
51
|
-
- [docs/orchestrate.md](docs/orchestrate.md): waves
|
|
51
|
+
- [docs/orchestrate.md](docs/orchestrate.md): waves, budgets, and [the edge-case probe](docs/orchestrate.md#the-edge-case-probe-orchestrateprobe-l497).
|
|
52
52
|
- [docs/deliver.md](docs/deliver.md): wait for the CI, deploy, verify the effect.
|
|
53
53
|
- [docs/spec.md](docs/spec.md): screen and back-end sheets.
|
|
54
54
|
- [docs/ref.md](docs/ref.md): reference documents (specs, norms, mock-ups) and the coverage of their elements by the lots.
|
package/dist/articles.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { isOpen } from './plan.js';
|
|
4
4
|
/** Titre du lot ouvert à la livraison. */
|
|
5
|
-
|
|
5
|
+
const articleTitle = (name) => `Article ${name} à rafraîchir`;
|
|
6
6
|
const SECTIONS_SHOWN = 12;
|
|
7
7
|
/** Titres d'un article : lignes `#` du Markdown (hors en-tête YAML) et balises `<h1>`…`<h6>` d'une page Astro ou MDX. */
|
|
8
8
|
export function articleHeadings(text) {
|
package/dist/audit.js
CHANGED
|
@@ -140,7 +140,7 @@ export function isReleaseOnly(sha, root) {
|
|
|
140
140
|
}));
|
|
141
141
|
}
|
|
142
142
|
/** Le commit a été acquitté par « raf ignore » : par son sha, ou par son sujet exact. */
|
|
143
|
-
|
|
143
|
+
function isAcknowledged(plan, c) {
|
|
144
144
|
return plan.acknowledged.some((a) => (a.sha !== undefined && a.sha === c.sha) || (a.sha === undefined && a.subject === c.subject));
|
|
145
145
|
}
|
|
146
146
|
/**
|
|
@@ -158,7 +158,7 @@ export function exemptPlanOnly(linked, plan, root) {
|
|
|
158
158
|
return { ...linked, orphans, unknown: linked.unknown.filter((u) => !acked(u.commit)) };
|
|
159
159
|
}
|
|
160
160
|
/** Fenêtre de l'audit : --since, sinon la date d'adoption du plan, sinon 30 jours. */
|
|
161
|
-
|
|
161
|
+
function auditSince(plan, explicit) {
|
|
162
162
|
if (explicit)
|
|
163
163
|
return explicit;
|
|
164
164
|
// Une date seule vaudrait « ce jour-là à l'heure actuelle » pour git : minuit explicite.
|
|
@@ -168,7 +168,7 @@ export function auditSince(plan, explicit) {
|
|
|
168
168
|
* Avertissements : un lot visible (hors abandonné) sans titre public — le site retombe sur le titre technique du plan,
|
|
169
169
|
* sauf pour un lot terminé qu'une Nouveauté cite : le site en reprend le titre (cf. reusedNewsTitle).
|
|
170
170
|
*/
|
|
171
|
-
|
|
171
|
+
function missingPublicTitles(lots, entries) {
|
|
172
172
|
return lots
|
|
173
173
|
.filter((l) => l.visible && l.status !== 'dropped' && !l.public && reusedNewsTitle(l, entries) === null)
|
|
174
174
|
.map((l) => ({ message: `${l.id} : lot visible sans titre public — raf public ${l.id} "…"`, warning: true }));
|
|
@@ -225,7 +225,7 @@ export function hypothesesOf(root, configFile) {
|
|
|
225
225
|
* travail touchent des chemins sans toucher le document que la règle désigne. Les commits de plan ne comptent pas.
|
|
226
226
|
* Un lot dont l'écart est acquitté et motivé (`raf docs-ok`, L317) n'est pas compté.
|
|
227
227
|
*/
|
|
228
|
-
|
|
228
|
+
function docSyncIssues(plan, root, byLot) {
|
|
229
229
|
const { sync, since } = readDocsConfig(plan.configFile ?? join(root, 'cadence.yaml'));
|
|
230
230
|
if (!sync.length)
|
|
231
231
|
return [];
|
|
@@ -349,7 +349,7 @@ export function unreviewedWork(plan, root, lotId) {
|
|
|
349
349
|
* à la journée près, on ne distingue pas un lot fermé avant l'activation d'un lot fermé après
|
|
350
350
|
* (et `raf done` refuse de toute façon dès l'activation).
|
|
351
351
|
*/
|
|
352
|
-
|
|
352
|
+
function uxIssues(plan) {
|
|
353
353
|
const since = plan.uxSince;
|
|
354
354
|
if (!since)
|
|
355
355
|
return [];
|
|
@@ -363,7 +363,7 @@ export function uxIssues(plan) {
|
|
|
363
363
|
* pas : aucun verdict, ou un verdict antérieur à ces commits. Même règle de date que `uxIssues` ; un
|
|
364
364
|
* lot sans commit n'a rien à faire relire.
|
|
365
365
|
*/
|
|
366
|
-
|
|
366
|
+
function reviewIssues(plan, root, byLot) {
|
|
367
367
|
const since = plan.reviewSince;
|
|
368
368
|
if (!since)
|
|
369
369
|
return [];
|
package/dist/config.js
CHANGED
|
@@ -6,6 +6,7 @@ import { DEFAULT_TEST_PATTERNS } from './testgate.js';
|
|
|
6
6
|
import { gitRoot } from './git.js';
|
|
7
7
|
import { planCommitProblem } from './plan-commit.js';
|
|
8
8
|
import { isComposedGate } from './orchestrate/workers.js';
|
|
9
|
+
import { booleanKey } from './yaml-keys.js';
|
|
9
10
|
import { FIELDS, Plan, RafError, STATUSES } from './plan.js';
|
|
10
11
|
const KEYS = ['path', 'project', 'since', 'ignore', 'files', 'lots', 'fields', 'statuses', 'estimates', 'add', 'onWrite', 'commit'];
|
|
11
12
|
const ON_WRITE_KEYS = ['command', 'files'];
|
|
@@ -311,7 +312,7 @@ export function readSessionConfig(file) {
|
|
|
311
312
|
return config;
|
|
312
313
|
}
|
|
313
314
|
const EFFORTS = ['default', 'low', 'medium', 'high', 'xhigh', 'max'];
|
|
314
|
-
|
|
315
|
+
const EFFORT_STEPS = ['precheck', 'spec', 'maquette', 'fiche', 'accept', 'implement', 'probe', 'fix', 'review', 'ux'];
|
|
315
316
|
/** Clé `verify:` de cadence.yaml. Absente : `{}`. */
|
|
316
317
|
export function readVerifyConfig(file) {
|
|
317
318
|
if (!existsSync(file))
|
|
@@ -338,10 +339,10 @@ export function readVerifyConfig(file) {
|
|
|
338
339
|
return { inPlace: v.inPlace };
|
|
339
340
|
}
|
|
340
341
|
const REVIEW_KEYS = ['threshold', 'light', 'full'];
|
|
341
|
-
const ORCH_KEYS = ['start', 'verdict', 'test', 'testReplay', 'build', 'worktreeDeps', 'gate', 'accept', 'maquette', 'maquetteReviews', 'fiche', 'ux', 'precheck', 'prodAccess', 'worktrees', 'testGate', 'spec', 'review', 'escalation', 'continue', 'effort', 'permissionMode', 'addDirs', 'timeouts'];
|
|
342
|
+
const ORCH_KEYS = ['start', 'verdict', 'test', 'testReplay', 'build', 'worktreeDeps', 'gate', 'accept', 'maquette', 'maquetteReviews', 'fiche', 'ux', 'precheck', 'probe', 'prodAccess', 'worktrees', 'testGate', 'spec', 'review', 'escalation', 'continue', 'effort', 'permissionMode', 'addDirs', 'timeouts'];
|
|
342
343
|
/** Clé `orchestrate:` de cadence.yaml. Absente : les défauts (auto, 45 min d'implémentation, 25 min de revue). */
|
|
343
344
|
export function readOrchestrateConfig(file) {
|
|
344
|
-
const config = { precheck: true, maquette: true, maquetteReviews: 3, fiche: true, precheckLocal: false, prodAccess: false, worktrees: false, spec: { dir: 'docs/specs' }, review: { threshold: 0.25, light: 'sonnet', full: 'opus' }, effort: { precheck: 'low', spec: 'medium', maquette: 'high', fiche: 'medium', accept: 'medium', implement: 'medium', fix: 'medium', review: 'high', ux: 'high' }, permissionMode: 'auto', addDirs: [], timeouts: { work: 45 * 60_000, review: 25 * 60_000 } };
|
|
345
|
+
const config = { precheck: true, maquette: true, maquetteReviews: 3, fiche: true, precheckLocal: false, prodAccess: false, worktrees: false, spec: { dir: 'docs/specs' }, review: { threshold: 0.25, light: 'sonnet', full: 'opus' }, effort: { precheck: 'low', spec: 'medium', maquette: 'high', fiche: 'medium', accept: 'medium', implement: 'medium', probe: 'medium', fix: 'medium', review: 'high', ux: 'high' }, permissionMode: 'auto', addDirs: [], timeouts: { work: 45 * 60_000, review: 25 * 60_000 } };
|
|
345
346
|
if (!existsSync(file))
|
|
346
347
|
return config;
|
|
347
348
|
let raw;
|
|
@@ -417,11 +418,9 @@ export function readOrchestrateConfig(file) {
|
|
|
417
418
|
throw bad('maquetteReviews : entier supérieur ou égal à 1 attendu');
|
|
418
419
|
config.maquetteReviews = o.maquetteReviews;
|
|
419
420
|
}
|
|
420
|
-
|
|
421
|
-
if (
|
|
422
|
-
|
|
423
|
-
config.fiche = o.fiche;
|
|
424
|
-
}
|
|
421
|
+
for (const k of ['fiche', 'probe'])
|
|
422
|
+
if (o[k] != null)
|
|
423
|
+
config[k] = booleanKey(o[k], bad(`${k} : true ou false attendu`));
|
|
425
424
|
if (o.precheck != null) {
|
|
426
425
|
if (typeof o.precheck !== 'boolean' && o.precheck !== 'local')
|
|
427
426
|
throw bad('precheck : true, false ou local attendu');
|
|
@@ -534,7 +533,7 @@ export function readOrchestrateConfig(file) {
|
|
|
534
533
|
}
|
|
535
534
|
if (o.effort != null) {
|
|
536
535
|
if (!isObject(o.effort))
|
|
537
|
-
throw bad('effort doit être un objet { precheck, spec, maquette, fiche, accept, implement, fix, review, ux }');
|
|
536
|
+
throw bad('effort doit être un objet { precheck, spec, maquette, fiche, accept, implement, probe, fix, review, ux }');
|
|
538
537
|
for (const [k, v] of Object.entries(o.effort)) {
|
|
539
538
|
if (!EFFORT_STEPS.includes(k))
|
|
540
539
|
throw bad(`effort.${k} inconnu (attendu : ${EFFORT_STEPS.join(', ')})`);
|
package/dist/corrections.js
CHANGED
|
@@ -99,11 +99,10 @@ export function readJournals(runsDir, until, since) {
|
|
|
99
99
|
const name = basename(file, '.json');
|
|
100
100
|
const fixSteps = steps.filter((s) => s.kind === 'fix' && done(s));
|
|
101
101
|
const passes = fixSteps.map((fix) => {
|
|
102
|
-
const review = [...steps].reverse().find((s) => s.n < fix.n && isReview(s.kind) && s.report);
|
|
103
|
-
// Une passe fix entre
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
return { key: `${wave}/${name}/${fix.n}`, project: rec.project, lot: rec.lot, visible: !!rec.visible, findings, commits: fix.commits ?? [], ...(fix.ended ? { fixEnded: fix.ended } : {}), ...(findings.length > 0 && review ? { reviewKind: review.kind } : {}) };
|
|
102
|
+
const review = [...steps].reverse().find((s) => s.n < fix.n && ((isReview(s.kind) && s.report) || (s.kind === 'probe' && Array.isArray(s.constats) && s.constats.length > 0)));
|
|
103
|
+
// La revue (ou la sonde, L497, qui porte ses constats dans son étape) qui précède. Une passe fix entre elle et celle-ci a déjà traité ces constats : cette passe-ci (tests rouges) n'en a pas.
|
|
104
|
+
const findings = !review || fixSteps.some((f) => f.n > review.n && f.n < fix.n) ? [] : review.kind === 'probe' ? review.constats.filter((t) => typeof t === 'string') : review.report ? findingsOf(readJson(join(waveDir, name, review.report))) : [];
|
|
105
|
+
return { key: `${wave}/${name}/${fix.n}`, project: rec.project, lot: rec.lot, visible: !!rec.visible, findings, commits: fix.commits ?? [], ...(fix.ended ? { fixEnded: fix.ended } : {}), ...(findings.length > 0 && review && review.kind !== 'probe' ? { reviewKind: review.kind } : {}) };
|
|
107
106
|
});
|
|
108
107
|
const precheck = precheckVerdictOf(rec, steps);
|
|
109
108
|
const esc = rec.escalation;
|
|
@@ -296,9 +295,9 @@ const SOUS_DEFINITIONS = {
|
|
|
296
295
|
autre: 'tout bug de code qui n\'entre dans aucune des valeurs précédentes',
|
|
297
296
|
};
|
|
298
297
|
/** Les maillons de la chaîne où un mécanisme peut être posé (R2). */
|
|
299
|
-
|
|
298
|
+
const MAILLONS = ['brief implement', 'auto-vérification avant rendu', 'porte locale', 'fiche'];
|
|
300
299
|
/** Le maillon dont relève chaque sous-cause (R2, hypothèse) ; `autre` n'en désigne aucun. */
|
|
301
|
-
|
|
300
|
+
const MAILLON_DE = {
|
|
302
301
|
'cas-limite': 'brief implement',
|
|
303
302
|
'test-vide': 'brief implement',
|
|
304
303
|
'affirmation-fausse': 'auto-vérification avant rendu',
|
|
@@ -308,8 +307,8 @@ export const MAILLON_DE = {
|
|
|
308
307
|
autre: null,
|
|
309
308
|
};
|
|
310
309
|
/** Seuils de désignation du maillon dominant (R2, hypothèse) : passes sous-classées au moins, et part minimale du maillon en tête, en %. */
|
|
311
|
-
|
|
312
|
-
|
|
310
|
+
const MAILLON_MIN_PASSES = 10;
|
|
311
|
+
const MAILLON_MIN_PART = 40;
|
|
313
312
|
/** Extrait une sous-cause connue d'une réponse (JSON `{sous}`) ; null sinon. */
|
|
314
313
|
export function parseSousCause(text) {
|
|
315
314
|
try {
|
|
@@ -428,7 +427,7 @@ export function sousCausesRanked(b) {
|
|
|
428
427
|
return SOUS_CAUSES.map((sous, i) => ({ sous, label: SOUS_LABELS[sous], passes: b.parSousCause[sous], i })).filter((x) => x.passes > 0).sort((a, b2) => b2.passes - a.passes || a.i - b2.i);
|
|
429
428
|
}
|
|
430
429
|
/** La section « Bug de code par sous-cause » de `cadence corrections` (A1) : sous-cause, passes, part des passes sous-classées, maillon. */
|
|
431
|
-
|
|
430
|
+
function renderSousCauses(b) {
|
|
432
431
|
const out = ['### Bug de code par sous-cause', '', `${b.classees} passes sous-classées sur ${b.classees + b.sansSousCause}${b.sansSousCause > 0 ? ` · sans sous-cause : ${b.sansSousCause} (à classer : cadence corrections --classify)` : ''}.`, ''];
|
|
433
432
|
if (b.classees > 0)
|
|
434
433
|
out.push('| Sous-cause | Passes | Part | Maillon |', '|---|---|---|---|', ...sousCausesRanked(b).map((r) => `| ${r.label} | ${r.passes} | ${pct(r.passes, b.classees)} | ${MAILLON_DE[r.sous] ?? 'aucun'} |`), '');
|
package/dist/dates.js
CHANGED
package/dist/deliver.js
CHANGED
|
@@ -14,7 +14,7 @@ import { appendDelivery, appendFailedDelivery, lastDelivery, lockAlive, lockFaul
|
|
|
14
14
|
const POLL_CI = 15_000;
|
|
15
15
|
const CI_APPEAR = 300_000;
|
|
16
16
|
/** Intervalle de réessai d'une vérification : celui de deliver ET de `cadence verify --retry`. */
|
|
17
|
-
|
|
17
|
+
const POLL_VERIFY = 10_000;
|
|
18
18
|
const CI_OK = new Set(['success', 'skipped', 'neutral']);
|
|
19
19
|
const GH_TIMEOUT = 60_000;
|
|
20
20
|
export const TIMED_OUT = 124;
|
|
@@ -474,7 +474,7 @@ async function waitCi(ctx, deps, sha, env) {
|
|
|
474
474
|
}
|
|
475
475
|
}
|
|
476
476
|
/** Un essai d'une vérification : cause de l'échec, ou null. Partagé par deliver et `cadence verify`. */
|
|
477
|
-
|
|
477
|
+
async function tryCheck(c, deps, sha, env, budgetMs) {
|
|
478
478
|
if (c.command !== undefined) {
|
|
479
479
|
const code = await deps.exec(c.command, env, budgetMs);
|
|
480
480
|
return code === 0 ? null : codeText(code);
|
package/dist/hud-context.js
CHANGED
|
@@ -2,15 +2,15 @@ import { existsSync, readdirSync, readFileSync, realpathSync, statSync, unlinkSy
|
|
|
2
2
|
import { basename, join, resolve } from 'node:path';
|
|
3
3
|
import { cadenceHome } from './orchestrate/registry.js';
|
|
4
4
|
/** Au-delà, le chiffre écrit par la bande est trop vieux pour guider un arrêt (la bande le rafraîchit toutes les 5 s). */
|
|
5
|
-
|
|
5
|
+
const HUD_CONTEXT_MAX_AGE_MS = 120_000;
|
|
6
6
|
/** Un fichier de session plus vieux que cela est ignoré et supprimé à la lecture (une session fermée ne le rafraîchit plus). */
|
|
7
|
-
|
|
7
|
+
const HUD_CONTEXT_PURGE_MS = 24 * 3600_000;
|
|
8
8
|
/** Dossier où la bande cadence-hud publie le contexte de chaque session : un fichier `<id de session>.json` par session. */
|
|
9
|
-
|
|
9
|
+
function hudContextDir(env = process.env) {
|
|
10
10
|
return join(cadenceHome(env), 'hud-context');
|
|
11
11
|
}
|
|
12
12
|
/** Le nom de fichier d'une session : le même nettoyage que le script d'écriture de la bande (lettres, chiffres, `-`, `_`). */
|
|
13
|
-
|
|
13
|
+
function hudContextFile(session) {
|
|
14
14
|
return `${session.replace(/[^A-Za-z0-9_-]/g, '_')}.json`;
|
|
15
15
|
}
|
|
16
16
|
function sameFolder(a, b) {
|
|
@@ -56,7 +56,7 @@ function livingFiles(dir, nowMs) {
|
|
|
56
56
|
return kept;
|
|
57
57
|
}
|
|
58
58
|
/** La variable que Claude Code pose dans l'environnement de ses commandes Bash : l'id de la session, nom du fichier que la bande écrit. */
|
|
59
|
-
|
|
59
|
+
const SESSION_ID_ENV = 'CLAUDE_CODE_SESSION_ID';
|
|
60
60
|
/**
|
|
61
61
|
* Le contexte publié par la bande pour la session de l'appelant, dans l'ordre : `session` quand on la désigne, sinon la session de la
|
|
62
62
|
* variable `CLAUDE_CODE_SESSION_ID` ; seulement si elle est absente ET qu'un seul fichier publie pour `cwd`, celui-là (deux sessions
|
package/dist/lead.js
CHANGED
|
@@ -64,7 +64,7 @@ export function findLeadDir(start) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
/** Seuil de contexte du lead quand `lead.contextThreshold` n'est pas posé (L437). */
|
|
67
|
-
|
|
67
|
+
const CONTEXT_THRESHOLD_DEFAULT = 150_000;
|
|
68
68
|
/** Clé `lead.contextThreshold` du cadence.yaml du dossier (L437) : le nombre de tokens de contexte à partir duquel le lead ne lance plus de vague. En tokens et non en % : 60 % d'une fenêtre de 1M valait 600k. Un entier ≥ 1, sinon refus. */
|
|
69
69
|
export function readLeadContextThreshold(parent) {
|
|
70
70
|
const found = readLeadBlock(parent, 'lead.contextThreshold', 'un entier ≥ 1 (tokens)');
|
|
@@ -94,15 +94,15 @@ export function readDepth(parent) {
|
|
|
94
94
|
throw new RafError(`${file} : depth doit être un entier ≥ 1`);
|
|
95
95
|
return d;
|
|
96
96
|
}
|
|
97
|
-
|
|
97
|
+
const NOTES_MAX = 120;
|
|
98
98
|
export const TITLE_MAX = 60;
|
|
99
99
|
/** Au-delà de ce nombre de jours sans activité, un lot en cours est dit silencieux. */
|
|
100
|
-
|
|
100
|
+
const TOUR_IDLE = 3;
|
|
101
101
|
/** Un lot créé depuis moins de ce nombre de jours (aujourd'hui compris : 0 à 6 jours d'écart) compte parmi les « ajoutés cette semaine ». */
|
|
102
|
-
|
|
102
|
+
const ADDED_DAYS = 7;
|
|
103
103
|
const empty = (project) => ({ project, doing: [], drift: [], notes: [], next: null, progress: { done: 0, doing: 0, todo: 0, added7: 0 }, repo: [] });
|
|
104
104
|
/** Les lots non clos du plan, hors récurrents, chacun suivi de ses sous-tâches non closes ; celles d'un lot clos ou récurrent sortent avec lui. */
|
|
105
|
-
|
|
105
|
+
function openLots(lots) {
|
|
106
106
|
return lots
|
|
107
107
|
.filter((l) => isOpen(l.status) && !isRecurring(l))
|
|
108
108
|
.flatMap((l) => [
|
package/dist/link.js
CHANGED
package/dist/news.js
CHANGED
|
@@ -33,7 +33,7 @@ import { activeLock, REPO_LOCK, releaseLock, takeLock } from './lock.js';
|
|
|
33
33
|
import { groupLots, runPool } from './pool.js';
|
|
34
34
|
import { repoBranch } from './integrate.js';
|
|
35
35
|
import { baseBranchOf, keptLines, keptTrees, lotBranch, lotTreeDir, planFileSet, recoverTree, removeTree } from './worktree.js';
|
|
36
|
-
import { schemaFor } from './schemas.js';
|
|
36
|
+
import { agentOf, schemaFor } from './schemas.js';
|
|
37
37
|
import { resolveLotRepos } from '../repos.js';
|
|
38
38
|
import { excludeState, lotFinished, lotKey, lotRepoPaths, lotSlug, neighbourDirs, newLot, RunStore } from './state.js';
|
|
39
39
|
import { linkNodeBin, nvmVersionsDir, resolveNode } from './node-env.js';
|
|
@@ -302,7 +302,7 @@ async function preflight(args, targets, io, deps, launch, opts = {}) {
|
|
|
302
302
|
refusals.push(`${key} : ${e.message}`);
|
|
303
303
|
continue;
|
|
304
304
|
}
|
|
305
|
-
if (info && olderThanEffort(info.version) && Object.
|
|
305
|
+
if (info && olderThanEffort(info.version) && Object.entries(env.config.effort).some(([step, e]) => e !== 'default' && (step !== 'probe' || env.config.probe))) {
|
|
306
306
|
refusals.push(`${key} : claude ${info.version} n'a pas --effort (2.1.284 requise) : mettre claude à jour, ou orchestrate.effort à « default » pour chaque passe`);
|
|
307
307
|
continue;
|
|
308
308
|
}
|
|
@@ -518,6 +518,7 @@ function dryRun(lots, io, deps, budget, id) {
|
|
|
518
518
|
else
|
|
519
519
|
steps.unshift({ kind: 'precheck', model: env.config.precheckLocal ? 'local' : 'sonnet' });
|
|
520
520
|
}
|
|
521
|
+
steps.push(...(env.config.probe ? [{ kind: 'probe', model: 'sonnet' }] : [])); // L497 : entre l'écriture et la revue
|
|
521
522
|
const { light, full } = env.config.review;
|
|
522
523
|
if (l.small)
|
|
523
524
|
steps.push({ kind: l.visible ? 'review-small' : 'review', model: l.light ? light : full });
|
|
@@ -550,7 +551,7 @@ function dryRun(lots, io, deps, budget, id) {
|
|
|
550
551
|
io.out(l.light ? ' revue conforme avec mineurs : pas de passe des mineurs, ils sont rendus en notes' : ' revue conforme avec mineurs : une passe de correction des mineurs (session neuve), puis une revue courte ; les mineurs refusés sont rendus en « choix »');
|
|
551
552
|
const pwDir = join(resolve(RunStore.runsDir(io.cwd)), id, lotSlug(l.project, l.lot), 'playwright');
|
|
552
553
|
// brief écrit par --dry-run = base d'une délégation à la main : aucun dossier de vague (il n'existe pas)
|
|
553
|
-
const vars = { chemin: l.repo, lot: l.lot, titre: headline(lot, l.goal), objectif: objective(lot, l.goal), commits: '', reponse: '', constats: '', ux: '', choix: '', checks: '', news: l.visible ? newsText(l.lot) : '', captures: '' };
|
|
554
|
+
const vars = { chemin: l.repo, lot: l.lot, titre: headline(lot, l.goal), objectif: objective(lot, l.goal), commits: '', reponse: '', constats: '', ux: '', choix: '', checks: '', news: l.visible ? newsText(l.lot) : '', captures: '', diff: 'git diff <sha de départ>..HEAD' };
|
|
554
555
|
vars.decisions = decisionsText(humanDecisions(lot, lot.parent ? env.loadPlan().lots().find((x) => x.id === lot.parent) : undefined));
|
|
555
556
|
if (specStage)
|
|
556
557
|
vars.spec = specText({ dir: env.config.spec.dir, render: env.config.spec.render, screen: l.screen, back: !!l.specLot });
|
|
@@ -564,11 +565,10 @@ function dryRun(lots, io, deps, budget, id) {
|
|
|
564
565
|
}
|
|
565
566
|
for (const s of steps) {
|
|
566
567
|
const file = join(tmp, `${lotSlug(l.project, l.lot)}--${s.kind}.md`);
|
|
567
|
-
|
|
568
|
-
writeFileSync(file, brief);
|
|
568
|
+
writeFileSync(file, renderBrief(briefName(l, s.kind), s.kind === 'fiche' || vars.fiche === undefined ? vars : { ...vars, fiche: '' }, deps.templatesDir)); // la consigne de revue de fiche ne sert qu'au brief de la fiche : implement et review en sont la base d'une délégation à la main
|
|
569
569
|
const playwright = !!mcpServersFor(s.kind, l.visible, '', l.small, !!l.screen).playwright;
|
|
570
570
|
const local = s.model === 'local';
|
|
571
|
-
const args = (local ? buildLocalArgs : buildArgs)({ kind: s.kind, sessionId: '<uuid>', brief: '<brief>', model: s.model === 'local' ? 'sonnet' : s.model, local, effort: env.config.effort[s.kind === 'review-small' ? 'review' : s.kind], schema: schemaFor(s.kind), agent: s.kind
|
|
571
|
+
const args = (local ? buildLocalArgs : buildArgs)({ kind: s.kind, sessionId: '<uuid>', brief: '<brief>', model: s.model === 'local' ? 'sonnet' : s.model, local, effort: env.config.effort[s.kind === 'review-small' ? 'review' : s.kind], schema: schemaFor(s.kind), agent: agentOf(s.kind), cwd: l.repo, wave: id, permissionMode: env.config.permissionMode, addDirs: [...env.config.addDirs, ...(playwright && (s.kind === 'implement' || s.kind === 'spec') ? [pwDir] : []), ...neighbourDirs(l)], timeoutMs: 0, mcpConfig: '<mcp>', playwright, disablePlugins: enabledPlugins(settingsFiles(deps.claudeHome, l.repo)) }, agents).map((a) => (a.startsWith('{') ? '<json>' : a));
|
|
572
572
|
io.out(` ${s.kind} : ${local ? 'claude-local' : 'claude'} ${args.join(' ')}`);
|
|
573
573
|
io.out(` brief : ${file}`);
|
|
574
574
|
const servers = Object.keys(mcpServersFor(s.kind, l.visible, '', l.small, !!l.screen));
|