create-byan-agent 2.44.0 → 2.47.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 +95 -0
- package/install/templates/.claude/CLAUDE.md +11 -0
- package/install/templates/.claude/hooks/agent-gate-check.js +53 -0
- package/install/templates/.claude/hooks/inject-voice-anchor.js +34 -1
- package/install/templates/.claude/hooks/lib/agent-gate.js +127 -0
- package/install/templates/.claude/hooks/lib/plain-language.js +155 -0
- package/install/templates/.claude/hooks/plain-language-check.js +53 -0
- package/install/templates/.claude/rules/agent-entry-gate.md +83 -0
- package/install/templates/.claude/rules/plain-language.md +73 -0
- package/install/templates/.claude/settings.json +8 -0
- package/install/templates/.claude/skills/byan-byan/SKILL.md +14 -0
- package/install/templates/.claude/workflows/intelligent-dispatch.js +68 -0
- package/install/templates/_byan/agent/byan/byan-tao.md +12 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/agent-matcher.js +167 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/codex-bridge.js +176 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/dispatch-blackboard.js +114 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/dispatch-orchestrator.js +116 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/dispatch-router.js +149 -0
- package/install/templates/_byan/mcp/byan-mcp-server/server.js +37 -0
- package/install/templates/_byan/mcp/byan-mcp-server/skill-bundles-manifest.json +1 -1
- package/install/templates/_byan/workflow/simple/byan/data/mantras.yaml +15 -4
- package/install/templates/dist/skill-bundles/byan-byan.zip +0 -0
- package/install/templates/docs/intelligent-dispatch.md +84 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,101 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [2.47.0] - 2026-07-16
|
|
13
|
+
|
|
14
|
+
### Added - Mandatory agent entry gate (match-or-create), the base of BYAN
|
|
15
|
+
|
|
16
|
+
- Makes the agent dispatch the front door of every non-conversational task: BYAN +
|
|
17
|
+
Hermes evaluate which specialist agent fits the need and PROPOSE it ; the user
|
|
18
|
+
validates (double validation IA + human) ; THEN the workflow runs. No suited
|
|
19
|
+
agent -> propose an interview to frame the need -> web research on the trade's
|
|
20
|
+
competencies + best practices -> create the tailored agent -> workflow. The
|
|
21
|
+
interview is triggered by the ABSENCE of a suited agent, not by task size (a fit
|
|
22
|
+
routes directly, no ceremony). This closes the gap where a plain task got done
|
|
23
|
+
inline without any dispatch.
|
|
24
|
+
- F1 `lib/agent-matcher.js` — pure suitability pre-filter: (task text + roster)
|
|
25
|
+
-> ranked candidates + a {fit | no-fit} verdict. Curated Hermes trigger words
|
|
26
|
+
(weighted) over a title/role text overlap ; accent-insensitive ; a no-fit is the
|
|
27
|
+
interview signal. Loader reads the roster from agent-manifest.csv (robust CSV
|
|
28
|
+
parse). It PROPOSES, it does not decide alone.
|
|
29
|
+
- F2/F3 doctrine `.claude/rules/agent-entry-gate.md` (+ pointer in CLAUDE.md + a
|
|
30
|
+
section 0 in the byan-byan skill): the rail, the two dispatch layers (agent vs
|
|
31
|
+
runtime), the proportionality (verify every task, interview only on no-fit), the
|
|
32
|
+
double validation, and the web-research step in agent creation.
|
|
33
|
+
- F4 reactive net `.claude/hooks/agent-gate-check.js` (+ pure core
|
|
34
|
+
`lib/agent-gate.js`): a Stop hook that flags a task done directly (files written)
|
|
35
|
+
with no agent proposal and outside an active FD cycle ; the next-turn voice
|
|
36
|
+
reminder surfaces it in plain French. Non-blocking (the correction lands next
|
|
37
|
+
turn) ; exits 0 in every path. Honest ceiling: no pre-display interception, so
|
|
38
|
+
the rail is doctrine + this net, not a first-turn wall.
|
|
39
|
+
- F5: tests (`test/agent-matcher.test.js` node:test, `.claude/__tests__/agent-gate.test.js`
|
|
40
|
+
jest). Full MCP suite + jest suite green ; workflow linter OK ; skill bundles
|
|
41
|
+
re-checked ; zero emoji. Shipped via install/templates (5 files added to the sync
|
|
42
|
+
manifest). Doc: `.claude/rules/agent-entry-gate.md`.
|
|
43
|
+
|
|
44
|
+
## [2.46.0] - 2026-07-15
|
|
45
|
+
|
|
46
|
+
### Added - Intelligent dispatch: Codex/Claude routing + architect-dev loop (option B)
|
|
47
|
+
|
|
48
|
+
- Route each task to the runtime that is genuinely better for it, on the right
|
|
49
|
+
model and effort for its complexity, and let an architect (Claude) and a dev
|
|
50
|
+
(Codex or Claude) exchange turn by turn until the work converges. Layered,
|
|
51
|
+
ports-and-adapters design so the durable core stays independent of the transport.
|
|
52
|
+
- F1 `lib/dispatch-router.js` — pure routing brain: {nature, complexity} ->
|
|
53
|
+
{runtime, model, effort}. Runtime table is the cross-checked result (Codex for
|
|
54
|
+
execution/shell/deploy/devops/browser ; Claude for architecture/refactor/quality/
|
|
55
|
+
planning ; unknown -> Claude). Complexity picks the Claude model tier via
|
|
56
|
+
native-tiers (haiku/sonnet/inherit) or the Codex reasoning-effort (low/med/high).
|
|
57
|
+
Two red lines enforced in code, not left to the caller: Fable is refused
|
|
58
|
+
(assertNoFable throws) and verification is forced to Claude (a runtime does not
|
|
59
|
+
grade its own work).
|
|
60
|
+
- F2 `lib/codex-bridge.js` — Codex transport behind a swappable adapter. The
|
|
61
|
+
shipped codex-exec adapter runs `codex exec` read-only for a unified diff that
|
|
62
|
+
Claude applies with git apply (Codex writes nothing -> works under a locked-down
|
|
63
|
+
sandbox e.g. Landlock). Failure is a value (ok:false + reason) so the loop falls
|
|
64
|
+
back to Claude. codex-mcp is a declared V2 slot (available:false) for the tighter
|
|
65
|
+
`codex mcp-server` coupling later, with no change to F1/F4.
|
|
66
|
+
- F3 `lib/dispatch-blackboard.js` — the turn-by-turn shared board the agents use to
|
|
67
|
+
exchange (build/render pure ; JSONL sidecar I/O isolated). The honest substitute
|
|
68
|
+
for a live peer chat: an orchestrated loop, like a chat server holding the turns.
|
|
69
|
+
- F4 `lib/dispatch-orchestrator.js` — the loop core: routes via F1, runs
|
|
70
|
+
architect<->dev through the board until convergence, executors injected (Codex vs
|
|
71
|
+
Claude) so it is fully unit-tested. `.claude/workflows/intelligent-dispatch.js` is
|
|
72
|
+
the native launch facade for one routed task (design -> implement -> verify ;
|
|
73
|
+
the verify leaf is a Claude reviewer, not the dev).
|
|
74
|
+
- Enforcement: the Codex auto-delegate config ships DISARMED (absent = off) ;
|
|
75
|
+
arming stays a per-machine opt-in (`_byan/_config/autodelegate.json`, gitignored).
|
|
76
|
+
Unit tests: `test/dispatch-router|codex-bridge|dispatch-blackboard|dispatch-orchestrator.test.js`.
|
|
77
|
+
Also corrected a pre-existing api-tools test drift (byan_api_knowledge_retrieve
|
|
78
|
+
was registered in server.js but absent from the test's tool list). Full MCP suite
|
|
79
|
+
green (833 node:test) + jest suite green. Doc: `docs/intelligent-dispatch.md`.
|
|
80
|
+
Shipped via install/templates (10 files added to the sync manifest).
|
|
81
|
+
|
|
82
|
+
## [2.45.0] - 2026-07-15
|
|
83
|
+
|
|
84
|
+
### Added - Plain-language guard for every agent (Mantra IA-26 "Parler Reel")
|
|
85
|
+
|
|
86
|
+
- New team-wide rule: agents speak clear, coherent French to the user — no gratuitous
|
|
87
|
+
English when a French word exists ("redemarrer le conteneur", not "cutoff"), no raw
|
|
88
|
+
internal jargon (leaf/tier/downgrade/gate/inline/advisory), no misapplied metaphor
|
|
89
|
+
(you do not "forge" a token). A technical term with no French equivalent (commit,
|
|
90
|
+
cache, token) is kept but explained once. Test: the reader understands with no
|
|
91
|
+
dictionary. This is the sibling of IA-23 (no emoji) and applies to ALL agents.
|
|
92
|
+
- Mechanism, in three layers, none of which re-generates an already-shown reply:
|
|
93
|
+
1. The rule reaches every agent: mantra `IA-26` (`mantras.yaml` + `mantras-sources.md`),
|
|
94
|
+
`.claude/rules/plain-language.md`, and a pointer in `CLAUDE.md`.
|
|
95
|
+
2. BYAN's own voice stays fresh: a line in the per-turn reminder
|
|
96
|
+
(`inject-voice-anchor.js`) + entries in `tao.md` Section 4 (Vocabulaire Interdit).
|
|
97
|
+
3. A forward net (non-blocking): the Stop hook `plain-language-check.js` spots the
|
|
98
|
+
known repeat-offenders in the finished reply and writes a one-turn flag under
|
|
99
|
+
`_byan-output/`; the next-turn reminder reads it, signals it in plain French, and
|
|
100
|
+
clears it. No blocking, no re-answer — the correction is carried to the next turn
|
|
101
|
+
(a blocking guard would force a costly regen and the user has already read the slip).
|
|
102
|
+
- Core logic isolated in `.claude/hooks/lib/plain-language.js` (French-aware word
|
|
103
|
+
boundary so "metier"/"chantier" stay clear of the "tier" match; code spans stripped
|
|
104
|
+
before scanning). Tests: `.claude/__tests__/plain-language.test.js`. Full suite green
|
|
105
|
+
(2629 jest). Shipped via `install/templates/`.
|
|
106
|
+
|
|
12
107
|
## [2.44.0] - 2026-07-15
|
|
13
108
|
|
|
14
109
|
### Changed - Revived the Sonnet middle tier for native-workflow model routing
|
|
@@ -15,6 +15,16 @@ Pour invoquer Hermes, tape: `@hermes` ou demande simplement "quel agent pour [ta
|
|
|
15
15
|
|
|
16
16
|
Voir @.claude/rules/hermes-dispatcher.md pour les commandes Hermes.
|
|
17
17
|
|
|
18
|
+
## Porte d'entree — dispatch d'agent obligatoire (match-or-create)
|
|
19
|
+
|
|
20
|
+
La base de BYAN : toute tache non-conversationnelle passe d'abord par le dispatch
|
|
21
|
+
d'agent. BYAN + Hermes evaluent quel agent specialiste colle au besoin et le
|
|
22
|
+
PROPOSENT ; l'utilisateur valide (double validation IA + humain), PUIS on lance le
|
|
23
|
+
workflow. Aucun agent adapte -> interview pour cadrer le besoin -> recherche web
|
|
24
|
+
(competences + bonnes pratiques du metier) -> creation de l'agent sur mesure ->
|
|
25
|
+
workflow. Le declencheur de l'interview est l'absence d'un agent adapte, pas la
|
|
26
|
+
taille de la tache. Detail + enforcement : voir @.claude/rules/agent-entry-gate.md
|
|
27
|
+
|
|
18
28
|
## Architecture BYAN
|
|
19
29
|
|
|
20
30
|
```
|
|
@@ -33,6 +43,7 @@ Voir @.claude/rules/hermes-dispatcher.md pour les commandes Hermes.
|
|
|
33
43
|
|
|
34
44
|
- Pas d'emojis dans le code, commits, ou specs techniques (Mantra IA-23)
|
|
35
45
|
- Code auto-documente, commentaires uniquement pour le POURQUOI (Mantra IA-24)
|
|
46
|
+
- Parler reel: francais clair et coherent avec l'utilisateur, zero jargon interne / anglais gratuit (Mantra IA-26, voir @.claude/rules/plain-language.md)
|
|
36
47
|
- Format commits: `type: description` (feat, fix, docs, refactor, test, chore)
|
|
37
48
|
- Simplicite d'abord - Rasoir d'Ockham (Mantra #37)
|
|
38
49
|
- Challenge Before Confirm - Valider avant d'accepter (Mantra IA-16)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Stop hook — the reactive net for BYAN's mandatory agent entry gate (F4).
|
|
5
|
+
//
|
|
6
|
+
// At end of turn it asks: was a task handled DIRECTLY (files written) without any
|
|
7
|
+
// agent proposal and outside an active FD cycle ? If so it writes a one-turn flag
|
|
8
|
+
// under _byan-output/ ; the next-turn voice reminder surfaces it in plain French.
|
|
9
|
+
// It NEVER blocks the turn (the user asked to signal, not trap) — always exits 0.
|
|
10
|
+
//
|
|
11
|
+
// The judgment lives in lib/agent-gate.js (pure) ; this shell only extracts the
|
|
12
|
+
// signals from the transcript + fd-state.
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const { extractLastAssistantText, extractRecentMessages } = require('./lib/transcript-read');
|
|
16
|
+
const gate = require('./lib/agent-gate');
|
|
17
|
+
|
|
18
|
+
// detect(payload, projectDir) — testable core. Returns the assessment and writes
|
|
19
|
+
// the flag on a slip.
|
|
20
|
+
function detect(payload, projectDir) {
|
|
21
|
+
const text = extractLastAssistantText(payload);
|
|
22
|
+
const messages = extractRecentMessages(payload, 8) || [];
|
|
23
|
+
const assessment = gate.assessTurn({
|
|
24
|
+
wroteFiles: gate.hasWriteActivity(messages),
|
|
25
|
+
proposedAgent: gate.hasProposalMarker(text),
|
|
26
|
+
fdActive: gate.fdIsActive(projectDir),
|
|
27
|
+
});
|
|
28
|
+
if (assessment.slip) gate.writeSlip(projectDir, assessment.reason);
|
|
29
|
+
return assessment;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readStdin() {
|
|
33
|
+
try { return fs.readFileSync(0, 'utf8'); } catch { return ''; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (require.main === module) {
|
|
37
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
38
|
+
let payload = {};
|
|
39
|
+
try {
|
|
40
|
+
const raw = readStdin();
|
|
41
|
+
if (raw && raw.trim()) payload = JSON.parse(raw);
|
|
42
|
+
} catch {
|
|
43
|
+
payload = {};
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
detect(payload, projectDir);
|
|
47
|
+
} catch {
|
|
48
|
+
// never block end-of-turn
|
|
49
|
+
}
|
|
50
|
+
process.stdout.write('{}');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { detect };
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
const fs = require('fs');
|
|
27
27
|
const path = require('path');
|
|
28
28
|
const { buildTaoContext, turnCounterPath } = require('./inject-tao');
|
|
29
|
+
const pl = require('./lib/plain-language');
|
|
30
|
+
const gate = require('./lib/agent-gate');
|
|
29
31
|
|
|
30
32
|
const ANCHOR = [
|
|
31
33
|
'Voix BYAN (rappel par tour ; tao complet chargé au démarrage de session) :',
|
|
@@ -33,6 +35,7 @@ const ANCHOR = [
|
|
|
33
35
|
'- Challenge avant de confirmer ; questionne les absolus (Mantra IA-16).',
|
|
34
36
|
'- Signatures : "Attends — pourquoi ?", "OK. On construit.", "Ça, c\'est du générique.".',
|
|
35
37
|
'- Zéro emoji. Orienté solution : on cherche la meilleure option, pas le mur.',
|
|
38
|
+
'- 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).',
|
|
36
39
|
].join('\n');
|
|
37
40
|
|
|
38
41
|
const DEFAULT_REFRESH_EVERY = 12;
|
|
@@ -77,6 +80,22 @@ function decideAnchor({ turn, every, fullTao }) {
|
|
|
77
80
|
return { mode: 'anchor', additionalContext: ANCHOR };
|
|
78
81
|
}
|
|
79
82
|
|
|
83
|
+
// Append a plain-language slip reminder (IA-25) to the injected context when the
|
|
84
|
+
// previous turn tripped the forward net. Pure so it is unit-testable; the fs read
|
|
85
|
+
// + clear stays in the require.main path below. A missing/empty hit list is a
|
|
86
|
+
// no-op, so this never changes the anchor on a clean turn.
|
|
87
|
+
function withSlipReminder(baseContext, hits) {
|
|
88
|
+
const reminder = pl.formatReminder(hits);
|
|
89
|
+
return reminder ? `${baseContext}\n${reminder}` : baseContext;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Append the agent-gate reminder (F4) when the previous turn did a task directly
|
|
93
|
+
// without proposing an agent. Pure; the fs read + clear stays in require.main.
|
|
94
|
+
function withGateReminder(baseContext, slip) {
|
|
95
|
+
const reminder = gate.formatReminder(slip);
|
|
96
|
+
return reminder ? `${baseContext}\n${reminder}` : baseContext;
|
|
97
|
+
}
|
|
98
|
+
|
|
80
99
|
if (require.main === module) {
|
|
81
100
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
82
101
|
const every = refreshEvery();
|
|
@@ -84,9 +103,21 @@ if (require.main === module) {
|
|
|
84
103
|
writeTurn(projectDir, turn);
|
|
85
104
|
const fullTao = every > 0 && turn % every === 0 ? buildTaoContext(projectDir) : '';
|
|
86
105
|
const { additionalContext } = decideAnchor({ turn, every, fullTao });
|
|
106
|
+
// Forward net: if the previous turn slipped into jargon, remind now and clear
|
|
107
|
+
// the flag (one-shot). Read/clear here, formatting stays pure in withSlipReminder.
|
|
108
|
+
const slipHits = pl.readSlip(projectDir);
|
|
109
|
+
if (slipHits && slipHits.length) pl.clearSlip(projectDir);
|
|
110
|
+
// Forward net #2 (F4): agent entry-gate slip. Read + clear one-shot.
|
|
111
|
+
const gateSlip = gate.readSlip(projectDir);
|
|
112
|
+
if (gateSlip) gate.clearSlip(projectDir);
|
|
113
|
+
let ctx = withSlipReminder(additionalContext, slipHits);
|
|
114
|
+
ctx = withGateReminder(ctx, gateSlip);
|
|
87
115
|
process.stdout.write(
|
|
88
116
|
JSON.stringify({
|
|
89
|
-
hookSpecificOutput: {
|
|
117
|
+
hookSpecificOutput: {
|
|
118
|
+
hookEventName: 'UserPromptSubmit',
|
|
119
|
+
additionalContext: ctx,
|
|
120
|
+
},
|
|
90
121
|
})
|
|
91
122
|
);
|
|
92
123
|
}
|
|
@@ -98,5 +129,7 @@ module.exports = {
|
|
|
98
129
|
readTurn,
|
|
99
130
|
writeTurn,
|
|
100
131
|
decideAnchor,
|
|
132
|
+
withSlipReminder,
|
|
133
|
+
withGateReminder,
|
|
101
134
|
DEFAULT_REFRESH_EVERY,
|
|
102
135
|
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// F4 core — the reactive net for the mandatory agent entry gate.
|
|
4
|
+
//
|
|
5
|
+
// BYAN's philosophy: every non-conversational task passes through Hermes+BYAN
|
|
6
|
+
// PROPOSING a suited agent (or an interview to create one), validated by the
|
|
7
|
+
// user, BEFORE any work. No pre-display hook exists, so this net is post-hoc: at
|
|
8
|
+
// end of turn it detects a task done DIRECTLY (files written) without any agent
|
|
9
|
+
// proposal and without an active FD cycle, writes a one-turn flag, and the
|
|
10
|
+
// next-turn reminder surfaces it. Non-blocking by design (the user asked to
|
|
11
|
+
// "signal", not to trap the turn) — the correction lands on the next turn.
|
|
12
|
+
//
|
|
13
|
+
// Pure: assessTurn + the marker/write detectors take plain inputs. The flag I/O
|
|
14
|
+
// is isolated. The hook shell feeds it transcript + fd-state signals.
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
// Text signals that the entry gate WAS engaged this turn (an agent was proposed,
|
|
20
|
+
// an FD phase is being narrated, a specialist was addressed, or the interview /
|
|
21
|
+
// no-fit path was surfaced). Any of these means "not a silent direct-do".
|
|
22
|
+
const PROPOSAL_MARKERS = [
|
|
23
|
+
'[fd:', '@hermes', 'agent adapte', 'aucun agent', 'je propose', 'proposition d\'agent',
|
|
24
|
+
'interview', 'byan-hermes-dispatch', 'dispatch d\'agent', 'quel agent',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
// Tool names that count as "did real work on the repo" this turn.
|
|
28
|
+
const WRITE_TOOLS = new Set(['Write', 'Edit', 'MultiEdit', 'NotebookEdit']);
|
|
29
|
+
|
|
30
|
+
function hasProposalMarker(text) {
|
|
31
|
+
const t = String(text == null ? '' : text).toLowerCase();
|
|
32
|
+
return PROPOSAL_MARKERS.some((m) => t.includes(m));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// hasWriteActivity(messages) — true if any recent assistant message carried a
|
|
36
|
+
// tool_use block that writes to the repo. `messages` is an array of
|
|
37
|
+
// { role, content } where content is a string or a block array.
|
|
38
|
+
function hasWriteActivity(messages) {
|
|
39
|
+
if (!Array.isArray(messages)) return false;
|
|
40
|
+
for (const m of messages) {
|
|
41
|
+
if (!m || m.role !== 'assistant' || !Array.isArray(m.content)) continue;
|
|
42
|
+
for (const block of m.content) {
|
|
43
|
+
if (block && block.type === 'tool_use' && WRITE_TOOLS.has(block.name)) return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// assessTurn — the decision. A slip is a task done directly (files written) with
|
|
50
|
+
// no agent proposal AND no active FD cycle. When an FD is engaged, the gate is
|
|
51
|
+
// already in play (DISPATCH is part of it), so it is never a slip.
|
|
52
|
+
function assessTurn({ wroteFiles = false, proposedAgent = false, fdActive = false } = {}) {
|
|
53
|
+
const slip = Boolean(wroteFiles && !proposedAgent && !fdActive);
|
|
54
|
+
return {
|
|
55
|
+
slip,
|
|
56
|
+
reason: slip
|
|
57
|
+
? 'files written directly without an agent proposal and no active FD cycle'
|
|
58
|
+
: 'ok',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// --- slip flag (isolated I/O, mirrors the plain-language forward net) ------
|
|
63
|
+
|
|
64
|
+
function slipPath(projectDir) {
|
|
65
|
+
return path.join(projectDir, '_byan-output', '.agent-gate-slip.json');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function writeSlip(projectDir, detail) {
|
|
69
|
+
try {
|
|
70
|
+
const p = slipPath(projectDir);
|
|
71
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
72
|
+
fs.writeFileSync(p, JSON.stringify({ detail: String(detail || '') }));
|
|
73
|
+
return true;
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function readSlip(projectDir) {
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(fs.readFileSync(slipPath(projectDir), 'utf8'));
|
|
82
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function clearSlip(projectDir) {
|
|
89
|
+
try { fs.rmSync(slipPath(projectDir), { force: true }); } catch { /* never block */ }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// formatReminder — the plain-French note injected next turn. Empty when no slip.
|
|
93
|
+
function formatReminder(slip) {
|
|
94
|
+
if (!slip) return '';
|
|
95
|
+
return [
|
|
96
|
+
'Rappel dispatch (porte d\'entree BYAN) : au dernier tour une tache a ete',
|
|
97
|
+
'traitee en direct sans proposer d\'agent adapte. La base de BYAN, c\'est de',
|
|
98
|
+
'passer par Hermes : proposer l\'agent qui colle (ou une interview pour en',
|
|
99
|
+
'creer un), l\'utilisateur valide, PUIS on lance le travail. Applique-le ce tour-ci.',
|
|
100
|
+
].join(' ');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// fdIsActive(projectDir) — reads _byan-output/fd-state.json ; active means a live
|
|
104
|
+
// phase (not COMPLETED / ABORTED / absent). Read-only, best-effort.
|
|
105
|
+
function fdIsActive(projectDir) {
|
|
106
|
+
try {
|
|
107
|
+
const state = JSON.parse(fs.readFileSync(path.join(projectDir, '_byan-output', 'fd-state.json'), 'utf8'));
|
|
108
|
+
const phase = state && state.phase;
|
|
109
|
+
return Boolean(phase) && phase !== 'COMPLETED' && phase !== 'ABORTED';
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
PROPOSAL_MARKERS,
|
|
117
|
+
WRITE_TOOLS,
|
|
118
|
+
hasProposalMarker,
|
|
119
|
+
hasWriteActivity,
|
|
120
|
+
assessTurn,
|
|
121
|
+
slipPath,
|
|
122
|
+
writeSlip,
|
|
123
|
+
readSlip,
|
|
124
|
+
clearSlip,
|
|
125
|
+
formatReminder,
|
|
126
|
+
fdIsActive,
|
|
127
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Plain-language core — the shared, testable heart of the "parler reel" guard
|
|
4
|
+
// (Mantra IA-26). Two jobs, no I/O beyond the slip flag:
|
|
5
|
+
//
|
|
6
|
+
// 1. scanText: find the KNOWN repeat-offender words in an assistant reply and
|
|
7
|
+
// return each with its plain-French replacement. This is the mechanical net
|
|
8
|
+
// for words we already know we misuse; the generative rule (the principle in
|
|
9
|
+
// .claude/rules/plain-language.md + the voice anchor) handles the long tail.
|
|
10
|
+
// 2. slip flag: a tiny file under _byan-output/ that the Stop hook writes when a
|
|
11
|
+
// reply slipped, and the next-turn voice anchor reads + clears to remind the
|
|
12
|
+
// agent. NO re-answer, NO blocking — the correction is carried FORWARD to the
|
|
13
|
+
// next turn, which is the whole point (avoid the costly regen loop).
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
// Known repeat-offenders -> plain French. Deliberately conservative: only terms
|
|
19
|
+
// with a clean everyday equivalent and a low false-positive risk once code is
|
|
20
|
+
// stripped. Common technical borrowings that have no French equivalent (commit,
|
|
21
|
+
// cache, token) are NOT here — the rule keeps them, explained once.
|
|
22
|
+
const OFFENDERS = Object.freeze([
|
|
23
|
+
{ term: 'inline', good: 'directement (je le fais moi-meme)' },
|
|
24
|
+
{ term: 'cutoff', good: "l'action reelle (ex: redemarrer, couper)" },
|
|
25
|
+
{ term: 'housekeeping', good: 'rangement / menage du code' },
|
|
26
|
+
{ term: 'downgrade', good: 'retrograder / baisser en gamme' },
|
|
27
|
+
{ term: 'advisory', good: 'signalement non bloquant' },
|
|
28
|
+
{ term: 'wrapper', good: 'enveloppe / surcouche' },
|
|
29
|
+
{ term: 'fallback', good: 'repli / solution de secours' },
|
|
30
|
+
{ term: 'throughput', good: 'debit' },
|
|
31
|
+
{ term: 'overhead', good: 'surcout' },
|
|
32
|
+
{ term: 'gate', good: 'point de controle / porte' },
|
|
33
|
+
{ term: 'leaf', good: 'etape / tache' },
|
|
34
|
+
{ term: 'tier', good: 'niveau / gamme' },
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
// A metaphor misuse, not a single word: "forger" applied to a token/jeton.
|
|
38
|
+
// Matched separately so the replacement can name the real verb.
|
|
39
|
+
const METAPHOR_OFFENDERS = Object.freeze([
|
|
40
|
+
{
|
|
41
|
+
id: 'forger-token',
|
|
42
|
+
re: /forg\w*\s+(?:un |une |des |le |les |la )?(?:token|jeton)/i,
|
|
43
|
+
label: 'forger un token',
|
|
44
|
+
good: 'generer / creer un token',
|
|
45
|
+
},
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
// A word boundary that treats accented letters as part of a word, so \btier\b
|
|
49
|
+
// style matching does NOT fire inside "metier", "chantier", "quartier"... The
|
|
50
|
+
// native \b is wrong here because JS \w excludes accented chars, splitting French
|
|
51
|
+
// words. This lookbehind/lookahead pair is the correct French-aware boundary.
|
|
52
|
+
const LEFT = '(?<![A-Za-zÀ-ÿ0-9_])';
|
|
53
|
+
const RIGHT = '(?![A-Za-zÀ-ÿ0-9_])';
|
|
54
|
+
|
|
55
|
+
function escapeRegExp(s) {
|
|
56
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function termRegExp(term) {
|
|
60
|
+
return new RegExp(LEFT + escapeRegExp(term) + RIGHT, 'i');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Remove fenced code blocks and inline `code` spans before scanning, so quoting a
|
|
64
|
+
// file, flag, or function literally named "tier" / "gate" does not trip the net.
|
|
65
|
+
// Prose is what we police; quoted code is not prose.
|
|
66
|
+
function stripCode(text) {
|
|
67
|
+
if (typeof text !== 'string' || !text) return '';
|
|
68
|
+
return text
|
|
69
|
+
.replace(/```[\s\S]*?```/g, ' ')
|
|
70
|
+
.replace(/`[^`]*`/g, ' ');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Find every distinct offender present in the prose. Returns [{ bad, good }].
|
|
74
|
+
function scanText(text) {
|
|
75
|
+
const prose = stripCode(text);
|
|
76
|
+
if (!prose) return [];
|
|
77
|
+
const hits = [];
|
|
78
|
+
const seen = new Set();
|
|
79
|
+
for (const { term, good } of OFFENDERS) {
|
|
80
|
+
if (termRegExp(term).test(prose) && !seen.has(term)) {
|
|
81
|
+
seen.add(term);
|
|
82
|
+
hits.push({ bad: term, good });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const m of METAPHOR_OFFENDERS) {
|
|
86
|
+
if (m.re.test(prose) && !seen.has(m.id)) {
|
|
87
|
+
seen.add(m.id);
|
|
88
|
+
hits.push({ bad: m.label, good: m.good });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return hits;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// A short French reminder, for injection at the NEXT turn. Bounded to a few
|
|
95
|
+
// offenders so the note stays tiny even if a reply slipped many times.
|
|
96
|
+
function formatReminder(hits) {
|
|
97
|
+
if (!Array.isArray(hits) || hits.length === 0) return '';
|
|
98
|
+
const shown = hits.slice(0, 6)
|
|
99
|
+
.map((h) => `"${h.bad}" -> ${h.good}`)
|
|
100
|
+
.join(' ; ');
|
|
101
|
+
return [
|
|
102
|
+
'Rappel langage (IA-26) : au dernier tour tu as glisse du jargon ou de',
|
|
103
|
+
`l'anglais gratuit -> ${shown}. Reformule en francais reel et coherent ce`,
|
|
104
|
+
'tour-ci, sans refaire la reponse precedente.',
|
|
105
|
+
].join(' ');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// The slip flag lives under _byan-output/ (gitignored), same family as the other
|
|
109
|
+
// hook sidecars. It is a transient one-turn signal, never source of truth.
|
|
110
|
+
function slipPath(projectDir) {
|
|
111
|
+
return path.join(projectDir, '_byan-output', '.jargon-slip.json');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function writeSlip(projectDir, hits) {
|
|
115
|
+
try {
|
|
116
|
+
const p = slipPath(projectDir);
|
|
117
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
118
|
+
fs.writeFileSync(p, JSON.stringify({ hits }));
|
|
119
|
+
return true;
|
|
120
|
+
} catch {
|
|
121
|
+
return false; // best-effort: a write failure must never block a turn
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function readSlip(projectDir) {
|
|
126
|
+
try {
|
|
127
|
+
const raw = fs.readFileSync(slipPath(projectDir), 'utf8');
|
|
128
|
+
const parsed = JSON.parse(raw);
|
|
129
|
+
return Array.isArray(parsed.hits) ? parsed.hits : [];
|
|
130
|
+
} catch {
|
|
131
|
+
return null; // no flag (or unreadable) -> nothing to remind
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function clearSlip(projectDir) {
|
|
136
|
+
try {
|
|
137
|
+
fs.rmSync(slipPath(projectDir), { force: true });
|
|
138
|
+
} catch {
|
|
139
|
+
// never block
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = {
|
|
144
|
+
OFFENDERS,
|
|
145
|
+
METAPHOR_OFFENDERS,
|
|
146
|
+
escapeRegExp,
|
|
147
|
+
termRegExp,
|
|
148
|
+
stripCode,
|
|
149
|
+
scanText,
|
|
150
|
+
formatReminder,
|
|
151
|
+
slipPath,
|
|
152
|
+
writeSlip,
|
|
153
|
+
readSlip,
|
|
154
|
+
clearSlip,
|
|
155
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Stop hook — the FORWARD net for the "parler reel" rule (Mantra IA-26).
|
|
5
|
+
//
|
|
6
|
+
// It reads the assistant reply that just finished, looks for the known
|
|
7
|
+
// jargon/anglais repeat-offenders, and — if it finds any — writes a one-turn
|
|
8
|
+
// slip flag under _byan-output/. It NEVER blocks the turn: there is no re-answer,
|
|
9
|
+
// no regen. The next-turn voice anchor (inject-voice-anchor.js) reads the flag,
|
|
10
|
+
// reminds the agent in plain French, and clears it. Carrying the correction
|
|
11
|
+
// FORWARD is deliberate: a blocking guard would force a costly regen AND the user
|
|
12
|
+
// has already read the slip anyway (no pre-display hook exists).
|
|
13
|
+
//
|
|
14
|
+
// Always exits 0. A read/scan/write failure degrades to a no-op.
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const { extractLastAssistantText } = require('./lib/transcript-read');
|
|
18
|
+
const pl = require('./lib/plain-language');
|
|
19
|
+
|
|
20
|
+
// Testable core: scan the finished reply, flag a slip if any. Returns the hits.
|
|
21
|
+
function detectAndFlag(payload, projectDir) {
|
|
22
|
+
const text = extractLastAssistantText(payload);
|
|
23
|
+
const hits = pl.scanText(text);
|
|
24
|
+
if (hits.length > 0) pl.writeSlip(projectDir, hits);
|
|
25
|
+
return hits;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function readStdin() {
|
|
29
|
+
try {
|
|
30
|
+
return fs.readFileSync(0, 'utf8');
|
|
31
|
+
} catch {
|
|
32
|
+
return '';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (require.main === module) {
|
|
37
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
38
|
+
let payload = {};
|
|
39
|
+
try {
|
|
40
|
+
const raw = readStdin();
|
|
41
|
+
if (raw && raw.trim()) payload = JSON.parse(raw);
|
|
42
|
+
} catch {
|
|
43
|
+
payload = {};
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
detectAndFlag(payload, projectDir);
|
|
47
|
+
} catch {
|
|
48
|
+
// never block end-of-turn
|
|
49
|
+
}
|
|
50
|
+
process.stdout.write('{}');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { detectAndFlag };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Porte d'entree BYAN — dispatch d'agent obligatoire (match-or-create)
|
|
2
|
+
|
|
3
|
+
> La base de BYAN : mettre la personne sur des rails pour toute tache ou projet.
|
|
4
|
+
> Avant de faire le travail, on regarde quel agent specialiste colle au besoin.
|
|
5
|
+
> Un agent adapte existe -> on le propose et on lance son workflow. Aucun ne
|
|
6
|
+
> colle -> on interviewe pour cadrer le besoin, on cherche les competences du
|
|
7
|
+
> metier, et on cree l'agent sur mesure. Le dispatch d'agent n'est pas une
|
|
8
|
+
> option : c'est l'entree de tout.
|
|
9
|
+
|
|
10
|
+
## Le principe
|
|
11
|
+
|
|
12
|
+
Il y a DEUX dispatch, ne pas les confondre :
|
|
13
|
+
- **Dispatch d'agent (Hermes)** — QUI fait le travail : quel agent BYAN, ou on en
|
|
14
|
+
cree un. C'est le rail decrit ici.
|
|
15
|
+
- **Dispatch de runtime** — une fois l'agent choisi, sur quel moteur (Codex ou
|
|
16
|
+
Claude) + modele + effort. C'est la couche du dessous (voir
|
|
17
|
+
`docs/intelligent-dispatch.md`).
|
|
18
|
+
|
|
19
|
+
Cette regle porte sur le premier : la porte d'entree.
|
|
20
|
+
|
|
21
|
+
## Le flux (sur toute tache non-conversationnelle)
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
demande utilisateur
|
|
25
|
+
-> BYAN + Hermes evaluent quel agent specialiste colle (matcher F1)
|
|
26
|
+
AGENT ADAPTE TROUVE
|
|
27
|
+
-> BYAN le PROPOSE ("je pars sur @dev, tu valides ?")
|
|
28
|
+
-> l'utilisateur valide (double validation IA + humain)
|
|
29
|
+
-> on lance le workflow de cet agent (+ dispatch runtime)
|
|
30
|
+
AUCUN AGENT ADAPTE
|
|
31
|
+
-> BYAN le dit et propose une interview
|
|
32
|
+
-> interview : cadrer precisement le besoin de l'agent
|
|
33
|
+
-> recherche web : competences + bonnes pratiques du metier vise
|
|
34
|
+
-> creation de l'agent sur mesure
|
|
35
|
+
-> on lance le workflow
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Le declencheur de l'interview, c'est **l'absence d'un agent adapte**, pas la
|
|
39
|
+
taille de la tache. "code un script" -> l'agent dev existe -> workflow direct,
|
|
40
|
+
zero ceremonie. "il me faut un agent art moderne" -> aucun agent -> interview +
|
|
41
|
+
recherche + creation.
|
|
42
|
+
|
|
43
|
+
## La proportion (pour que le rail ne devienne pas un mur)
|
|
44
|
+
|
|
45
|
+
Le rail impose de **verifier** la pertinence d'un agent a chaque tache — pas de
|
|
46
|
+
declencher une interview a chaque fois. Agent existant qui colle = on route
|
|
47
|
+
direct. Besoin flou ou aucun agent adapte = la ou l'interview + creation se
|
|
48
|
+
declenchent. La ceremonie est proportionnee au manque, pas systematique.
|
|
49
|
+
|
|
50
|
+
## La double validation
|
|
51
|
+
|
|
52
|
+
Le matcher (F1, `_byan/mcp/byan-mcp-server/lib/agent-matcher.js`) est un
|
|
53
|
+
pre-tri deterministe : il classe les candidats du roster et rend un verdict
|
|
54
|
+
{fit | no-fit}. Il PROPOSE, il ne tranche pas seul. BYAN presente la proposition
|
|
55
|
+
en clair ; l'utilisateur valide. IA propose, humain confirme.
|
|
56
|
+
|
|
57
|
+
## La creation d'agent enrichie (chemin no-fit)
|
|
58
|
+
|
|
59
|
+
Quand aucun agent ne colle, la creation passe par :
|
|
60
|
+
1. **Interview** — cadrer le besoin (ce que l'agent doit faire, ses lignes
|
|
61
|
+
rouges, son perimetre). Flux INT / agent-builder existant.
|
|
62
|
+
2. **Recherche web** — avant de generer l'agent, chercher les competences reelles
|
|
63
|
+
et les bonnes pratiques du metier vise (ex : pour un agent "art moderne",
|
|
64
|
+
courants, techniques, references). C'est ce qui evite l'agent-zombie generique.
|
|
65
|
+
3. **Generation** — generer l'agent sur mesure a partir de l'interview + la
|
|
66
|
+
recherche.
|
|
67
|
+
4. **Workflow** — lancer le travail avec le nouvel agent.
|
|
68
|
+
|
|
69
|
+
## L'enforcement (honnete sur son plafond)
|
|
70
|
+
|
|
71
|
+
Aucun controle ne s'execute AVANT que la reponse s'affiche (pas de hook de
|
|
72
|
+
pre-affichage cote Claude Code). Le rail tient donc par deux forces, pas par un
|
|
73
|
+
mur beton au premier tour :
|
|
74
|
+
1. **Doctrine** — cette regle + le skill byan-byan + `CLAUDE.md` font que BYAN
|
|
75
|
+
lance la proposition d'agent d'office.
|
|
76
|
+
2. **Filet reactif** — `.claude/hooks/agent-gate-check.js` (Stop) repere une tache
|
|
77
|
+
traitee en direct (fichiers ecrits) sans proposition d'agent et hors cycle FD,
|
|
78
|
+
pose un drapeau, et le rappel du tour suivant le signale en clair. Non
|
|
79
|
+
bloquant : la correction est portee au tour d'apres (meme methode que les
|
|
80
|
+
autres filets BYAN). Coeur teste : `.claude/hooks/lib/agent-gate.js`.
|
|
81
|
+
|
|
82
|
+
Quand un cycle FD est deja engage, la porte est deja en jeu (la phase DISPATCH en
|
|
83
|
+
fait partie) : le filet ne se declenche pas.
|