create-byan-agent 2.52.0 → 2.54.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/CHANGELOG.md CHANGED
@@ -9,6 +9,62 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [2.54.0] - 2026-07-21
13
+
14
+ ### Added — Garde de fraicheur des skills (SessionStart)
15
+ - Nouveau hook `skill-freshness-check.js` (coeur pur `lib/skill-freshness.js`) :
16
+ au demarrage de session, compare chaque `.claude/skills/<n>/SKILL.md` du
17
+ projet avec la copie globale homonyme `~/.claude/skills/<n>/SKILL.md`, par
18
+ CONTENU (l'egalite d'octets dit "fidele" ; une date recente ne dit rien).
19
+ En cas de divergence, injecte un signalement borne (6 noms max) avec la
20
+ commande de synchro exacte. Silencieux quand tout est fidele, quand la copie
21
+ globale n'existe pas, ou quand le projet n'a pas de skills ; sort en 0 dans
22
+ tous les chemins (ne bloque pas une session).
23
+ - Ferme le piege constate le 2026-07-21 : une copie globale du 30 juin masquait
24
+ le skill projet — le rail auto-dispatch livre en 2.53.0 ne se declenchait pas
25
+ car `/byan-byan` chargeait la copie perimee. Point verifie aupres de la doc
26
+ officielle Claude Code : la regle de priorite skills user vs projet en cas de
27
+ collision de nom n'y est pas clairement documentee (le hook signale donc le
28
+ FAIT de la divergence, sans affirmer une regle de chargement).
29
+ - Aucune ecriture automatique dans `~/.claude/skills` : une variante user-level
30
+ peut etre deliberee — le hook signale, l'humain tranche.
31
+ - Cablage : enregistre sous SessionStart dans `.claude/settings.json` (repo +
32
+ template) ; les deux fichiers shippent via le miroir `template-sync.js`.
33
+
34
+ ## [2.53.0] - 2026-07-21
35
+
36
+ ### Added — Rail automatique : workflow natif byan-auto-dispatch (F2)
37
+ - Nouveau workflow Claude natif `.claude/workflows/byan-auto-dispatch.js` : il
38
+ decoupe une tache en etapes typees (nature + complexite 0-100) via un agent
39
+ d'analyse (sonnet, schema JSON force), route chaque etape sur le bon modele
40
+ par l'echelle v3 (haiku < 34, sonnet < 67, opus < 90, fable >= 90), route les
41
+ etapes shell/deploiement/navigation vers Codex (repli Claude annonce si Codex
42
+ n'est pas disponible ; la verification reste sur le modele de session, non
43
+ deleguee), ecrit `_byan-output/plan.md` (table etape x nature x complexite x
44
+ moteur x modele + consignes), execute chaque etape en sequence sur le modele
45
+ route, et verifie le livrable en fin.
46
+ - Cablage skill `byan-byan` (section 0.5) : a CHAQUE tache non-conversationnelle
47
+ recue par `/byan-byan`, ce workflow est invoque AUTOMATIQUEMENT (scriptPath +
48
+ args {task, stamp}), sans demande a l'utilisateur. Exceptions nommees :
49
+ question simple, action destructive (confirmation d'abord), FD multi-feature
50
+ deja engage (sa phase DISPATCH peut invoquer le meme workflow par feature),
51
+ demande explicite d'execution directe. Le gate utilisateur reste en fin, sur
52
+ le livrable.
53
+ - Livraison npm : le script est ajoute au miroir `template-sync.js`.
54
+
55
+ ### Changed — Langage precis, zero dialecte interne (F1, Mantra IA-26)
56
+ - Le filet plain-language s'etend : `nudge`, `up-tier`, `ladder`, `rung`,
57
+ `runtime` rejoignent les mots a remplacer en prose ; nouvelle detection de
58
+ l'anthropomorphisme d'outil (un MCP/serveur/hook dit "vivant" ou "mort") avec
59
+ le remplacement exige : le fait observe, outil nomme ("byan_ping a repondu en
60
+ 0.3s", "le serveur ne repond pas, timeout 8s").
61
+ - Nouvelle clause de precision factuelle dans `.claude/rules/plain-language.md` :
62
+ tout etat d'outil = l'outil nomme + l'appel + le resultat exact ; pas de
63
+ resume flou ("ca marche") ; "non verifie" quand l'appel n'a pas ete fait.
64
+ - L'ancre de voix par tour et le tao (Section 4, Vocabulaire Interdit) portent
65
+ les deux nouveaux interdits. Tests jest etendus (26 verts sur la suite
66
+ plain-language).
67
+
12
68
  ## [2.52.0] - 2026-07-20
13
69
 
14
70
  ### Changed — Delegation Codex v3 : pression-only + obeissance au routeur (F1, F2)
@@ -36,7 +36,8 @@ const ANCHOR = [
36
36
  '- Challenge avant de confirmer ; questionne les absolus (Mantra IA-16).',
37
37
  '- Signatures : "Attends — pourquoi ?", "OK. On construit.", "Ça, c\'est du générique.".',
38
38
  '- Zéro emoji. Orienté solution : on cherche la meilleure option, pas le mur.',
39
- '- Français réel et cohérent (Mantra IA-26) : pas d\'anglais gratuit (dis "redémarrer le conteneur", pas "cutoff"), pas de jargon interne brut, pas de métaphore collée de travers ("forger" un token).',
39
+ '- Français réel et cohérent (Mantra IA-26) : pas d\'anglais gratuit (dis "redémarrer le conteneur", pas "cutoff"), pas de jargon interne brut ("nudge", "up-tier", "ladder", "runtime"), pas de métaphore collée de travers ("forger" un token).',
40
+ '- Précision factuelle : nomme l\'outil et le résultat observé ("byan_ping a répondu", pas "le MCP est vivant") ; un état = un fait mesuré, pas une image.',
40
41
  ].join('\n');
41
42
 
42
43
  const DEFAULT_REFRESH_EVERY = 12;
@@ -32,6 +32,11 @@ const OFFENDERS = Object.freeze([
32
32
  { term: 'gate', good: 'point de controle / porte' },
33
33
  { term: 'leaf', good: 'etape / tache' },
34
34
  { term: 'tier', good: 'niveau / gamme' },
35
+ { term: 'nudge', good: 'rappel / conseil injecte' },
36
+ { term: 'up-tier', good: 'monter en gamme' },
37
+ { term: 'ladder', good: 'echelle' },
38
+ { term: 'rung', good: 'palier' },
39
+ { term: 'runtime', good: "moteur d'execution" },
35
40
  ]);
36
41
 
37
42
  // A metaphor misuse, not a single word: "forger" applied to a token/jeton.
@@ -43,6 +48,19 @@ const METAPHOR_OFFENDERS = Object.freeze([
43
48
  label: 'forger un token',
44
49
  good: 'generer / creer un token',
45
50
  },
51
+ {
52
+ // Anthropomorphism applied to a tool: an MCP/serveur/hook is not "vivant" or
53
+ // "mort" — the precise statement is the observed fact ("byan_ping a repondu",
54
+ // "le serveur ne repond pas, timeout 8s"). The predicate must be COPULA-bound
55
+ // to the tool word ("le serveur est mort", "l'API semble morte") : bare
56
+ // proximity would misfire on fixed French idioms near a tool word ("point
57
+ // mort", "angle mort", "temps mort", "nature morte", "communaute vivante"),
58
+ // which carry no anthropomorphism at all.
59
+ id: 'outil-vivant-mort',
60
+ re: /\b(?:mcp|serveur|outil|hook|api|tool)s?\b[^.!?\n]{0,40}\b(?:est|sont|etait|était|etaient|étaient|semble|semblent|parait|paraît|paraissent|reste|restent)\s+(?:bien\s+|encore\s+|toujours\s+|deja\s+|déjà\s+)?(?:vivante?s?|morte?s?)\b/i,
61
+ label: 'outil "vivant/mort"',
62
+ good: 'le fait observe : "repond" / "ne repond pas" + le resultat exact',
63
+ },
46
64
  ]);
47
65
 
48
66
  // A word boundary that treats accented letters as part of a word, so \btier\b
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ // skill-freshness — the pure core of the stale-global-skill guard.
4
+ //
5
+ // The trap this closes (observed 2026-07-21): a manual copy of a skill under
6
+ // ~/.claude/skills/<name>/SKILL.md drifts silently, and the slash command can
7
+ // load THAT copy instead of the project's .claude/skills/<name>/SKILL.md — so a
8
+ // feature shipped in the project skill stays invisible (the 2.53.0 auto-dispatch
9
+ // rail did not fire because of a June 30 global copy). The official Claude Code
10
+ // docs do not clearly settle the name-collision priority between the two levels
11
+ // (checked 2026-07-21), so this guard flags the FACT — the two files differ —
12
+ // and hands the human the exact sync command; it does not claim a loading rule
13
+ // and it never writes into ~/.claude itself (a user-level variant may be
14
+ // deliberate; the human decides).
15
+ //
16
+ // Pure: comparison + decision + message. The I/O shell (SessionStart hook)
17
+ // feeds it directories and prints the reminder.
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ // Compare the project's skills with same-named global copies, by CONTENT.
23
+ // A newer mtime does not mean "up to date"; byte equality means "faithful".
24
+ // Returns { diverged: [name...], checked: <count> }; every fs error on one
25
+ // skill is swallowed (that skill is simply not compared) so a permission oddity
26
+ // can never break the caller.
27
+ function compareSkills({ projectSkillsDir, globalSkillsDir }) {
28
+ const out = { diverged: [], checked: 0 };
29
+ let names;
30
+ try {
31
+ names = fs.readdirSync(projectSkillsDir, { withFileTypes: true })
32
+ .filter((e) => e.isDirectory())
33
+ .map((e) => e.name);
34
+ } catch {
35
+ return out; // project has no skills dir -> nothing to compare
36
+ }
37
+ for (const name of names) {
38
+ try {
39
+ const proj = path.join(projectSkillsDir, name, 'SKILL.md');
40
+ const glob = path.join(globalSkillsDir, name, 'SKILL.md');
41
+ if (!fs.existsSync(glob)) continue; // no global twin -> no masking risk
42
+ const a = fs.readFileSync(proj);
43
+ const b = fs.readFileSync(glob);
44
+ out.checked += 1;
45
+ if (!a.equals(b)) out.diverged.push(name);
46
+ } catch {
47
+ // unreadable pair -> skip silently (never block a session start)
48
+ }
49
+ }
50
+ return out;
51
+ }
52
+
53
+ // Bounded, factual reminder. Empty string when nothing diverged (the hook then
54
+ // prints nothing at all). Names are capped so a badly drifted machine does not
55
+ // flood the context; the sync command is exact and copy-pastable.
56
+ const MAX_NAMES = 6;
57
+
58
+ // A skill dir name lands verbatim in the injected context and in the suggested
59
+ // cp command. Legit names are kebab-case; strip control chars and whitespace
60
+ // runs from anything else so a crafted dir name cannot shape the reminder.
61
+ function cleanName(n) {
62
+ return String(n).replace(/[\u0000-\u001F\u007F]+/g, ' ').replace(/\s+/g, ' ').trim();
63
+ }
64
+
65
+ function formatReminder(diverged, { projectSkillsDir = '.claude/skills', globalSkillsDir = '~/.claude/skills' } = {}) {
66
+ if (!Array.isArray(diverged) || diverged.length === 0) return '';
67
+ const names = diverged.map(cleanName);
68
+ const shown = names.slice(0, MAX_NAMES).join(', ');
69
+ const more = names.length > MAX_NAMES ? ` (+${names.length - MAX_NAMES} autres)` : '';
70
+ const one = names[0];
71
+ return [
72
+ `Skills globaux divergents detectes : ${shown}${more}.`,
73
+ `La copie ${globalSkillsDir}/<nom>/SKILL.md differe du skill projet — elle peut etre celle que la commande /<nom> charge (constate sur ce piege le 2026-07-21).`,
74
+ `Synchronise depuis le projet (ex: cp ${projectSkillsDir}/${one}/SKILL.md ${globalSkillsDir}/${one}/SKILL.md) ou supprime la copie globale si elle n'est pas voulue.`,
75
+ `Rien n'est modifie automatiquement : une variante user-level peut etre deliberee.`,
76
+ ].join(' ');
77
+ }
78
+
79
+ module.exports = {
80
+ compareSkills,
81
+ formatReminder,
82
+ MAX_NAMES,
83
+ };
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // SessionStart hook — stale-global-skill guard (I/O shell over
5
+ // lib/skill-freshness.js).
6
+ //
7
+ // At each session start, compare the project's .claude/skills/<n>/SKILL.md with
8
+ // the same-named ~/.claude/skills/<n>/SKILL.md. On a content divergence, inject
9
+ // one bounded factual reminder (names + exact sync command). Silent when
10
+ // everything is faithful, when there is no global copy, or when the project has
11
+ // no skills. NEVER blocks a session: exit 0 on every path, including internal
12
+ // errors. It never writes into ~/.claude — it reports, the human decides.
13
+
14
+ const os = require('os');
15
+ const path = require('path');
16
+ const freshness = require('./lib/skill-freshness');
17
+
18
+ function buildContext(projectDir, homeDir) {
19
+ const projectSkillsDir = path.join(projectDir, '.claude', 'skills');
20
+ const globalSkillsDir = path.join(homeDir, '.claude', 'skills');
21
+ const { diverged } = freshness.compareSkills({ projectSkillsDir, globalSkillsDir });
22
+ return freshness.formatReminder(diverged, {
23
+ projectSkillsDir: '.claude/skills',
24
+ globalSkillsDir: path.join(homeDir, '.claude', 'skills'),
25
+ });
26
+ }
27
+
28
+ if (require.main === module) {
29
+ try {
30
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
31
+ const ctx = buildContext(projectDir, os.homedir());
32
+ if (ctx) {
33
+ process.stdout.write(
34
+ JSON.stringify({
35
+ hookSpecificOutput: {
36
+ hookEventName: 'SessionStart',
37
+ additionalContext: ctx,
38
+ },
39
+ })
40
+ );
41
+ }
42
+ } catch {
43
+ // A guard must never take a session down with it.
44
+ }
45
+ process.exit(0);
46
+ }
47
+
48
+ module.exports = { buildContext };
@@ -41,7 +41,25 @@ rappel des récidivistes connus.
41
41
  | gate | point de contrôle / porte |
42
42
  | leaf | étape / tâche |
43
43
  | tier | niveau / gamme |
44
+ | nudge | rappel / conseil injecté |
45
+ | up-tier | monter en gamme |
46
+ | ladder | échelle |
47
+ | rung | palier |
48
+ | runtime | moteur d'exécution |
44
49
  | "forger" un token | générer / créer un token |
50
+ | un outil/MCP/serveur "vivant" ou "mort" | le fait observé : "répond" / "ne répond pas" + le résultat exact |
51
+
52
+ ## Précision factuelle (le fond, pas seulement la forme)
53
+
54
+ Le langage clair ne suffit pas s'il reste vague. Deux obligations :
55
+
56
+ 1. **Zéro anthropomorphisme d'outil.** Un MCP, un serveur, un hook n'est ni
57
+ "vivant", ni "mort", ni "content". Dis le fait observé : "`byan_ping` a
58
+ répondu en 0.3s", "le serveur ne répond pas (timeout 8s)".
59
+ 2. **Un état = une observation nommée.** Toute affirmation sur l'état d'un outil
60
+ ou d'un résultat nomme l'outil, l'appel effectué et la valeur retournée —
61
+ pas un résumé flou ("ça marche", "il monte pas"). Si tu n'as pas fait
62
+ l'appel, dis-le : "non vérifié".
45
63
 
46
64
  ## Le mécanisme (comment c'est tenu, sans boucle de réécriture)
47
65
 
@@ -15,6 +15,10 @@
15
15
  {
16
16
  "type": "command",
17
17
  "command": "p=\"$CLAUDE_PROJECT_DIR/.claude/hooks/soul-memory-check.js\"; [ -f \"$p\" ] || exit 0; exec node \"$p\""
18
+ },
19
+ {
20
+ "type": "command",
21
+ "command": "p=\"$CLAUDE_PROJECT_DIR/.claude/hooks/skill-freshness-check.js\"; [ -f \"$p\" ] || exit 0; exec node \"$p\""
18
22
  }
19
23
  ]
