create-byan-agent 2.50.0 → 2.53.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,99 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [2.53.0] - 2026-07-21
13
+
14
+ ### Added — Rail automatique : workflow natif byan-auto-dispatch (F2)
15
+ - Nouveau workflow Claude natif `.claude/workflows/byan-auto-dispatch.js` : il
16
+ decoupe une tache en etapes typees (nature + complexite 0-100) via un agent
17
+ d'analyse (sonnet, schema JSON force), route chaque etape sur le bon modele
18
+ par l'echelle v3 (haiku < 34, sonnet < 67, opus < 90, fable >= 90), route les
19
+ etapes shell/deploiement/navigation vers Codex (repli Claude annonce si Codex
20
+ n'est pas disponible ; la verification reste sur le modele de session, non
21
+ deleguee), ecrit `_byan-output/plan.md` (table etape x nature x complexite x
22
+ moteur x modele + consignes), execute chaque etape en sequence sur le modele
23
+ route, et verifie le livrable en fin.
24
+ - Cablage skill `byan-byan` (section 0.5) : a CHAQUE tache non-conversationnelle
25
+ recue par `/byan-byan`, ce workflow est invoque AUTOMATIQUEMENT (scriptPath +
26
+ args {task, stamp}), sans demande a l'utilisateur. Exceptions nommees :
27
+ question simple, action destructive (confirmation d'abord), FD multi-feature
28
+ deja engage (sa phase DISPATCH peut invoquer le meme workflow par feature),
29
+ demande explicite d'execution directe. Le gate utilisateur reste en fin, sur
30
+ le livrable.
31
+ - Livraison npm : le script est ajoute au miroir `template-sync.js`.
32
+
33
+ ### Changed — Langage precis, zero dialecte interne (F1, Mantra IA-26)
34
+ - Le filet plain-language s'etend : `nudge`, `up-tier`, `ladder`, `rung`,
35
+ `runtime` rejoignent les mots a remplacer en prose ; nouvelle detection de
36
+ l'anthropomorphisme d'outil (un MCP/serveur/hook dit "vivant" ou "mort") avec
37
+ le remplacement exige : le fait observe, outil nomme ("byan_ping a repondu en
38
+ 0.3s", "le serveur ne repond pas, timeout 8s").
39
+ - Nouvelle clause de precision factuelle dans `.claude/rules/plain-language.md` :
40
+ tout etat d'outil = l'outil nomme + l'appel + le resultat exact ; pas de
41
+ resume flou ("ca marche") ; "non verifie" quand l'appel n'a pas ete fait.
42
+ - L'ancre de voix par tour et le tao (Section 4, Vocabulaire Interdit) portent
43
+ les deux nouveaux interdits. Tests jest etendus (26 verts sur la suite
44
+ plain-language).
45
+
46
+ ## [2.52.0] - 2026-07-20
47
+
48
+ ### Changed — Delegation Codex v3 : pression-only + obeissance au routeur (F1, F2)
49
+ - **F1 — la delegation ne se declenche plus que sous PRESSION budget.** La nature
50
+ d'une tache (code / mecanique) n'est plus un declencheur d'auto-delegation a elle
51
+ seule. Deleguer au Codex de l'abonnement (qui reste en dessous de Claude en
52
+ qualite) vaut le coup pour epargner le budget Claude, pas par tache de code. Hors
53
+ pression, le code tourne sur Claude sans rappel. Le seuil par defaut passe a 75 %
54
+ (`autodelegate-decision.js` + `codex-delegate-guard.js` alignes sur un seul
55
+ nombre). Sans jauge `budget`, la pression n'est pas calculee et rien n'est
56
+ propose (documente, non bloquant).
57
+ - **F2 — le garde `codex-delegate-guard` obeit au routeur.** Le blocage ne mord
58
+ que pour une tache que le `dispatch-router` route lui-meme vers Codex (nature
59
+ execution / shell / deploiement / devops / navigateur). Une tache
60
+ architecture / refactor / qualite / planif — et toute verification — route vers
61
+ Claude, donc le garde reste silencieux. Le garde recalcule router + pression
62
+ uniquement dans la branche ou un blocage est encore possible (les chemins
63
+ d'autorisation courants ne paient rien) ; les deux signaux echouent en mode
64
+ ouvert (signal irresolu -> on autorise, sans blocage a tort).
65
+
66
+ ### Changed — Echelle de modele par complexite + Fable dernier recours (F3)
67
+ - **Cote routeur (`dispatch-router.claudeModelForComplexity`)** : echelle a quatre
68
+ rungs — `haiku` (bas) -> `sonnet` (moyen) -> `opus` (haut) -> `fable` (extreme,
69
+ dernier recours, ~2x le prix d'Opus). C'est une RECOMMANDATION par tache, pas un
70
+ changement du modele de session en cours (Claude Code n'expose pas ce levier).
71
+ - **Cote leaves de workflow (`native-tiers` + linter)** : un up-tier explicite
72
+ (`opus` / `fable`) devient un choix d'auteur AUTORISE sur n'importe quel leaf,
73
+ miroir de l'echelle du routeur. Le plancher anti-downgrade est conserve (haiku /
74
+ sonnet interdits sur un leaf protege) ; seul le plafond est ouvert (le pin-up
75
+ n'est plus une violation). Nouveaux : `UP_TIER_MODELS`, `isUpTierModel`.
76
+ - **Revirement d'invariant assume** : l'ancienne ligne rouge "pas de Fable /
77
+ pas de pin-up" est levee cote Claude. Cote Codex elle tient (Codex ne peut pas
78
+ lancer un modele Claude) : `assertNoFable` garde le chemin Codex + `codex-bridge`.
79
+ - Doctrine alignee : `native-workflows.md`, `native-workflows-contract.md`,
80
+ `intelligent-dispatch.md`, `codex-auto-delegation.md`, skill `byan-byan`.
81
+
82
+ ## [2.51.0] - 2026-07-20
83
+
84
+ ### Changed — Delegation Codex : fermeture de l'auto-esquive (option B)
85
+ - Le blocage `codex-delegate-guard` ne peut plus etre contourne par BYAN lui-meme.
86
+ Retire : la marque de contenu auto-ecrite (`// BYAN-DELEGATE: reviewed`) et
87
+ l'auto-passe par re-soumission/fenetre de grace — les deux permettaient a Claude
88
+ de s'auto-accorder la sortie. Les seules sorties valides sont desormais : une
89
+ delegation Codex reelle ce tour, Codex indisponible (detecte via `codex --version`),
90
+ ou un opt-out HUMAIN ("reste sur claude" / "sans codex" dans la demande, ou le
91
+ fichier `.byan-codex-autodelegate/off`). Le motif "script court / latence / je
92
+ verifie" n'est plus une raison valide. Coeur pur `lib/codex-delegate-gate.js`
93
+ (`humanOptOutFromText`, `decideDelegateGate` durci) + `codex-delegate-guard.js`
94
+ (probe Codex, opt-out depuis la demande). Le rendu injecte (`autodelegate-decision`
95
+ renderNudge), le skill `byan-byan` et `docs/codex-auto-delegation.md` sont alignes.
96
+
97
+ ### Notes
98
+ - Constat de session : le CLI Codex de l'abo ChatGPT est plafonne a gpt-5.5 (5.6
99
+ refuse). Recherche croisee : gpt-5.5 est sous le meilleur Claude sur tous les
100
+ domaines mesures. Donc la delegation Codex vaut pour economiser le budget Claude,
101
+ pas pour la qualite. Prochain chantier (FD dedie) : delegation declenchee par la
102
+ PRESSION budget (~75%) au lieu de par-nature ; le blocage obeit au routeur ;
103
+ echelle modele Claude par complexite (haiku/sonnet/opus) + Fable en dernier recours.
104
+
12
105
  ## [2.50.0] - 2026-07-17
13
106
 
14
107
  ### Added — BYAN souverainete (les concepts BYAN pilotent Claude, ne se font plus doubler)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-byan-agent",
3
- "version": "2.50.0",
3
+ "version": "2.51.0",
4
4
  "description": "BYAN - Intelligent AI agent installer with multi-platform native support (Claude Code, Codex/OpenCode)",
5
5
  "bin": {
6
6
  "create-byan-agent": "bin/create-byan-agent-v2.js"
@@ -1,35 +1,31 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- // PreToolUse hook — WI-1, the DENT for the armed Codex-delegation lane.
4
+ // PreToolUse hook — the armed Codex-delegation guard (v3 : router-obedient +
5
+ // pressure-gated). See lib/codex-delegate-gate.js for the policy.
5
6
  //
6
- // When the Codex lane is ARMED (yanstaller option on AND Codex linked) and the
7
- // current turn is about to WRITE delegable code without having delegated to Codex
8
- // first, DENY ONCE with the exact instruction to delegate. This is the tooth that
9
- // makes "delegate to Codex when armed" govern instead of being prose Claude can
10
- // double with a polite excuse (the session incident).
7
+ // It DENIES a code Write/Edit only when ALL hold : the lane is armed (yanstaller
8
+ // option on AND Codex linked), the ROUTER routes the task to Codex, we are under
9
+ // Claude budget PRESSURE, Codex is available, and no delegation happened this turn
10
+ // and the human has not opted out. Otherwise it allows. Never traps a turn on an
11
+ // internal error (fails open), always exits 0.
11
12
  //
12
- // Speed-bump, not a wall (the user's red line) :
13
- // - escape-hatch : `.byan-codex-autodelegate/off` silences it (shared with the
14
- // nudge hook one switch for the whole Codex-delegation posture).
15
- // - `// BYAN-DELEGATE: reviewed` in the file acknowledges a deliberate
16
- // Claude-lane choice (Codex unavailable, judgment/verify) and passes.
17
- // - deny-once : an identical resubmission passes, and a grace window after a
18
- // deny lets a multi-file build proceed without nagging on every file.
19
- //
20
- // Never traps a turn, always exits 0. Pure decision in lib/codex-delegate-gate.js;
21
- // armed/linked detection reused from codex-autodelegate.js; delegability from
22
- // autodelegate-decision.js; the turn transcript via transcript-read.js.
13
+ // The two computed signals (router decision, budget pressure) are only evaluated
14
+ // in the would-deny branch, so the common allow paths pay nothing. Both fail open
15
+ // (an unresolvable signal -> allow, never a wrong block).
23
16
 
24
17
  const fs = require('fs');
25
18
  const path = require('path');
19
+ const { spawnSync } = require('child_process');
20
+ const { pathToFileURL } = require('url');
26
21
  const gate = require('./lib/codex-delegate-gate');
27
22
  const { loadConfig, codexLinked, toggledOff } = require('./codex-autodelegate');
28
- const { looksDelegable } = require('./lib/autodelegate-decision');
29
- const { extractRecentMessages, extractLastAssistantText, contentToText } = require('./lib/transcript-read');
23
+ const { estimateClaudeUsage } = require('./lib/usage-estimator');
24
+ const { extractRecentMessages, contentToText } = require('./lib/transcript-read');
30
25
 
31
26
  const ROOT = process.env.CLAUDE_PROJECT_DIR || process.cwd();
32
- const GRACE_MS = 120000;
27
+ const ROUTER_PATH = path.resolve(__dirname, '../../_byan/mcp/byan-mcp-server/lib/dispatch-router.js');
28
+ const DEFAULT_PRESSURE_THRESHOLD = 75; // % of the Claude budget window
33
29
 
34
30
  function readStdin() {
35
31
  return new Promise((resolve) => {
@@ -40,44 +36,47 @@ function readStdin() {
40
36
  });
41
37
  }
42
38
 
43
- function allow() {
44
- return { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' } };
45
- }
46
-
47
- function deny(reason) {
48
- return {
49
- hookSpecificOutput: {
50
- hookEventName: 'PreToolUse',
51
- permissionDecision: 'deny',
52
- permissionDecisionReason: reason,
53
- },
54
- };
55
- }
39
+ const allow = () => ({ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' } });
40
+ const deny = (reason) => ({ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason } });
56
41
 
57
- function sidecarPath(root) {
58
- return path.join(root, '.byan-codex-delegate', 'last-deny.json');
42
+ // Codex availability probe : `codex --version` exits 0. Absent/broken -> false ->
43
+ // the guard must NOT deny (staying on Claude is the legit fallback).
44
+ function probeCodexAvailable(runner = spawnSync) {
45
+ try {
46
+ const res = runner('codex', ['--version'], { timeout: 8000, encoding: 'utf8' });
47
+ return res && res.status === 0;
48
+ } catch (_e) {
49
+ return false;
50
+ }
59
51
  }
60
52
 
61
- function readPriorDeny(root) {
53
+ // routerSaysCodex(text) : does dispatch-router route this task's text to Codex?
54
+ // The router keyword-matches on the text (execution/shell/deploy/script...). ESM
55
+ // module dynamically imported from this CJS hook. Fails open (false = don't block)
56
+ // if the router cannot be loaded.
57
+ async function defaultRouteProbe(text) {
62
58
  try {
63
- const p = JSON.parse(fs.readFileSync(sidecarPath(root), 'utf8'));
64
- return p && typeof p === 'object' ? p : null;
59
+ const mod = await import(pathToFileURL(ROUTER_PATH).href);
60
+ return mod.routeRuntime(String(text || '')) === 'codex';
65
61
  } catch (_e) {
66
- return null;
62
+ return false;
67
63
  }
68
64
  }
69
65
 
70
- function writePriorDeny(root, entry) {
66
+ // underPressure : Claude budget usage >= threshold. Needs a budget in the config
67
+ // (no budget -> pct null -> not under pressure -> no delegation, documented).
68
+ function computeUnderPressure(config) {
71
69
  try {
72
- fs.mkdirSync(path.dirname(sidecarPath(root)), { recursive: true });
73
- fs.writeFileSync(sidecarPath(root), JSON.stringify(entry));
70
+ const budget = config && config.budget ? config.budget : null;
71
+ if (!budget) return false;
72
+ const threshold = typeof config.threshold === 'number' ? config.threshold : DEFAULT_PRESSURE_THRESHOLD;
73
+ const usage = estimateClaudeUsage({ budget });
74
+ return typeof usage.pct === 'number' && usage.pct >= threshold;
74
75
  } catch (_e) {
75
- // best-effort : losing the deny memory only costs one extra deny, never a trap
76
+ return false;
76
77
  }
77
78
  }
78
79
 
79
- // The last user message text this turn, for the delegability signal (a code
80
- // target already suffices, this only broadens it).
81
80
  function lastUserText(payload) {
82
81
  const msgs = extractRecentMessages(payload, 12);
83
82
  if (!Array.isArray(msgs)) return '';
@@ -87,45 +86,38 @@ function lastUserText(payload) {
87
86
  return '';
88
87
  }
89
88
 
90
- // Pure-ish assembly of the decision from a payload (exported for tests, fs
91
- // injected via opts.root + opts.now).
92
- function runGuard(payload, { root = ROOT, now = Date.now() } = {}) {
89
+ // runGuard async (the router import is async). opts inject the probes for tests.
90
+ async function runGuard(payload, {
91
+ root = ROOT,
92
+ codexProbe = probeCodexAvailable,
93
+ routeProbe = defaultRouteProbe,
94
+ pressureProbe = computeUnderPressure,
95
+ } = {}) {
93
96
  const toolName = payload.tool_name || payload.toolName || '';
94
97
  if (toolName !== 'Write' && toolName !== 'Edit') return allow();
95
98
 
96
99
  const config = loadConfig(root);
97
100
  const armed = config.enabled === true && codexLinked();
98
- const escaped = toggledOff(root);
101
+ if (!armed) return allow(); // fast path, no transcript/probe work when off
99
102
 
103
+ const escaped = toggledOff(root);
100
104
  const input = payload.tool_input || payload.toolInput || {};
101
105
  const filePath = input.file_path || input.filePath || '';
102
- const content = toolName === 'Edit'
103
- ? String(input.new_string || input.newString || '')
104
- : String(input.content || '');
105
-
106
- const userText = lastUserText(payload);
107
- const delegable = gate.targetIsCode(filePath) || looksDelegable(userText);
108
- const reviewedMarker = gate.hasReviewedMarker(content) || gate.hasReviewedMarker(extractLastAssistantText(payload));
106
+ const isCode = gate.targetIsCode(filePath);
107
+ const humanOptOut = gate.humanOptOutFromText(lastUserText(payload));
109
108
  const delegationSeen = gate.delegationSeenThisTurn(extractRecentMessages(payload, 12) || []);
110
- const turnHash = gate.hashTurn(filePath, content);
109
+
110
+ // Only compute the two expensive signals when a deny is otherwise possible.
111
+ const maybeDeny = !escaped && !humanOptOut && isCode && !delegationSeen;
112
+ const routerSaysCodex = maybeDeny ? await routeProbe(lastUserText(payload)) : false;
113
+ const underPressure = (maybeDeny && routerSaysCodex) ? pressureProbe(config) : false;
114
+ const codexAvailable = (maybeDeny && routerSaysCodex && underPressure) ? codexProbe() : true;
111
115
 
112
116
  const decision = gate.decideDelegateGate({
113
- armed,
114
- delegable,
115
- delegationSeen,
116
- escaped,
117
- reviewedMarker,
118
- priorDeny: readPriorDeny(root),
119
- turnHash,
120
- now,
121
- graceMs: GRACE_MS,
117
+ armed, routerSaysCodex, targetIsCode: isCode, underPressure,
118
+ delegationSeen, escaped, humanOptOut, codexAvailable,
122
119
  });
123
-
124
- if (decision.decision === 'deny') {
125
- writePriorDeny(root, { hash: turnHash, ts: now });
126
- return deny(decision.reason);
127
- }
128
- return allow();
120
+ return decision.decision === 'deny' ? deny(decision.reason) : allow();
129
121
  }
130
122
 
131
123
  if (require.main === module) {
@@ -133,7 +125,7 @@ if (require.main === module) {
133
125
  let out;
134
126
  try {
135
127
  const payload = JSON.parse((await readStdin()) || '{}');
136
- out = runGuard(payload);
128
+ out = await runGuard(payload);
137
129
  } catch (_e) {
138
130
  out = allow();
139
131
  }
@@ -142,4 +134,4 @@ if (require.main === module) {
142
134
  })();
143
135
  }
144
136
 
145
- module.exports = { runGuard, sidecarPath, readPriorDeny, writePriorDeny, lastUserText };
137
+ module.exports = { runGuard, probeCodexAvailable, defaultRouteProbe, computeUnderPressure, lastUserText };
@@ -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;
@@ -22,7 +22,7 @@
22
22
 
23
23
  const { perfFavors } = require('./perf-routing');
24
24
 
25
- const DEFAULT_THRESHOLD = 80;
25
+ const DEFAULT_THRESHOLD = 75;
26
26
  const DEFAULT_INVOCATION = 'codex:codex-rescue --model gpt-5.4';
27
27
  const RED_LINE = 'delegable work only (code / mechanical) — judgment, analysis, soul and verification stay on Claude';
28
28
 
@@ -71,16 +71,12 @@ function decideAutodelegation({ requestText = '', usage = null, config = {} } =
71
71
  };
72
72
  }
73
73
 
74
- if (delegable) {
75
- return {
76
- delegate: true,
77
- mode: 'delegable-only',
78
- pct,
79
- reason: 'request looks like delegable coding work — propose handing it to Codex to spare the Claude 5h budget',
80
- redLine: RED_LINE,
81
- invocation,
82
- };
83
- }
74
+ // v3 : NATURE alone is no longer an auto-delegate trigger. The user's decision
75
+ // is PRESSURE-only — delegating to the subscription Codex (which trails Claude
76
+ // on quality) is worth it to spare the Claude budget, not per coding task. So a
77
+ // delegable task off-pressure does NOT propose Codex. `delegable` is kept only as
78
+ // a signal the perf-routing opt-in below can still use.
79
+ void delegable;
84
80
 
85
81
  // PERF (opt-in): the forces table (user-populated) may favor Codex for this
86
82
  // kind of task even at low pressure. Off by default; always heuristic.
@@ -141,8 +137,10 @@ function renderNudge(decision) {
141
137
  `[BYAN auto-delegate]${gauge}: ${scope} via \`${invocation}\` `
142
138
  + '(runs on the ChatGPT subscription, no API credit).',
143
139
  `Red line: ${decision.redLine}. The lane is ARMED (option on AND Codex linked), so this is a `
144
- + 'DIRECTIVE, not a mere suggestion: delegate the delegable part unless you have a specific reason '
145
- + "not to, fall back to Claude only if Codex is unavailable, and verify Codex's output before commit.",
140
+ + 'DIRECTIVE, not a suggestion: delegate the delegable part. The ONLY valid reasons to stay on '
141
+ + 'Claude are (1) Codex unavailable, or (2) the user asked to stay on Claude. "Small script / '
142
+ + 'latency / I verify anyway" is NOT a valid reason — the codex-delegate-guard denies a delegable '
143
+ + "code write that skips Codex. Verify Codex's output before commit.",
146
144
  ].join(' ');
147
145
  }
148
146
 
@@ -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
@@ -26,7 +26,8 @@ A native workflow script mutates FD/strict state only through the `byan_fd_*` /
26
26
 
27
27
  Each `agent()` leaf runs on the session model unless the call sets `opts.model`.
28
28
  The tiering decision lives in one place — `_byan/mcp/byan-mcp-server/lib/native-tiers.js`
29
- (tier vocabulary, leaf classifier, model map). Three tiers:
29
+ (tier vocabulary, leaf classifier, model map). Three AUTO-routed tiers, plus an
30
+ explicit UP-TIER an author may pin by complexity (v3):
30
31
 
31
32
  - **cheap (`model: 'haiku'`)** — a pure EXPLORATION leaf (read/load/parse/detect).
32
33
  - **balanced (`model: 'sonnet'`)** — two classes land here. (1) MECHANICAL
@@ -40,16 +41,25 @@ The tiering decision lives in one place — `_byan/mcp/byan-mcp-server/lib/nativ
40
41
  leaf may carry `model: 'sonnet'` (its tier) but not `haiku` (`analysis-below-tier`).
41
42
  Semantic/adversarial VERIFICATION is NOT analysis and stays deep.
42
43
  - **deep (OMIT `opts.model`)** — implementation + verification leaves inherit the
43
- session model (no pin-up to opus). A genuinely frontier ANALYSIS leaf opts back to
44
- deep with the `deep-` label prefix (`deep-assess-architecture`), the mirror of the
45
- `mech-` opt-in.
44
+ session model (the cost-safe default: no AUTO pin-up). A genuinely frontier
45
+ ANALYSIS leaf opts back to deep with the `deep-` label prefix
46
+ (`deep-assess-architecture`), the mirror of the `mech-` opt-in.
47
+ - **up-tier (`model: 'opus'` / `model: 'fable'`)** — an EXPLICIT authoring choice
48
+ to raise a genuinely complex leaf ABOVE the inherited tier (v3). `opus` for a
49
+ hard leaf, `fable` as the last-resort top-reasoning model for extreme complexity
50
+ (~2x Opus price). This is the workflow-leaf mirror of the dispatch-router
51
+ complexity ladder (haiku -> sonnet -> opus -> fable). Auto-routing does not pick
52
+ it — the author writes it deliberately. The anti-downgrade floor does not apply
53
+ upward, so the linter allows an up-tier pin on any leaf (it costs more, it does
54
+ not regress quality). The old blanket "no pin-up / no Fable" ban is lifted.
46
55
 
47
56
  The linter splits the two directions:
48
57
 
49
- - **Floor (HARD, blocks the commit)** — a downgrade model on a PROTECTED leaf, a
50
- pin-up, or a half-applied `mech-` opt-in is a contract violation
58
+ - **Floor (HARD, blocks the commit)** — a DOWNGRADE model (haiku/sonnet) on a
59
+ PROTECTED leaf, or a half-applied `mech-` opt-in, is a contract violation
51
60
  (`modelRoutingViolations` + `mechanicalLabelViolations`). This is the STRICT-2
52
- No Downgrade net.
61
+ No Downgrade net. An UP-TIER pin (opus/fable) is not a violation — the floor
62
+ guards downward only.
53
63
  - **Ceiling (ADVISORY, non-blocking)** — an exploration- OR analysis-labelled leaf
54
64
  that runs deep is *reported* (`byan-lint-workflows.js --advise`:
55
65
  `untiered-exploration` -> `model: 'haiku'`, `untiered-analysis` -> `model: 'sonnet'`),
@@ -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
 
@@ -21,8 +21,10 @@ the user should not have to ask for it. Four moments:
21
21
  (`_byan/mcp/byan-mcp-server/lib/agent-matcher.js`), the right model + effort
22
22
  (`dispatch-router.js` / native-tiers), and the right runtime — Codex for
23
23
  execution / shell / deploy / devops / browser ; Claude for architecture /
24
- refactor / quality / planning ; verification stays on Claude ; Fable is not
25
- emitted. No user ask. A suited agent -> use it. NO suited agent -> interview to
24
+ refactor / quality / planning ; verification stays on Claude. The Claude model
25
+ scales on a four-rung complexity ladder (haiku -> sonnet -> opus -> fable, fable
26
+ the extreme last resort) ; Fable is refused on the Codex path only. No user ask.
27
+ A suited agent -> use it. NO suited agent -> interview to
26
28
  frame the need, web-research the trade's competencies + best practices, create
27
29
  the tailored agent (the one place the human is required upstream).
28
30
  3. **Execute in party-mode with a live visual.** The user SEES what happens, live:
@@ -30,13 +32,17 @@ the user should not have to ask for it. Four moments:
30
32
  `TaskCreate` / `TaskUpdate`) plus the dispatch table shown at the top of BUILD.
31
33
  - **Codex-lane** — when the lane is ARMED (yanstaller option on AND Codex
32
34
  linked, i.e. `_byan/_config/autodelegate.json` present with `enabled:true`
33
- AND `~/.codex/auth.json` or `CODEX_API_KEY`) and the task is delegable,
35
+ AND `~/.codex/auth.json` or `CODEX_API_KEY`) and the task is delegable code,
34
36
  ACTUALLY delegate via `codex-bridge.js` (`codex exec` -> unified diff -> YOU
35
- apply it). Fall back to Claude if Codex is unavailable. When armed, the
36
- injected `[BYAN auto-delegate]` note is a DIRECTIVE, not a mere suggestion:
37
- you delegate the delegable work rather than doing it on Claude by default.
38
- When NOT armed (option off or Codex not linked), there is no delegation
39
- you run it on Claude.
37
+ apply it) or the `codex:codex-rescue` agent. This is a DIRECTIVE, enforced by
38
+ the `codex-delegate-guard` PreToolUse tooth : it DENIES a delegable code
39
+ Write/Edit that skipped Codex. The ONLY valid reasons to stay on Claude are
40
+ (1) Codex unavailable (auto-detected), or (2) the USER asked to stay on Claude
41
+ ("reste sur Claude" / "sans codex", or the `.byan-codex-autodelegate/off`
42
+ switch). "Small script / latency / I verify anyway" is NOT valid — that is
43
+ exactly the self-dodge the tooth closes (option B). You cannot self-grant a
44
+ bypass. When NOT armed (option off or Codex not linked), there is no
45
+ delegation — you run it on Claude.
40
46
  - **Claude-lane** — run it on Claude at the chosen model.
41
47
  4. **User gate at the END.** The user-validation gate lands at the end, once the
42
48
  feature/task is done and there is a deliverable to review — not upstream. (In a
@@ -56,6 +62,33 @@ net (`agent-gate-check.js`) at turn end — not a guarantee enforced before disp
56
62
  Full doctrine + runtime routing table: see @.claude/rules/agent-entry-gate.md and
57
63
  @docs/intelligent-dispatch.md
58
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
+
59
92
  ## 1. Activation triggers
60
93
 
61
94
  Invoke this protocol when the user :
@@ -129,13 +162,14 @@ Never call `byan_update_apply` without explicit user consent. That tool returns
129
162
  - nature `exploration` (load/read/scan/list/parse/fetch...) → `haiku`
130
163
  - nature `mechanical` (binary judgment-free checks : JSON parses, schema matches, lint passes — label prefix `mech-`, explicit opt-in only) → `sonnet`
131
164
  - nature `implementation` / `verification` / `analysis` / unknown → deep = **inherit the session model**
132
- - Keep protected work (verify/analysis/implement) off haiku/sonnet regardless of size ; no pin-up to opus. Pass an explicit `nature` to `byan_dispatch` when you know it.
165
+ - Keep protected work (verify/analysis/implement) off haiku/sonnet regardless of size. Auto-routing does not pin up ; but an author MAY deliberately UP-TIER a genuinely complex leaf to `opus` (hard) or `fable` (extreme, last resort) — the linter allows it (the floor guards downward only). Pass an explicit `nature` to `byan_dispatch` when you know it.
133
166
  - **Inside native workflow scripts** (`.claude/workflows/*.js` OR ad-hoc) the SAME tiering applies per `agent()` leaf via `opts.model`, enforced as a FLOOR not a ceiling on TWO nets :
134
- - **Repo linter** (committed scripts, pre-commit) : `modelRoutingViolations` HARD-blocks a downgrade on a protected leaf, a pin-up, or a half-applied `mech-` opt-in (`mechanical-without-model` / `mechanical-below-tier`) ; an exploration-labelled leaf left deep is a NON-blocking ADVISORY (`byan-lint-workflows.js --advise`), since many such leaves bear a gate/classification/exact-conversion and must stay deep — the human owns that call.
167
+ - **Repo linter** (committed scripts, pre-commit) : `modelRoutingViolations` HARD-blocks a downgrade (haiku/sonnet) on a protected leaf, or a half-applied `mech-` opt-in (`mechanical-without-model` / `mechanical-below-tier`) ; an UP-TIER pin (opus/fable) is allowed, not blocked (the floor guards downward only) ; an exploration-labelled leaf left deep is a NON-blocking ADVISORY (`byan-lint-workflows.js --advise`), since many such leaves bear a gate/classification/exact-conversion and must stay deep — the human owns that call.
135
168
  - **Tier gate hook** (EVERY Workflow invocation, inline or scriptPath) : `tier-script-guard.js` (PreToolUse) runs the same analysis (`lib/tier-script.js`) and DENIES ONCE when exploration/`mech-` leaves have no tier, with the exact leaf list to fix. Acknowledge deliberate deep choices with the `// BYAN-TIER: reviewed` comment marker ; an identical resubmission passes (deny-once by design, no trap). Every decision lands in `_byan-output/tier-ledger.jsonl` (the measurement basis for token gains). Escape hatch : `.byan-tier/off`.
136
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]`.
137
170
  - No per-leaf effort knob exists (the API exposes only `model`), so effort-by-complexity reduces to model-by-complexity.
138
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.
139
173
  - **If no specialist matches** : halt. Ask user whether to run INT (agent recruitment) first. Do NOT fallback silently to general-purpose.
140
174
  - **Exit gate** : user validates the mapping.
141
175