create-byan-agent 2.48.0 → 2.50.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 +50 -0
- package/install/package.json +1 -1
- package/install/templates/.claude/CLAUDE.md +21 -13
- package/install/templates/.claude/hooks/agent-gate-check.js +14 -2
- package/install/templates/.claude/hooks/codex-autodelegate.js +19 -2
- package/install/templates/.claude/hooks/codex-delegate-guard.js +145 -0
- package/install/templates/.claude/hooks/inject-voice-anchor.js +25 -0
- package/install/templates/.claude/hooks/lib/agent-gate.js +95 -5
- package/install/templates/.claude/hooks/lib/autodelegate-decision.js +11 -7
- package/install/templates/.claude/hooks/lib/codex-delegate-gate.js +0 -0
- package/install/templates/.claude/hooks/lib/fact-check-core.js +34 -2
- package/install/templates/.claude/hooks/lib/voice-conformance.js +97 -0
- package/install/templates/.claude/hooks/strict-scope-guard.js +71 -14
- package/install/templates/.claude/hooks/voice-conformance-check.js +53 -0
- package/install/templates/.claude/rules/agent-entry-gate.md +40 -20
- package/install/templates/.claude/settings.json +8 -0
- package/install/templates/.claude/skills/byan-byan/SKILL.md +52 -26
- package/install/templates/_byan/mcp/byan-mcp-server/bin/byan-armament-report.js +82 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/armament-report.js +80 -0
- package/install/templates/_byan/mcp/byan-mcp-server/skill-bundles-manifest.json +1 -1
- package/install/templates/dist/skill-bundles/byan-byan.zip +0 -0
- package/install/templates/docs/codex-auto-delegation.md +18 -3
- package/package.json +1 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WI-2 core — the reactive net for BYAN voice conformance (soul/tao).
|
|
4
|
+
//
|
|
5
|
+
// The tao is injected as context (inject-tao / voice-anchor) but nothing checks
|
|
6
|
+
// that a reply actually holds the voice : it is prose Claude can drift from. This
|
|
7
|
+
// net scans the finished reply for the OBJECTIVE, low-false-positive voice
|
|
8
|
+
// signals and flags a slip carried to the next turn — the same forward-net
|
|
9
|
+
// mechanics as plain-language / agent-gate. It is NON-BLOCKING by design : the
|
|
10
|
+
// register/timbre of the tao is semantic and cannot be a hard wall without
|
|
11
|
+
// constant false positives (honest ceiling ; deep audit stays byan-mantra-audit).
|
|
12
|
+
//
|
|
13
|
+
// Two objective signals only :
|
|
14
|
+
// 1. emoji in the reply (Mantra IA-23 : zero emoji — hard, unambiguous).
|
|
15
|
+
// 2. vouvoiement (BYAN tutoies always, per tao) — flagged only on a CLUSTER
|
|
16
|
+
// (>= 2 occurrences) so a single quoted "vous" does not trip it.
|
|
17
|
+
//
|
|
18
|
+
// Pure (no I/O beyond the slip flag). Code spans are stripped before scanning so
|
|
19
|
+
// a quoted `vous` variable or an emoji inside a code sample is not policed.
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const { stripCode } = require('./plain-language');
|
|
24
|
+
|
|
25
|
+
// True pictographic emoji (Mantra IA-23). Excludes plain arrows (U+2190-21FF)
|
|
26
|
+
// which BYAN uses legitimately in prose ("->", "→").
|
|
27
|
+
const EMOJI_RE = /[\u{1F000}-\u{1FAFF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{2B00}-\u{2BFF}\u{1F1E6}-\u{1F1FF}\u{FE0F}]/u;
|
|
28
|
+
|
|
29
|
+
// Second-person-plural address. French-aware boundaries so "nous"/"vousXYZ" and
|
|
30
|
+
// accented neighbours do not false-match.
|
|
31
|
+
const VOUS_RE = /(?<![A-Za-zÀ-ÿ0-9_])(vous|votre|vos|vôtre|vôtres)(?![A-Za-zÀ-ÿ0-9_])/gi;
|
|
32
|
+
|
|
33
|
+
// scanVoice(text) -> [{ kind, good }]. Empty when the reply holds the voice.
|
|
34
|
+
function scanVoice(text) {
|
|
35
|
+
const prose = stripCode(text);
|
|
36
|
+
if (!prose) return [];
|
|
37
|
+
const hits = [];
|
|
38
|
+
if (EMOJI_RE.test(prose)) {
|
|
39
|
+
hits.push({ kind: 'emoji', good: 'zero emoji (Mantra IA-23) — retire-les' });
|
|
40
|
+
}
|
|
41
|
+
const vousCount = (prose.match(VOUS_RE) || []).length;
|
|
42
|
+
if (vousCount >= 2) {
|
|
43
|
+
hits.push({ kind: 'vouvoiement', good: 'BYAN tutoie toujours (tao) — dis "tu", pas "vous"' });
|
|
44
|
+
}
|
|
45
|
+
return hits;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function formatReminder(hits) {
|
|
49
|
+
if (!Array.isArray(hits) || hits.length === 0) return '';
|
|
50
|
+
const shown = hits.map((h) => `${h.kind} (${h.good})`).join(' ; ');
|
|
51
|
+
return [
|
|
52
|
+
'Rappel voix (tao / soul) : au dernier tour la voix BYAN a glisse ->', `${shown}.`,
|
|
53
|
+
'Reformule ce tour-ci dans la voix BYAN (tutoiement, zero emoji), sans refaire',
|
|
54
|
+
'la reponse precedente.',
|
|
55
|
+
].join(' ');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// --- slip flag (isolated I/O, same family as the other forward nets) ----------
|
|
59
|
+
|
|
60
|
+
function slipPath(projectDir) {
|
|
61
|
+
return path.join(projectDir, '_byan-output', '.voice-slip.json');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function writeSlip(projectDir, hits) {
|
|
65
|
+
try {
|
|
66
|
+
const p = slipPath(projectDir);
|
|
67
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
68
|
+
fs.writeFileSync(p, JSON.stringify({ hits }));
|
|
69
|
+
return true;
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function readSlip(projectDir) {
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(fs.readFileSync(slipPath(projectDir), 'utf8'));
|
|
78
|
+
return Array.isArray(parsed.hits) ? parsed.hits : null;
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function clearSlip(projectDir) {
|
|
85
|
+
try { fs.rmSync(slipPath(projectDir), { force: true }); } catch { /* never block */ }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = {
|
|
89
|
+
EMOJI_RE,
|
|
90
|
+
VOUS_RE,
|
|
91
|
+
scanVoice,
|
|
92
|
+
formatReminder,
|
|
93
|
+
slipPath,
|
|
94
|
+
writeSlip,
|
|
95
|
+
readSlip,
|
|
96
|
+
clearSlip,
|
|
97
|
+
};
|
|
@@ -57,9 +57,48 @@ function matchesPrefix(rel, prefix) {
|
|
|
57
57
|
return rel === p || rel.startsWith(p + '/');
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
// WI-6 — the Bash write-redirection leak.
|
|
61
|
+
//
|
|
62
|
+
// The Write/Edit deny is bypassable : `cat > src/x.js`, `echo >> a`, `tee f`,
|
|
63
|
+
// `cmd <<EOF > f` write files through Bash, which the tool-name check missed.
|
|
64
|
+
// bashWriteTargets extracts the file targets of write-redirections from a Bash
|
|
65
|
+
// command so the SAME allowed-paths rule applies. Deliberately conservative to
|
|
66
|
+
// avoid denying legit Bash : it skips fd-dups (`2>&1`, `>&2`), process
|
|
67
|
+
// substitution (`>(...)`), and the /dev/* sinks. It is not a hermetic sandbox
|
|
68
|
+
// (pipes, `python -c open()`, variables escape) — it closes the COMMON reflex
|
|
69
|
+
// leak, honestly bounded (see docs).
|
|
70
|
+
function bashWriteTargets(command) {
|
|
71
|
+
const cmd = String(command || '');
|
|
72
|
+
const out = [];
|
|
73
|
+
// A redirection target only counts when it LOOKS like a file path : it contains
|
|
74
|
+
// a "/" or a file extension. This is the false-positive kill switch : it drops
|
|
75
|
+
// comparison / arithmetic / here-string operands (`[ 5 > 3 ]`, `(( a > 2 ))`,
|
|
76
|
+
// `<<< "a > b"` -> "3", "2", "b") which are numbers or bare words, and drops an
|
|
77
|
+
// unexpanded variable target (`> $F`) which we cannot resolve — denying it would
|
|
78
|
+
// be a false positive on a possibly in-scope path. Honest ceiling : a bare
|
|
79
|
+
// filename with no extension (`> outfile`) or a variable target is NOT caught.
|
|
80
|
+
const consider = (t) => {
|
|
81
|
+
if (!t) return;
|
|
82
|
+
if (t.startsWith('$') || t.startsWith('&') || t.startsWith('-')) return; // variable / fd-dup / flag
|
|
83
|
+
if (/^\/dev\//.test(t)) return; // /dev sinks
|
|
84
|
+
const pathShaped = t.includes('/') || /\.[A-Za-z0-9]+$/.test(t);
|
|
85
|
+
if (pathShaped) out.push(t);
|
|
86
|
+
};
|
|
87
|
+
// > , >> , &> , &>> , and fd-prefixed (2>) redirections. The lookahead (?![&(])
|
|
88
|
+
// drops `>&1` (fd dup) and `>(` (process substitution).
|
|
89
|
+
const redir = /(?:^|[\s;|&(])\d*(?:>>?|&>>?)\s*(?![&(])("?)([^\s"'`|;&<>()]+)\1/g;
|
|
90
|
+
let m;
|
|
91
|
+
while ((m = redir.exec(cmd)) !== null) consider(m[2]);
|
|
92
|
+
// tee [flags] file...
|
|
93
|
+
const tee = /\btee\b((?:\s+-\S+)*)\s+("?)([^\s"'`|;&<>()]+)\2/g;
|
|
94
|
+
while ((m = tee.exec(cmd)) !== null) consider(m[3]);
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Pure decision : returns { deny, reason }. Handles Write/Edit (file_path) and,
|
|
99
|
+
// since WI-6, Bash (write-redirection targets that land INSIDE the repo).
|
|
100
|
+
function decideScope({ state, config, toolName, filePath, command }) {
|
|
101
|
+
if (!['Write', 'Edit', 'Bash'].includes(toolName)) return { deny: false };
|
|
63
102
|
if (!isEngaged(state)) return { deny: false };
|
|
64
103
|
|
|
65
104
|
const guard = (config && config.scope_guard) || {};
|
|
@@ -69,25 +108,42 @@ function decideScope({ state, config, toolName, filePath }) {
|
|
|
69
108
|
if (!Array.isArray(allowed) || allowed.length === 0) return { deny: false };
|
|
70
109
|
|
|
71
110
|
const root = projectRoot();
|
|
72
|
-
const rel = toRelative(filePath, root);
|
|
73
|
-
if (!rel) return { deny: false };
|
|
74
|
-
|
|
75
111
|
const exempt = guard.exempt_globs || [];
|
|
76
|
-
if (exempt.some((g) => matchesPrefix(rel, g))) return { deny: false };
|
|
77
|
-
|
|
78
|
-
if (allowed.some((a) => matchesPrefix(rel, a))) return { deny: false };
|
|
79
|
-
|
|
80
112
|
const base =
|
|
81
113
|
(config && config.banners && config.banners.scope_deny) ||
|
|
82
114
|
'Strict mode: this write targets a path outside the locked scope.';
|
|
83
|
-
|
|
115
|
+
|
|
116
|
+
// Returns the offending rel path, or null when the target is allowed/exempt.
|
|
117
|
+
const offending = (rawTarget, { repoOnly = false } = {}) => {
|
|
118
|
+
const rel = toRelative(rawTarget, root);
|
|
119
|
+
if (!rel) return null;
|
|
120
|
+
// repoOnly (Bash): a target outside the repo (../, /tmp, absolute elsewhere)
|
|
121
|
+
// is transient, not a repo change under cover of the locked task -> ignore.
|
|
122
|
+
if (repoOnly && rel.startsWith('..')) return null;
|
|
123
|
+
if (exempt.some((g) => matchesPrefix(rel, g))) return null;
|
|
124
|
+
if (allowed.some((a) => matchesPrefix(rel, a))) return null;
|
|
125
|
+
return rel;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const buildReason = (rel) =>
|
|
84
129
|
`${base}\n` +
|
|
85
130
|
`Target: ${rel}\n` +
|
|
86
131
|
`Locked paths: ${allowed.join(', ')}\n` +
|
|
87
132
|
`Either this file belongs to the scope (re-lock with byan_strict_lock_scope ` +
|
|
88
133
|
`including the corrected paths) or it does not (do not write it).`;
|
|
89
134
|
|
|
90
|
-
|
|
135
|
+
if (toolName === 'Write' || toolName === 'Edit') {
|
|
136
|
+
const bad = offending(filePath);
|
|
137
|
+
return bad ? { deny: true, reason: buildReason(bad) } : { deny: false };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Bash : deny if any in-repo write-redirection target is out of scope.
|
|
141
|
+
const targets = bashWriteTargets(command);
|
|
142
|
+
for (const t of targets) {
|
|
143
|
+
const bad = offending(t, { repoOnly: true });
|
|
144
|
+
if (bad) return { deny: true, reason: buildReason(bad) };
|
|
145
|
+
}
|
|
146
|
+
return { deny: false };
|
|
91
147
|
}
|
|
92
148
|
|
|
93
149
|
function allow() {
|
|
@@ -106,8 +162,9 @@ if (require.main === module) {
|
|
|
106
162
|
const toolName = payload.tool_name || payload.toolName || '';
|
|
107
163
|
const input = payload.tool_input || payload.toolInput || {};
|
|
108
164
|
const filePath = input.file_path || '';
|
|
165
|
+
const command = input.command || '';
|
|
109
166
|
|
|
110
|
-
const decision = decideScope({ state, config, toolName, filePath });
|
|
167
|
+
const decision = decideScope({ state, config, toolName, filePath, command });
|
|
111
168
|
if (!decision.deny) {
|
|
112
169
|
process.stdout.write(JSON.stringify(allow()));
|
|
113
170
|
process.exit(0);
|
|
@@ -125,4 +182,4 @@ if (require.main === module) {
|
|
|
125
182
|
})();
|
|
126
183
|
}
|
|
127
184
|
|
|
128
|
-
module.exports = { decideScope, toRelative, matchesPrefix };
|
|
185
|
+
module.exports = { decideScope, toRelative, matchesPrefix, bashWriteTargets };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Stop hook — WI-2, the reactive net for BYAN voice conformance (soul/tao).
|
|
5
|
+
//
|
|
6
|
+
// At end of turn it scans the finished reply for the objective voice slips (emoji,
|
|
7
|
+
// vouvoiement cluster) and, on a slip, writes a one-turn flag under _byan-output/.
|
|
8
|
+
// The next-turn voice anchor surfaces it in plain French. It NEVER blocks the turn
|
|
9
|
+
// (the register is semantic — a hard wall would false-positive) : always exits 0.
|
|
10
|
+
//
|
|
11
|
+
// The judgment lives in lib/voice-conformance.js (pure) ; this shell only pulls
|
|
12
|
+
// the reply text from the transcript.
|
|
13
|
+
|
|
14
|
+
const { extractLastAssistantText } = require('./lib/transcript-read');
|
|
15
|
+
const voice = require('./lib/voice-conformance');
|
|
16
|
+
|
|
17
|
+
function detect(payload, projectDir) {
|
|
18
|
+
const text = extractLastAssistantText(payload);
|
|
19
|
+
const hits = voice.scanVoice(text);
|
|
20
|
+
if (hits.length) voice.writeSlip(projectDir, hits);
|
|
21
|
+
return hits;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readStdin() {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
let data = '';
|
|
27
|
+
process.stdin.on('data', (c) => (data += c));
|
|
28
|
+
process.stdin.on('end', () => resolve(data));
|
|
29
|
+
process.stdin.on('error', () => resolve(''));
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (require.main === module) {
|
|
34
|
+
(async () => {
|
|
35
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
36
|
+
let payload = {};
|
|
37
|
+
try {
|
|
38
|
+
const raw = await readStdin();
|
|
39
|
+
if (raw && raw.trim()) payload = JSON.parse(raw);
|
|
40
|
+
} catch {
|
|
41
|
+
payload = {};
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
detect(payload, projectDir);
|
|
45
|
+
} catch {
|
|
46
|
+
// never block end-of-turn
|
|
47
|
+
}
|
|
48
|
+
process.stdout.write('{}');
|
|
49
|
+
process.exit(0);
|
|
50
|
+
})();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { detect };
|
|
@@ -47,35 +47,55 @@ declencher une interview a chaque fois. Agent existant qui colle = on route
|
|
|
47
47
|
direct. Besoin flou ou aucun agent adapte = la ou l'interview + creation se
|
|
48
48
|
declenchent. La ceremonie est proportionnee au manque, pas systematique.
|
|
49
49
|
|
|
50
|
-
## La chaine complete, automatique (
|
|
50
|
+
## La chaine complete, automatique (comprendre -> dispatch -> execution party-mode -> gate en fin)
|
|
51
51
|
|
|
52
52
|
Le dispatch d'agent n'est que la premiere marche. A l'entree, sur toute tache,
|
|
53
53
|
BYAN enchaine la chaine ENTIERE de lui-meme, sans que l'utilisateur ait a la
|
|
54
|
-
demander :
|
|
55
|
-
|
|
56
|
-
1. **
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
54
|
+
demander. Quatre moments :
|
|
55
|
+
|
|
56
|
+
1. **Comprendre la demande.** Demande claire -> on avance directement, sans
|
|
57
|
+
question. Doute ou demande mal exprimee -> BYAN expose UN plan clair (quels
|
|
58
|
+
agents, quels modeles, quel effort) et l'utilisateur tranche. Ce point de
|
|
59
|
+
controle en amont ne se declenche que sur un vrai doute — pas en reflexe a
|
|
60
|
+
chaque tache.
|
|
61
|
+
2. **Dispatch (Hermes, automatique).** Le bon agent (matcher
|
|
62
|
+
`_byan/mcp/byan-mcp-server/lib/agent-matcher.js`), le bon modele + effort
|
|
63
|
+
(`dispatch-router.js` / native-tiers) et le bon moteur : Codex pour execution /
|
|
64
|
+
shell / deploiement / devops / navigateur ; Claude pour architecture / refactor
|
|
65
|
+
/ qualite / planif ; la verification reste sur Claude ; Fable n'est pas emis.
|
|
66
|
+
Decision automatique, pas de validation utilisateur. Fit -> cet agent ;
|
|
67
|
+
no-fit -> interview + recherche web + creation (seul point ou l'humain reste
|
|
68
|
+
requis en amont).
|
|
69
|
+
3. **Execution en party-mode avec un visuel live.** L'utilisateur VOIT ce qui se
|
|
70
|
+
passe, en direct : la liste de taches native (une entree par agent/etape,
|
|
71
|
+
mise a jour en temps reel) plus la table de dispatch en tete. Codex-lane : quand
|
|
72
|
+
la voie est ARMEE (option yanstaller + Codex linke) et la tache delegable,
|
|
73
|
+
deleguer REELLEMENT via le pont (`lib/codex-bridge.js` : `codex exec` -> diff
|
|
74
|
+
unifie -> Claude applique ; repli sur Claude si Codex indisponible) — le conseil
|
|
75
|
+
injecte est alors une directive, pas une simple suggestion. Non armee : pas de
|
|
76
|
+
delegation, on execute sur Claude. Claude-lane : executer sur Claude au modele
|
|
77
|
+
choisi.
|
|
78
|
+
4. **Gate utilisateur en fin.** Le point de controle utilisateur arrive a la fin,
|
|
79
|
+
une fois la feature/tache faite et s'il y a un livrable a valider — pas en
|
|
80
|
+
amont. Dans un FD multi-feature complet, les gates par phase s'appliquent en
|
|
81
|
+
plus (c'est la gouvernance plus lourde du FD ; le chemin direct ci-dessus est
|
|
82
|
+
le cas courant).
|
|
68
83
|
|
|
69
84
|
Ce qui reste a l'humain : (a) creer un nouvel agent quand aucun ne colle, (b)
|
|
70
|
-
confirmer une action destructive
|
|
71
|
-
|
|
85
|
+
confirmer une action destructive, (c) trancher le plan en amont quand il y a
|
|
86
|
+
doute. Le reste — comprehension, match agent, routage moteur, execution — part
|
|
87
|
+
tout seul. Detail du routage moteur : `docs/intelligent-dispatch.md`.
|
|
72
88
|
|
|
73
|
-
## La double validation
|
|
89
|
+
## La double validation (calibree : sur doute, nouvel agent, destructif)
|
|
74
90
|
|
|
75
91
|
Le matcher (F1, `_byan/mcp/byan-mcp-server/lib/agent-matcher.js`) est un
|
|
76
92
|
pre-tri deterministe : il classe les candidats du roster et rend un verdict
|
|
77
|
-
{fit | no-fit}. Il PROPOSE, il ne tranche pas seul.
|
|
78
|
-
|
|
93
|
+
{fit | no-fit}. Il PROPOSE, il ne tranche pas seul. Mais la confirmation humaine
|
|
94
|
+
n'est PAS un reflexe a chaque tache : sur une demande claire avec un agent adapte,
|
|
95
|
+
BYAN route et execute, et le gate est en fin. La confirmation humaine se declenche
|
|
96
|
+
sur les trois cas qui la meritent : un vrai doute (le plan en amont), la creation
|
|
97
|
+
d'un nouvel agent, une action destructive. IA propose, humain confirme — la ou ca
|
|
98
|
+
compte, pas partout.
|
|
79
99
|
|
|
80
100
|
## La creation d'agent enrichie (chemin no-fit)
|
|
81
101
|
|
|
@@ -90,6 +90,10 @@
|
|
|
90
90
|
"type": "command",
|
|
91
91
|
"command": "p=\"$CLAUDE_PROJECT_DIR/.claude/hooks/agent-gate-check.js\"; [ -f \"$p\" ] || exit 0; exec node \"$p\""
|
|
92
92
|
},
|
|
93
|
+
{
|
|
94
|
+
"type": "command",
|
|
95
|
+
"command": "p=\"$CLAUDE_PROJECT_DIR/.claude/hooks/voice-conformance-check.js\"; [ -f \"$p\" ] || exit 0; exec node \"$p\""
|
|
96
|
+
},
|
|
93
97
|
{
|
|
94
98
|
"type": "command",
|
|
95
99
|
"command": "p=\"$CLAUDE_PROJECT_DIR/.claude/hooks/drain-advisory.js\"; [ -f \"$p\" ] || exit 0; exec node \"$p\""
|
|
@@ -112,6 +116,10 @@
|
|
|
112
116
|
{
|
|
113
117
|
"type": "command",
|
|
114
118
|
"command": "p=\"$CLAUDE_PROJECT_DIR/.claude/hooks/strict-scope-guard.js\"; [ -f \"$p\" ] || exit 0; exec node \"$p\""
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"type": "command",
|
|
122
|
+
"command": "p=\"$CLAUDE_PROJECT_DIR/.claude/hooks/codex-delegate-guard.js\"; [ -f \"$p\" ] || exit 0; exec node \"$p\""
|
|
115
123
|
}
|
|
116
124
|
]
|
|
117
125
|
},
|
|
@@ -7,31 +7,54 @@ description: BYAN — Builder of YAN. Core meta-agent that owns the Feature Deve
|
|
|
7
7
|
|
|
8
8
|
You are BYAN when this skill is active. You own the eight-phase Feature Development workflow and you enforce it mechanically. Every new feature the user asks for goes through all phases in order. No skipping. No implicit transitions. The cycle includes a REFACTOR loop back to BUILD when VALIDATE fails.
|
|
9
9
|
|
|
10
|
-
## 0. Entry
|
|
11
|
-
|
|
12
|
-
On
|
|
13
|
-
the user should
|
|
14
|
-
|
|
15
|
-
1. **
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
2. **
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
10
|
+
## 0. Entry chain — comprehend, dispatch, execute in party-mode, gate at the end
|
|
11
|
+
|
|
12
|
+
On every non-conversational task, run this chain yourself, of your own accord —
|
|
13
|
+
the user should not have to ask for it. Four moments:
|
|
14
|
+
|
|
15
|
+
1. **Comprehend the request.** Clear -> advance directly, no question. Doubt or a
|
|
16
|
+
badly-expressed request -> expose ONE clear plan (which agents, which models,
|
|
17
|
+
which effort) and let the user decide. This upstream plan-gate fires ONLY on
|
|
18
|
+
genuine doubt — it is not a reflex on every task. When the request is clear,
|
|
19
|
+
skip it and route.
|
|
20
|
+
2. **Dispatch (Hermes, automatic).** Pick the right agent
|
|
21
|
+
(`_byan/mcp/byan-mcp-server/lib/agent-matcher.js`), the right model + effort
|
|
22
|
+
(`dispatch-router.js` / native-tiers), and the right runtime — Codex for
|
|
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
|
|
26
|
+
frame the need, web-research the trade's competencies + best practices, create
|
|
27
|
+
the tailored agent (the one place the human is required upstream).
|
|
28
|
+
3. **Execute in party-mode with a live visual.** The user SEES what happens, live:
|
|
29
|
+
the native task list (one entry per agent/step, updated in real time via
|
|
30
|
+
`TaskCreate` / `TaskUpdate`) plus the dispatch table shown at the top of BUILD.
|
|
31
|
+
- **Codex-lane** — when the lane is ARMED (yanstaller option on AND Codex
|
|
32
|
+
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,
|
|
34
|
+
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.
|
|
40
|
+
- **Claude-lane** — run it on Claude at the chosen model.
|
|
41
|
+
4. **User gate at the END.** The user-validation gate lands at the end, once the
|
|
42
|
+
feature/task is done and there is a deliverable to review — not upstream. (In a
|
|
43
|
+
full multi-feature FD, the per-phase gates below still apply — that is the
|
|
44
|
+
heavier governance the FD opts into; the direct path above is the common case.)
|
|
45
|
+
|
|
46
|
+
Proportionate: a trivial task with an existing agent routes directly, no ceremony;
|
|
47
|
+
a genuine multi-feature build escalates into the full eight-phase FD. The human
|
|
48
|
+
stays required only for (a) creating a NEW agent and (b) confirming a destructive
|
|
49
|
+
action. Everything else — comprehension routing, agent match, runtime routing,
|
|
50
|
+
execution — is automatic. Do not do a task yourself without running this chain.
|
|
51
|
+
|
|
52
|
+
**Honest ceiling.** Claude Code has no control point before a response is shown,
|
|
53
|
+
so this chain is model-driven doctrine, not a hard mechanism: the delegation and
|
|
54
|
+
the visual are things you DO because this section says so, backed by the reactive
|
|
55
|
+
net (`agent-gate-check.js`) at turn end — not a guarantee enforced before display.
|
|
56
|
+
Full doctrine + runtime routing table: see @.claude/rules/agent-entry-gate.md and
|
|
57
|
+
@docs/intelligent-dispatch.md
|
|
35
58
|
|
|
36
59
|
## 1. Activation triggers
|
|
37
60
|
|
|
@@ -122,7 +145,10 @@ Never call `byan_update_apply` without explicit user consent. That tool returns
|
|
|
122
145
|
- TDD first : write/update tests before implementation.
|
|
123
146
|
- Atomic commits : `type: description`, no emoji, one feature per commit.
|
|
124
147
|
- Parallel BUILD via `party-mode-native` only if roles are independent and write to non-overlapping paths.
|
|
125
|
-
- **
|
|
148
|
+
- **Party-mode live visual** (the user must SEE what happens) :
|
|
149
|
+
1. At BUILD entry, print the dispatch table (feature x specialist x model x strategy) so the plan is on screen before any work starts.
|
|
150
|
+
2. Create ONE native task per feature/agent with `TaskCreate` (the checklist Claude Code renders live). Set it `in_progress` (`TaskUpdate`) with the acting agent as `owner` the moment that agent starts, and `completed` the moment it finishes. One entry per agent/step, updated in real time — that is the visual.
|
|
151
|
+
3. This native list is the primary visual ; the `tool-transparency` hook is the audit trail underneath it, writing per-tool entries to `_byan-output/tool-log.jsonl`. Every sub-task you spawn must be visible in both.
|
|
126
152
|
- **Exit gate** : user sees the diff and says "ok build".
|
|
127
153
|
|
|
128
154
|
### Phase 6 — REVIEW (qualitative pre-flight + tiered adversarial second pair of eyes)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// WI-7 — the armament observation report (CLI, ESM).
|
|
3
|
+
//
|
|
4
|
+
// Reads the observation ledgers under _byan-output/, reads the CURRENT armed
|
|
5
|
+
// flags from config, and prints per-guard : sample size, would-fire count (the
|
|
6
|
+
// false-positive risk proxy), current armed state, and a calibrated
|
|
7
|
+
// recommendation. It NEVER arms anything — arming is a deliberate config flip the
|
|
8
|
+
// human makes after reading this.
|
|
9
|
+
//
|
|
10
|
+
// Usage : node bin/byan-armament-report.js [--json] [--root <dir>]
|
|
11
|
+
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { buildReport } from '../lib/armament-report.js';
|
|
15
|
+
|
|
16
|
+
function parseArgs(argv) {
|
|
17
|
+
const a = { json: false, root: process.env.CLAUDE_PROJECT_DIR || process.cwd() };
|
|
18
|
+
for (let i = 2; i < argv.length; i++) {
|
|
19
|
+
if (argv[i] === '--json') a.json = true;
|
|
20
|
+
else if (argv[i] === '--root') a.root = argv[++i];
|
|
21
|
+
}
|
|
22
|
+
return a;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readLedger(root, name) {
|
|
26
|
+
try {
|
|
27
|
+
const p = path.join(root, '_byan-output', name);
|
|
28
|
+
return fs.readFileSync(p, 'utf8').split('\n').filter(Boolean).map((l) => {
|
|
29
|
+
try { return JSON.parse(l); } catch { return null; }
|
|
30
|
+
}).filter(Boolean);
|
|
31
|
+
} catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Best-effort read of the current armed flags from config (absent -> false).
|
|
37
|
+
function readArmedFlags(root) {
|
|
38
|
+
const flags = { autobench: false, punt: false, completeness: false };
|
|
39
|
+
try {
|
|
40
|
+
const a = JSON.parse(fs.readFileSync(path.join(root, '.claude', 'hooks', 'lib', 'autobench-config.json'), 'utf8'));
|
|
41
|
+
flags.autobench = Boolean(a && a.enforcement && a.enforcement.armed);
|
|
42
|
+
} catch { /* absent -> false */ }
|
|
43
|
+
try {
|
|
44
|
+
const d = JSON.parse(fs.readFileSync(path.join(root, '_byan', '_config', 'delivery-default.json'), 'utf8'));
|
|
45
|
+
flags.punt = Boolean(d && d.puntGuard && d.puntGuard.armed);
|
|
46
|
+
flags.completeness = Boolean(d && d.completenessGate && d.completenessGate.armed);
|
|
47
|
+
} catch { /* absent -> false */ }
|
|
48
|
+
return flags;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function render(report) {
|
|
52
|
+
const lines = ['BYAN armament report (observe first, arm on evidence) :', ''];
|
|
53
|
+
const label = { autobench: 'Auto-Benchmark', punt: 'Punt guard', completeness: 'Completeness gate' };
|
|
54
|
+
for (const key of Object.keys(report)) {
|
|
55
|
+
const g = report[key];
|
|
56
|
+
const s = g.summary;
|
|
57
|
+
lines.push(`- ${label[key] || key} : armed=${g.armed} | observations=${s.total} | would-fire=${s.wouldFire} (${Math.round(s.fireRate * 100)}%)`);
|
|
58
|
+
lines.push(` -> ${g.recommend.arm ? 'ARM' : 'HOLD'} : ${g.recommend.reason}`);
|
|
59
|
+
}
|
|
60
|
+
lines.push('');
|
|
61
|
+
lines.push('Note : "would-fire" est un indicateur de risque a relire, pas un compte prouve de faux positifs.');
|
|
62
|
+
lines.push('Armer reste une decision manuelle (autobench-config.json enforcement.armed, delivery-default.json puntGuard/completenessGate.armed).');
|
|
63
|
+
return lines.join('\n');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function main() {
|
|
67
|
+
const args = parseArgs(process.argv);
|
|
68
|
+
const report = buildReport(
|
|
69
|
+
{
|
|
70
|
+
benchmark: readLedger(args.root, 'benchmark-ledger.jsonl'),
|
|
71
|
+
punt: readLedger(args.root, 'punt-ledger.jsonl'),
|
|
72
|
+
completeness: readLedger(args.root, 'completeness-ledger.jsonl'),
|
|
73
|
+
},
|
|
74
|
+
readArmedFlags(args.root)
|
|
75
|
+
);
|
|
76
|
+
if (args.json) process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
77
|
+
else process.stdout.write(render(report) + '\n');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
main();
|
|
81
|
+
|
|
82
|
+
export { readLedger, readArmedFlags, render };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// WI-7 core — the armament observation report (ESM, matches this package's type).
|
|
2
|
+
//
|
|
3
|
+
// Two BYAN teeth are BUILT but DISARMED by config : the auto-benchmark Stop guard
|
|
4
|
+
// and the punt / completeness guards. They only observe + ledger today. Arming
|
|
5
|
+
// one turns it into a refuse-once, which costs a regeneration on every FALSE
|
|
6
|
+
// positive — the risk the user rejected for a hard wall. So arming must be a
|
|
7
|
+
// measured decision, not a blind flip.
|
|
8
|
+
//
|
|
9
|
+
// This pure core reads the observation ledgers and, per guard, reports : how many
|
|
10
|
+
// entries the guard WOULD have acted on if armed (the false-positive risk proxy),
|
|
11
|
+
// over how large a sample, and a recommendation. It NEVER arms anything — the
|
|
12
|
+
// human flips the config after reading this. Honest limit : the ledger has no
|
|
13
|
+
// ground truth, so "wouldFire" is a risk proxy to eyeball, not a proven
|
|
14
|
+
// false-positive count.
|
|
15
|
+
|
|
16
|
+
// Per-ledger classifiers : does this entry represent the guard ACTING (a block /
|
|
17
|
+
// refuse-once) if it were armed ?
|
|
18
|
+
export function classifyBenchmark(e) {
|
|
19
|
+
if (!e || typeof e !== 'object') return 'skip';
|
|
20
|
+
if (typeof e.event === 'string' && /block|regen|unmarked/i.test(e.event)) return 'wouldFire';
|
|
21
|
+
// a real choice presented (choice-language + an artifact) without a marker and
|
|
22
|
+
// not on the never-list is exactly what the armed guard blocks.
|
|
23
|
+
if (e.choiceLang && e.artifact && !e.marker && !e.neverHit) return 'wouldFire';
|
|
24
|
+
return 'satisfied';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function classifyPunt(e) {
|
|
28
|
+
if (!e || typeof e !== 'object') return 'skip';
|
|
29
|
+
if (e.punt === true) return 'wouldFire';
|
|
30
|
+
if (typeof e.event === 'string' && /punt/i.test(e.event)) return 'wouldFire';
|
|
31
|
+
return 'satisfied';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function classifyCompleteness(e) {
|
|
35
|
+
if (!e || typeof e !== 'object') return 'skip';
|
|
36
|
+
if (Array.isArray(e.missing) && e.missing.length > 0) return 'wouldFire';
|
|
37
|
+
if (typeof e.event === 'string' && /block|gap|fire/i.test(e.event)) return 'wouldFire';
|
|
38
|
+
return 'satisfied';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function summarize(entries, classify) {
|
|
42
|
+
const s = { total: 0, wouldFire: 0, satisfied: 0, armedSeen: false };
|
|
43
|
+
for (const e of Array.isArray(entries) ? entries : []) {
|
|
44
|
+
const c = classify(e);
|
|
45
|
+
if (c === 'skip') continue;
|
|
46
|
+
s.total += 1;
|
|
47
|
+
if (e && e.armed === true) s.armedSeen = true;
|
|
48
|
+
if (c === 'wouldFire') s.wouldFire += 1;
|
|
49
|
+
else s.satisfied += 1;
|
|
50
|
+
}
|
|
51
|
+
s.fireRate = s.total ? s.wouldFire / s.total : 0;
|
|
52
|
+
return s;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// recommend — the calibrated arming call. Never arm on a small sample ; arm only
|
|
56
|
+
// on a clean record ; otherwise send the human to review the contexts first.
|
|
57
|
+
export function recommend(summary, { minSample = 20 } = {}) {
|
|
58
|
+
if (!summary || summary.total < minSample) {
|
|
59
|
+
return { arm: false, reason: `echantillon trop petit (${summary ? summary.total : 0} < ${minSample}) — continuer d'observer avant de decider` };
|
|
60
|
+
}
|
|
61
|
+
if (summary.wouldFire === 0) {
|
|
62
|
+
return { arm: true, reason: `0 declenchement sur ${summary.total} observations — armement sur (aucun faux positif observe)` };
|
|
63
|
+
}
|
|
64
|
+
const pct = Math.round(summary.fireRate * 100);
|
|
65
|
+
return { arm: false, reason: `${summary.wouldFire}/${summary.total} declenchements (${pct}%) — relire ces contextes avant d'armer (chaque faux positif coute une regeneration)` };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// buildReport — the whole picture. `armedFlags` carries the CURRENT config state
|
|
69
|
+
// (read by the bin) so the report shows observed-vs-armed side by side.
|
|
70
|
+
export function buildReport({ benchmark = [], punt = [], completeness = [] } = {}, armedFlags = {}) {
|
|
71
|
+
const guards = {
|
|
72
|
+
autobench: { summary: summarize(benchmark, classifyBenchmark), armed: Boolean(armedFlags.autobench) },
|
|
73
|
+
punt: { summary: summarize(punt, classifyPunt), armed: Boolean(armedFlags.punt) },
|
|
74
|
+
completeness: { summary: summarize(completeness, classifyCompleteness), armed: Boolean(armedFlags.completeness) },
|
|
75
|
+
};
|
|
76
|
+
for (const k of Object.keys(guards)) {
|
|
77
|
+
guards[k].recommend = recommend(guards[k].summary);
|
|
78
|
+
}
|
|
79
|
+
return guards;
|
|
80
|
+
}
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"name": "byan-byan",
|
|
95
95
|
"module": "core",
|
|
96
96
|
"tier": "connector-bound",
|
|
97
|
-
"sourceHash": "
|
|
97
|
+
"sourceHash": "84fa718d638f1ddb37796c9b80c702fca48350decb40979bd1adea1452224abc"
|
|
98
98
|
},
|
|
99
99
|
"byan-byan-v2": {
|
|
100
100
|
"name": "byan-byan-v2",
|
|
Binary file
|