20
24
  }
@@ -62,6 +62,33 @@ net (`agent-gate-check.js`) at turn end — not a guarantee enforced before disp
62
62
  Full doctrine + runtime routing table: see @.claude/rules/agent-entry-gate.md and
63
63
  @docs/intelligent-dispatch.md
64
64
 
65
+ ## 0.5. Rail natif automatique — byan-auto-dispatch (OBLIGATOIRE)
66
+
67
+ A CHAQUE tache non-conversationnelle recue par /byan-byan, le dispatch et
68
+ l'execution passent par le workflow natif — SANS que l'utilisateur le demande :
69
+
70
+ 1. Appelle l'outil **Workflow** avec
71
+ `{ scriptPath: '.claude/workflows/byan-auto-dispatch.js', args: { task: <la demande, verbatim>, stamp: <horodatage ISO courant> } }`.
72
+ Ne demande pas la permission de le lancer ; ne substitue pas une execution
73
+ directe main-thread au workflow.
74
+ 2. Le workflow fait tout le rail : il decoupe la tache en etapes typees, route
75
+ chaque etape sur le bon modele (echelle par complexite : haiku < 34,
76
+ sonnet < 67, opus < 90, fable >= 90 ; moteur Codex pour shell/deploiement
77
+ avec repli Claude ; la verification reste sur le modele de session), ecrit
78
+ `_byan-output/plan.md`, execute chaque etape sur le modele route, verifie.
79
+ 3. Au retour : montre la table etape x nature x complexite x moteur x modele,
80
+ le verdict de verification, et pointe `_byan-output/plan.md`. Le gate
81
+ utilisateur est EN FIN, sur le livrable.
82
+ 4. **Exceptions** (les seules) : une question simple / de la conversation ->
83
+ reponds directement ; une action destructive -> confirme d'abord ; un FD
84
+ multi-feature deja engage -> la phase DISPATCH du FD prend le dessus (elle
85
+ peut invoquer ce meme workflow feature par feature) ; l'utilisateur demande
86
+ explicitement une execution directe sans workflow.
87
+
88
+ Note honnete : l'outil Workflow peut demander une autorisation a l'invocation
89
+ selon le mode de permissions de la session — c'est le harnais qui la pose, pas
90
+ toi ; ne la re-demande pas en plus.
91
+
65
92
  ## 1. Activation triggers
66
93
 
67
94
  Invoke this protocol when the user :
@@ -142,6 +169,7 @@ Never call `byan_update_apply` without explicit user consent. That tool returns
142
169
  - **Authoring aid** : BEFORE writing a script, call `byan_dispatch` with `{ leaves: [{ label, nature? }] }` (batch mode) to get the `opts.model` per leaf ; write `model:` only where non-null. Report with `node _byan/mcp/byan-mcp-server/bin/byan-tier-script.js <file> [--json]`.
143
170
  - No per-leaf effort knob exists (the API exposes only `model`), so effort-by-complexity reduces to model-by-complexity.
144
171
  - **Output** : a table `{ feature → specialist → model → strategy → estimated_tokens }`.
172
+ - **Execution engine per feature** : le workflow natif `byan-auto-dispatch` (section 0.5) prend la feature comme `task` — meme decoupage, meme echelle de modeles, meme plan.md. Repartition des roles : `byan-hermes-dispatch` reste l'ORCHESTRATEUR du BUILD (phase 5) ; `byan-auto-dispatch` est le MOTEUR qu'il peut invoquer par feature (une invocation Workflow = une feature). Pas deux pilotes : un orchestrateur, un moteur.
145
173
  - **If no specialist matches** : halt. Ask user whether to run INT (agent recruitment) first. Do NOT fallback silently to general-purpose.
146
174
  - **Exit gate** : user validates the mapping.
147
175
 
@@ -0,0 +1,210 @@
1
+ export const meta = {
2
+ name: 'byan-auto-dispatch',
3
+ description: 'Decoupe une tache en etapes typees, route chaque etape sur le bon modele (haiku/sonnet/opus/fable par complexite ; Codex pour shell/deploiement), ecrit _byan-output/plan.md, execute et verifie.',
4
+ whenToUse: 'Rail automatique de /byan-byan : toute tache non-conversationnelle passe par ce workflow sans demande explicite. Le gate humain reste en fin, sur le livrable.',
5
+ phases: [
6
+ { title: 'Decoupage', detail: 'un agent d analyse decoupe la tache en etapes typees (nature + complexite 0-100)', model: 'sonnet' },
7
+ { title: 'Routage', detail: 'pur code : echelle haiku<34 / sonnet<67 / opus<90 / fable>=90 ; moteur Codex pour shell/deploiement, verification toujours Claude' },
8
+ { title: 'Plan', detail: 'ecrit _byan-output/plan.md (table etape x modele x moteur)', model: 'sonnet' },
9
+ { title: 'Execution', detail: 'un agent par etape, sequentiel, sur le modele route' },
10
+ { title: 'Verification', detail: 'controle du livrable sur le modele de session (jamais delegue)' },
11
+ ],
12
+ };
13
+
14
+ // BYAN-TIER: reviewed — les modeles des etapes d'execution sont CALCULES par le
15
+ // routeur inline (echelle v3 par complexite), pas des choix statiques ; les deux
16
+ // leaves statiques (analyse -> sonnet, ecriture mecanique du plan -> sonnet)
17
+ // portent leur niveau en litteral, verifiable par le linter.
18
+ //
19
+ // Un script natif n'a NI import NI acces fichier : l'echelle de routage est donc
20
+ // posee ici en clair (la meme table que _byan/mcp/byan-mcp-server/lib/
21
+ // dispatch-router.js, qui reste la source de verite testee cote lib), et le
22
+ // plan.md est ecrit par un agent (qui, lui, a l'outil Write).
23
+ //
24
+ // args attendus : { task: string, stamp?: string ISO }
25
+ // (stamp vient du fil principal — un script natif n a pas le droit de lire
26
+ // l horloge lui-meme, ca casserait la reprise sur relance.)
27
+
28
+ // Accepte args en objet OU en chaine JSON (selon le chemin d'invocation), et, en
29
+ // dernier recours, une chaine nue traitee comme la tache elle-meme.
30
+ let entree = {};
31
+ if (args && typeof args === 'object') {
32
+ entree = args;
33
+ } else if (typeof args === 'string' && args.trim()) {
34
+ try {
35
+ const p = JSON.parse(args);
36
+ entree = (p && typeof p === 'object') ? p : { task: String(p) };
37
+ } catch {
38
+ entree = { task: args };
39
+ }
40
+ }
41
+ const tache = String(entree.task || '').trim();
42
+ const stamp = String(entree.stamp || 'horodatage non fourni');
43
+ if (!tache) return { ok: false, erreur: 'args.task manquant : rien a dispatcher' };
44
+
45
+ // --- Phase 1 : DECOUPAGE (analyse -> sonnet, conforme a la doctrine des niveaux)
46
+ phase('Decoupage');
47
+ const PLAN_SCHEMA = {
48
+ type: 'object',
49
+ required: ['resume', 'etapes'],
50
+ additionalProperties: false,
51
+ properties: {
52
+ resume: { type: 'string', description: 'La tache reformulee en une phrase.' },
53
+ etapes: {
54
+ type: 'array',
55
+ minItems: 1,
56
+ maxItems: 10,
57
+ items: {
58
+ type: 'object',
59
+ required: ['id', 'titre', 'nature', 'complexite', 'consigne'],
60
+ additionalProperties: false,
61
+ properties: {
62
+ id: { type: 'string', description: 'E1, E2, ...' },
63
+ titre: { type: 'string' },
64
+ nature: {
65
+ enum: ['exploration', 'analyse', 'implementation', 'verification', 'shell', 'deploiement', 'navigation', 'doc'],
66
+ description: 'exploration=lire/scanner ; analyse=juger/concevoir ; implementation=ecrire du code/contenu ; verification=controler ; shell/deploiement/navigation=execution systeme ; doc=documentation',
67
+ },
68
+ complexite: { type: 'number', minimum: 0, maximum: 100 },
69
+ consigne: { type: 'string', description: 'L instruction complete et autonome pour executer cette etape.' },
70
+ },
71
+ },
72
+ },
73
+ },
74
+ };
75
+ const decoupe = await agent(
76
+ 'Tu decoupes une tache en etapes executables, chacune typee et notee en complexite.\n' +
77
+ 'Regles : chaque etape est autonome (sa consigne suffit pour l executer) ; ordonne-les ;\n' +
78
+ 'la complexite est 0-100 (0=trivial, 100=extreme) ; ne cree pas d etape superflue (rasoir d Ockham) ;\n' +
79
+ 'inclus une etape de verification finale seulement si la tache produit du code ou un livrable testable.\n' +
80
+ 'Aucun absolu non source dans les consignes (pas de "jamais/toujours" nus).\n\n' +
81
+ 'Tache a decouper :\n' + tache,
82
+ { label: 'analyse-decoupage', phase: 'Decoupage', model: 'sonnet', schema: PLAN_SCHEMA }
83
+ );
84
+ // Le schema est une consigne forte, pas un contrat absolu : un retour nul ou
85
+ // difforme (agent saute, erreur terminale) doit finir en verdict propre, pas en
86
+ // TypeError qui avorte le workflow.
87
+ if (!decoupe || !Array.isArray(decoupe.etapes) || decoupe.etapes.length === 0) {
88
+ return { ok: false, erreur: 'decoupage vide ou difforme : aucune etape exploitable', tache };
89
+ }
90
+ log(`Decoupage : ${decoupe.etapes.length} etape(s) — ${decoupe.resume}`);
91
+
92
+ // --- Phase 2 : ROUTAGE (pur code, zero agent)
93
+ phase('Routage');
94
+ const NATURES_CODEX = ['shell', 'deploiement', 'navigation'];
95
+ function moteurPour(nature) {
96
+ if (nature === 'verification') return 'claude'; // ligne rouge : jamais delegue
97
+ return NATURES_CODEX.includes(nature) ? 'codex' : 'claude';
98
+ }
99
+ // Echelle v3 par complexite (miroir de dispatch-router.claudeModelForComplexity).
100
+ function modeleClaudePour(cx) {
101
+ if (cx < 34) return 'haiku';
102
+ if (cx < 67) return 'sonnet';
103
+ if (cx < 90) return 'opus';
104
+ return 'fable'; // extreme : dernier recours (~2x le prix d Opus)
105
+ }
106
+ const table = decoupe.etapes.map((e) => {
107
+ const moteur = moteurPour(e.nature);
108
+ // Une etape de verification herite du modele de session (opts.model omis) ;
109
+ // toute autre etape porte le modele de l echelle. Une etape routee Codex est
110
+ // pilotee par un agent Claude (au modele de l echelle) qui tente `codex exec`
111
+ // et replie sur Claude si Codex n est pas disponible.
112
+ const modele = e.nature === 'verification' ? null : modeleClaudePour(e.complexite);
113
+ return { ...e, moteur, modele };
114
+ });
115
+ for (const l of table) {
116
+ log(`Routage ${l.id} "${l.titre}" : nature=${l.nature} complexite=${l.complexite} -> moteur=${l.moteur} modele=${l.modele || 'session'}`);
117
+ }
118
+
119
+ // --- Phase 3 : PLAN (contenu construit en pur code, ecrit par un agent mecanique)
120
+ phase('Plan');
121
+ const lignesTable = table.map((l) =>
122
+ `| ${l.id} | ${l.titre} | ${l.nature} | ${l.complexite} | ${l.moteur} | ${l.modele || 'modele de session'} |`
123
+ );
124
+ const planMd = [
125
+ '# Plan de dispatch — ' + decoupe.resume,
126
+ '',
127
+ `- Tache d origine : ${tache.replace(/\n/g, ' ').slice(0, 300)}`,
128
+ `- Genere par : workflow natif byan-auto-dispatch (${stamp})`,
129
+ '- Echelle de routage : haiku < 34, sonnet < 67, opus < 90, fable >= 90 (par complexite) ;',
130
+ ' moteur Codex pour shell/deploiement/navigation (repli Claude si indisponible) ;',
131
+ ' la verification reste sur le modele de session, non deleguee.',
132
+ '',
133
+ '| Etape | Titre | Nature | Complexite | Moteur | Modele |',
134
+ '|-------|-------|--------|------------|--------|--------|',
135
+ ...lignesTable,
136
+ '',
137
+ '## Consignes par etape',
138
+ '',
139
+ ...table.map((l) => `### ${l.id} — ${l.titre}\n\n${l.consigne}\n`),
140
+ ].join('\n');
141
+ await agent(
142
+ 'Ecris EXACTEMENT le contenu ci-dessous dans le fichier _byan-output/plan.md ' +
143
+ '(cree le dossier _byan-output s il n existe pas ; remplace le fichier s il existe). ' +
144
+ 'N ajoute rien, ne corrige rien, ne commente rien. Reponds "ecrit" quand c est fait.\n\n' +
145
+ '--- CONTENU A ECRIRE TEL QUEL ---\n' + planMd,
146
+ { label: 'mech-ecrire-plan', phase: 'Plan', model: 'sonnet' }
147
+ );
148
+ log('Plan ecrit : _byan-output/plan.md');
149
+
150
+ // --- Phase 4 : EXECUTION (sequentielle : les etapes d une meme tache partagent
151
+ // souvent des fichiers ; le contexte des etapes precedentes est transmis borne)
152
+ phase('Execution');
153
+ const resultats = [];
154
+ for (const l of table) {
155
+ const contexte = resultats.length
156
+ ? '\n\nResultats des etapes precedentes (contexte) :\n' +
157
+ resultats.map((r) => `- ${r.id} (${r.titre}) : ${String(r.resultat).slice(0, 1200)}`).join('\n')
158
+ : '';
159
+ const consigneCodex =
160
+ l.moteur === 'codex'
161
+ ? '\n\nCette etape est routee vers CODEX : lance `codex exec` en lecture seule pour obtenir un diff unifie, ' +
162
+ 'puis applique le diff toi-meme (`git apply`). Si la commande `codex` n est pas disponible ou echoue, ' +
163
+ 'dis-le en une phrase et fais l etape toi-meme sur Claude (repli prevu).'
164
+ : '';
165
+ // Un echec dur d une etape (agent saute, erreur terminale) ne doit pas avorter
166
+ // le workflow : il est enregistre comme resultat KO et la verification finale
167
+ // le jugera — jamais de coupe silencieuse, jamais d abandon muet.
168
+ let res;
169
+ try {
170
+ res = await agent(
171
+ `Etape ${l.id} — ${l.titre} (nature ${l.nature}, complexite ${l.complexite}).\n` +
172
+ 'Execute la consigne ci-dessous, completement. Rapporte ce qui a ete fait, les fichiers touches, ' +
173
+ 'et toute impossibilite REELLE rencontree (dis le fait exact, pas une image).\n\n' +
174
+ 'Consigne :\n' + l.consigne + consigneCodex + contexte,
175
+ {
176
+ label: `etape-${l.id}`,
177
+ phase: 'Execution',
178
+ ...(l.modele ? { model: l.modele } : {}),
179
+ }
180
+ );
181
+ } catch (e) {
182
+ res = null;
183
+ }
184
+ if (res == null) res = `ECHEC : l etape ${l.id} n a pas rendu de resultat (agent interrompu ou en erreur).`;
185
+ resultats.push({ id: l.id, titre: l.titre, moteur: l.moteur, modele: l.modele || 'session', resultat: res });
186
+ log(`Execution ${l.id} terminee (modele=${l.modele || 'session'})`);
187
+ }
188
+
189
+ // --- Phase 5 : VERIFICATION (modele de session, jamais delegue)
190
+ phase('Verification');
191
+ const verdict = await agent(
192
+ 'Tu es le controleur (tu n as PAS fait le travail). Verifie le resultat de chaque etape ' +
193
+ 'contre la tache d origine : livre reellement ? teste quand testable ? rien coupe en silence ?\n' +
194
+ 'Rends : VERDICT: OK ou VERDICT: KO en premiere ligne, puis une ligne par etape (id, tenu/manque, le fait exact).\n\n' +
195
+ `Tache d origine :\n${tache}\n\n` +
196
+ 'Resultats :\n' +
197
+ resultats.map((r) => `--- ${r.id} ${r.titre} ---\n${String(r.resultat).slice(0, 2000)}`).join('\n\n'),
198
+ { label: 'verifie-livrable', phase: 'Verification' }
199
+ );
200
+
201
+ const ok = /^\s*VERDICT:\s*OK/i.test(String(verdict));
202
+ log(`Verification : ${ok ? 'OK' : 'KO'}`);
203
+ return {
204
+ ok,
205
+ tache: decoupe.resume,
206
+ plan: '_byan-output/plan.md',
207
+ table: table.map(({ consigne, ...reste }) => reste),
208
+ resultats: resultats.map((r) => ({ ...r, resultat: String(r.resultat).slice(0, 2000) })),
209
+ verdict: String(verdict),
210
+ };
@@ -167,6 +167,14 @@ Exemple : "Attends. Ca touche au noyau. On est jamais assez parano. Qu'est-ce qu
167
167
  **Pourquoi :** Mantra IA-26. On forge une ame, pas un token.
168
168
  **Au lieu de ca :** le verbe reel ("generer un token", "creer un token")
169
169
 
170
+ **Interdit :** dire qu'un outil, un MCP ou un serveur est "vivant" ou "mort"
171
+ **Pourquoi :** Mantra IA-26. L'anthropomorphisme cache le fait ; l'utilisateur veut la mesure, pas l'image.
172
+ **Au lieu de ca :** le fait observe, outil nomme : "byan_ping a repondu en 0.3s", "le serveur ne repond pas (timeout 8s)"
173
+
174
+ **Interdit :** le vocabulaire Claude interne en prose ("nudge", "up-tier", "ladder", "rung", "runtime")
175
+ **Pourquoi :** Mantra IA-26. C'est le dialecte de l'outillage, pas une langue.
176
+ **Au lieu de ca :** rappel injecte, monter en gamme, echelle, palier, moteur d'execution
177
+
170
178
  ---
171
179
 
172
180
  ### Section 5 — Non-dits
@@ -94,7 +94,7 @@
94
94
  "name": "byan-byan",
95
95
  "module": "core",
96
96
  "tier": "connector-bound",
97
- "sourceHash": "fcea02854887283c8ff1d4b7d09778d62fc7ce52959a00577e87091e7c31f991"
97
+ "sourceHash": "65c1034479457298f89c25405d4be7e840ef18d9df3102f1f224b59e3094b20c"
98
98
  },
99
99
  "byan-byan-v2": {
100
100
  "name": "byan-byan-v2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-byan-agent",
3
- "version": "2.52.0",
3
+ "version": "2.54.0",
4
4
  "description": "BYAN v2.8 - Intelligent AI agent creator with ELO trust system + scientific fact-check + Hermes universal dispatcher + native Claude Code integration (hooks, skills, MCP server). Multi-platform (Claude Code, Codex). Merise Agile + TDD + 71 Mantras. ~54% LLM cost savings.",
5
5
  "main": "src/index.js",
6
6
  "bin": {