create-byan-agent 2.9.4 → 2.9.6
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/install/bin/byan-cleanup.js +156 -0
- package/install/bin/byan-kanban.js +159 -0
- package/install/bin/byan-ledger.js +45 -0
- package/install/bin/create-byan-agent-v2.js +15 -1
- package/install/lib/cleanup/detector.js +154 -0
- package/install/lib/cleanup/executor.js +72 -0
- package/install/lib/staging-consent.js +149 -0
- package/install/lib/subagent-generator.js +208 -0
- package/install/lib/token-ledger.js +131 -0
- package/install/templates/.claude/agents/bmad-bmad-master.md +14 -0
- package/install/templates/.claude/agents/bmad-bmb-agent-builder.md +14 -0
- package/install/templates/.claude/agents/bmad-bmb-module-builder.md +14 -0
- package/install/templates/.claude/agents/bmad-bmb-workflow-builder.md +14 -0
- package/install/templates/.claude/agents/bmad-bmm-analyst.md +14 -0
- package/install/templates/.claude/agents/bmad-bmm-architect.md +14 -0
- package/install/templates/.claude/agents/bmad-bmm-dev.md +14 -0
- package/install/templates/.claude/agents/bmad-bmm-pm.md +14 -0
- package/install/templates/.claude/agents/bmad-bmm-quick-flow-solo-dev.md +14 -0
- package/install/templates/.claude/agents/bmad-bmm-quinn.md +14 -0
- package/install/templates/.claude/agents/bmad-bmm-sm.md +14 -0
- package/install/templates/.claude/agents/bmad-bmm-tech-writer.md +14 -0
- package/install/templates/.claude/agents/bmad-bmm-ux-designer.md +14 -0
- package/install/templates/.claude/agents/bmad-byan-v2.md +14 -0
- package/install/templates/.claude/agents/bmad-byan.md +152 -0
- package/install/templates/.claude/agents/bmad-carmack.md +14 -0
- package/install/templates/.claude/agents/bmad-cis-brainstorming-coach.md +14 -0
- package/install/templates/.claude/agents/bmad-cis-creative-problem-solver.md +14 -0
- package/install/templates/.claude/agents/bmad-cis-design-thinking-coach.md +14 -0
- package/install/templates/.claude/agents/bmad-cis-innovation-strategist.md +14 -0
- package/install/templates/.claude/agents/bmad-cis-presentation-master.md +14 -0
- package/install/templates/.claude/agents/bmad-cis-storyteller.md +14 -0
- package/install/templates/.claude/agents/bmad-claude.md +26 -0
- package/install/templates/.claude/agents/bmad-codex.md +26 -0
- package/install/templates/.claude/agents/bmad-compliance.md +68 -0
- package/install/templates/.claude/agents/bmad-drawio.md +25 -0
- package/install/templates/.claude/agents/bmad-expert-merise-agile.md +54 -0
- package/install/templates/.claude/agents/bmad-fact-checker.md +14 -0
- package/install/templates/.claude/agents/bmad-forgeron.md +14 -0
- package/install/templates/.claude/agents/bmad-hermes.md +59 -0
- package/install/templates/.claude/agents/bmad-marc.md +25 -0
- package/install/templates/.claude/agents/bmad-patnote.md +26 -0
- package/install/templates/.claude/agents/bmad-rachid.md +25 -0
- package/install/templates/.claude/agents/bmad-tao.md +14 -0
- package/install/templates/.claude/agents/bmad-tea-tea.md +14 -0
- package/install/templates/.claude/agents/bmad-yanstaller.md +47 -0
- package/install/templates/.claude/hooks/fact-check-absolutes.js +185 -0
- package/install/templates/.claude/hooks/fd-phase-guard.js +87 -0
- package/install/templates/.claude/hooks/fd-response-check.js +92 -0
- package/install/templates/.claude/hooks/lib/failure-detector.js +14 -0
- package/install/templates/.claude/hooks/pre-compact-save.js +148 -0
- package/install/templates/.claude/hooks/stage-to-byan.js +119 -0
- package/install/templates/.claude/hooks/tool-failure-guard.js +6 -0
- package/install/templates/.claude/hooks/tool-transparency.js +4 -0
- package/install/templates/.claude/settings.json +27 -0
- package/install/templates/.claude/skills/byan-byan/SKILL.md +115 -163
- package/install/templates/.claude/skills/byan-orchestrate/SKILL.md +100 -0
- package/install/templates/.githooks/pre-commit +75 -0
- package/install/templates/.github/extensions/byan-staging/extension.mjs +169 -0
- package/install/templates/.github/extensions/byan-staging/package.json +8 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/copilot.js +148 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/fd-state.js +163 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/kanban.js +226 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/peer-review.js +187 -0
- package/install/templates/_byan/mcp/byan-mcp-server/server.js +463 -0
- package/install/templates/detector.js +154 -0
- package/package.json +6 -7
- package/src/loadbalancer/capability-matrix.js +157 -0
- package/src/loadbalancer/config.js +141 -0
- package/src/loadbalancer/graceful-degradation.js +212 -0
- package/src/loadbalancer/health-probe.js +151 -0
- package/src/loadbalancer/hooks/claude-hooks.js +53 -0
- package/src/loadbalancer/hooks/copilot-hooks.js +74 -0
- package/src/loadbalancer/index.js +81 -0
- package/src/loadbalancer/loadbalancer.default.yaml +65 -0
- package/src/loadbalancer/loadbalancer.js +324 -0
- package/src/loadbalancer/mcp-server.js +304 -0
- package/src/loadbalancer/metrics.js +146 -0
- package/src/loadbalancer/native/claude-integration.js +64 -0
- package/src/loadbalancer/native/copilot-integration.js +59 -0
- package/src/loadbalancer/pressure-score.js +102 -0
- package/src/loadbalancer/providers/base-provider.js +80 -0
- package/src/loadbalancer/providers/byan-api-provider.js +132 -0
- package/src/loadbalancer/providers/claude-provider.js +113 -0
- package/src/loadbalancer/providers/copilot-provider.js +104 -0
- package/src/loadbalancer/rate-limit-tracker.js +216 -0
- package/src/loadbalancer/session-bridge.js +179 -0
- package/src/loadbalancer/state/db.js +211 -0
- package/src/loadbalancer/state/migrations/001-initial.sql +50 -0
- package/src/loadbalancer/tools/index.js +123 -0
- package/src/loadbalancer/velocity-estimator.js +147 -0
- package/src/staging/staging.js +394 -0
- package/update-byan-agent/bin/update-byan-agent.js +27 -2
- package/API-BYAN-V2.md +0 -741
- package/BMAD-QUICK-REFERENCE.md +0 -370
- package/CHANGELOG-v2.1.0.md +0 -371
- package/MIGRATION-v2.0-to-v2.1.md +0 -430
- package/README-BYAN-V2.md +0 -446
- package/TEST-GUIDE-v2.3.2.md +0 -161
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installer step — BYAN memory-sync opt-in + consent (SM5).
|
|
3
|
+
*
|
|
4
|
+
* Shown during create-byan-agent. Only prompts when the user already
|
|
5
|
+
* provided a byan_web URL + token (via setupByanWebIntegration), since
|
|
6
|
+
* memory-sync without credentials is a no-op.
|
|
7
|
+
*
|
|
8
|
+
* On opt-in, writes :
|
|
9
|
+
* _byan/config.yaml → memory_sync: { enabled: true }
|
|
10
|
+
* OR loadbalancer.yaml if _byan/config.yaml not present
|
|
11
|
+
*
|
|
12
|
+
* Prints a clear consent notice listing what gets sent and how to
|
|
13
|
+
* disable. The user must type "oui" / "yes" to enable — no default to
|
|
14
|
+
* true.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs-extra');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const yaml = require('js-yaml');
|
|
20
|
+
const inquirer = require('inquirer');
|
|
21
|
+
const chalk = require('chalk');
|
|
22
|
+
|
|
23
|
+
const CONSENT_NOTICE = [
|
|
24
|
+
'',
|
|
25
|
+
chalk.yellow.bold('BYAN memory-sync — consent requis'),
|
|
26
|
+
'',
|
|
27
|
+
'Si vous activez cette option, apres chaque interaction avec',
|
|
28
|
+
'Claude Code ou Copilot CLI, BYAN envoie automatiquement a votre',
|
|
29
|
+
'instance byan_web les elements suivants :',
|
|
30
|
+
'',
|
|
31
|
+
' - messages utilisateur (prompts)',
|
|
32
|
+
' - reponses assistant',
|
|
33
|
+
' - chemins de fichiers modifies',
|
|
34
|
+
' - sessionId et timestamp',
|
|
35
|
+
'',
|
|
36
|
+
'Filtrage applique AVANT envoi :',
|
|
37
|
+
' - chit-chat (moins de 50 caracteres) -> ignore',
|
|
38
|
+
' - doublons (hash SHA256 du contenu) -> ignore',
|
|
39
|
+
' - categories : fact | decision | blocker | artifact',
|
|
40
|
+
'',
|
|
41
|
+
'Les donnees sont stockees dans VOTRE instance byan_web',
|
|
42
|
+
'(pas de tierce partie). Le token JWT identifie l auteur.',
|
|
43
|
+
'',
|
|
44
|
+
chalk.cyan('Desactiver plus tard :'),
|
|
45
|
+
' - editer _byan/config.yaml -> memory_sync: { enabled: false }',
|
|
46
|
+
' - OU invoquer le skill -> /byan-no-stage pour un turn',
|
|
47
|
+
'',
|
|
48
|
+
].join('\n');
|
|
49
|
+
|
|
50
|
+
async function promptConsent({ skipPrompts, defaultAnswer } = {}) {
|
|
51
|
+
if (skipPrompts) return { enabled: defaultAnswer === true };
|
|
52
|
+
|
|
53
|
+
console.log(CONSENT_NOTICE);
|
|
54
|
+
|
|
55
|
+
const { enable } = await inquirer.prompt([
|
|
56
|
+
{
|
|
57
|
+
type: 'confirm',
|
|
58
|
+
name: 'enable',
|
|
59
|
+
message:
|
|
60
|
+
'Activer la synchronisation automatique de vos conversations vers byan_web ?',
|
|
61
|
+
default: false,
|
|
62
|
+
},
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
return { enabled: enable };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function configPaths(projectRoot) {
|
|
69
|
+
return {
|
|
70
|
+
byanConfig: path.join(projectRoot, '_byan', 'config.yaml'),
|
|
71
|
+
lbConfig: path.join(projectRoot, 'loadbalancer.yaml'),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function writeMemorySyncFlag(projectRoot, enabled) {
|
|
76
|
+
const { byanConfig, lbConfig } = configPaths(projectRoot);
|
|
77
|
+
|
|
78
|
+
// Prefer _byan/config.yaml (BYAN primary config). Fall back to
|
|
79
|
+
// loadbalancer.yaml only if _byan/config.yaml is missing AND
|
|
80
|
+
// loadbalancer.yaml already exists.
|
|
81
|
+
let target;
|
|
82
|
+
if (await fs.pathExists(byanConfig)) {
|
|
83
|
+
target = byanConfig;
|
|
84
|
+
} else if (await fs.pathExists(lbConfig)) {
|
|
85
|
+
target = lbConfig;
|
|
86
|
+
} else {
|
|
87
|
+
target = byanConfig;
|
|
88
|
+
}
|
|
89
|
+
await fs.ensureDir(path.dirname(target));
|
|
90
|
+
|
|
91
|
+
let doc = {};
|
|
92
|
+
if (await fs.pathExists(target)) {
|
|
93
|
+
try {
|
|
94
|
+
doc = yaml.load(await fs.readFile(target, 'utf8')) || {};
|
|
95
|
+
} catch {
|
|
96
|
+
doc = {};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
doc.memory_sync = { ...(doc.memory_sync || {}), enabled: enabled === true };
|
|
100
|
+
|
|
101
|
+
await fs.writeFile(target, yaml.dump(doc), 'utf8');
|
|
102
|
+
return target;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function setupStagingConsent(projectRoot, options = {}) {
|
|
106
|
+
const tokenPresent = options.byanWebConfigured === true;
|
|
107
|
+
if (!tokenPresent) {
|
|
108
|
+
if (!options.quiet) {
|
|
109
|
+
console.log(
|
|
110
|
+
chalk.gray(' i memory-sync skipped (no byan_web token configured)')
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return { configured: false, reason: 'no_token' };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const { enabled } = await promptConsent({
|
|
117
|
+
skipPrompts: options.skipPrompts === true,
|
|
118
|
+
defaultAnswer: options.presetEnabled === true,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const target = await writeMemorySyncFlag(projectRoot, enabled);
|
|
122
|
+
|
|
123
|
+
if (!options.quiet) {
|
|
124
|
+
if (enabled) {
|
|
125
|
+
console.log(chalk.green(' OK memory-sync ENABLED in ' + path.relative(projectRoot, target)));
|
|
126
|
+
console.log(
|
|
127
|
+
chalk.gray(
|
|
128
|
+
' a chaque fin de turn, votre hook Stop (Claude) et votre'
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
console.log(
|
|
132
|
+
chalk.gray(
|
|
133
|
+
' extension Copilot staging enverront les memoires a byan_web.'
|
|
134
|
+
)
|
|
135
|
+
);
|
|
136
|
+
} else {
|
|
137
|
+
console.log(chalk.gray(' i memory-sync left DISABLED (opt-in declined)'));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { configured: true, enabled, configPath: target };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = {
|
|
145
|
+
setupStagingConsent,
|
|
146
|
+
writeMemorySyncFlag,
|
|
147
|
+
promptConsent,
|
|
148
|
+
CONSENT_NOTICE,
|
|
149
|
+
};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BMAD subagent generator (AW2).
|
|
3
|
+
*
|
|
4
|
+
* Reads a BYAN Copilot agent stub (.github/agents/<name>.md — YAML
|
|
5
|
+
* frontmatter + XML persona block) and emits a Claude Code native
|
|
6
|
+
* subagent file at .claude/agents/bmad-<slug>.md with :
|
|
7
|
+
* - frontmatter : name, description, model, color
|
|
8
|
+
* - body : system prompt derived from <persona> + <rules> + <menu>
|
|
9
|
+
*
|
|
10
|
+
* The resulting file makes the BMAD agent available as a
|
|
11
|
+
* subagent_type in Claude Code's Agent tool after a session restart.
|
|
12
|
+
* Model assignment is driven by ROLE_MODEL_MAP (token-optimal defaults).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
|
|
18
|
+
const ROLE_MODEL_MAP = {
|
|
19
|
+
// Reasoning-heavy → opus
|
|
20
|
+
architect: 'opus',
|
|
21
|
+
'bmm-architect': 'opus',
|
|
22
|
+
tea: 'opus',
|
|
23
|
+
'tea-tea': 'opus',
|
|
24
|
+
quinn: 'opus',
|
|
25
|
+
'bmm-quinn': 'opus',
|
|
26
|
+
'bmad-master': 'opus',
|
|
27
|
+
byan: 'opus',
|
|
28
|
+
'creative-problem-solver': 'opus',
|
|
29
|
+
'cis-creative-problem-solver': 'opus',
|
|
30
|
+
|
|
31
|
+
// Narrow mechanical → haiku
|
|
32
|
+
carmack: 'haiku',
|
|
33
|
+
rachid: 'haiku',
|
|
34
|
+
marc: 'haiku',
|
|
35
|
+
patnote: 'haiku',
|
|
36
|
+
drawio: 'haiku',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const ROLE_COLOR_MAP = {
|
|
40
|
+
opus: 'purple',
|
|
41
|
+
sonnet: 'blue',
|
|
42
|
+
haiku: 'cyan',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function normalizeName(stubFilename) {
|
|
46
|
+
return stubFilename
|
|
47
|
+
.replace(/^bmad-agent-/, '')
|
|
48
|
+
.replace(/\.md$/, '');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function pickModel(name) {
|
|
52
|
+
return ROLE_MODEL_MAP[name] || 'sonnet';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseFrontmatter(raw) {
|
|
56
|
+
const m = raw.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
|
|
57
|
+
if (!m) return {};
|
|
58
|
+
const out = {};
|
|
59
|
+
for (const line of m[1].split('\n')) {
|
|
60
|
+
const kv = line.match(/^(\w[\w-]*)\s*:\s*(.*)$/);
|
|
61
|
+
if (!kv) continue;
|
|
62
|
+
let v = kv[2].trim();
|
|
63
|
+
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
|
64
|
+
v = v.slice(1, -1);
|
|
65
|
+
}
|
|
66
|
+
out[kv[1]] = v;
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function extractXmlBlock(raw) {
|
|
72
|
+
const m = raw.match(/```xml\s*\n([\s\S]*?)\n```/);
|
|
73
|
+
return m ? m[1] : '';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function extractSection(xml, tag) {
|
|
77
|
+
const re = new RegExp(`<${tag}(?:\\s+[^>]*)?>([\\s\\S]*?)<\\/${tag}>`);
|
|
78
|
+
const m = xml.match(re);
|
|
79
|
+
return m ? m[1].trim() : null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function extractListItems(xml, parentTag, itemTag) {
|
|
83
|
+
const parent = extractSection(xml, parentTag);
|
|
84
|
+
if (!parent) return [];
|
|
85
|
+
const re = new RegExp(`<${itemTag}(?:\\s+[^>]*)?>([\\s\\S]*?)<\\/${itemTag}>`, 'g');
|
|
86
|
+
const items = [];
|
|
87
|
+
let m;
|
|
88
|
+
while ((m = re.exec(parent)) !== null) items.push(m[1].trim());
|
|
89
|
+
return items;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function cleanXmlTags(text) {
|
|
93
|
+
if (!text) return '';
|
|
94
|
+
return text
|
|
95
|
+
.replace(/<\/?[a-zA-Z_][\w-]*(\s+[^>]*)?>/g, '')
|
|
96
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
97
|
+
.trim();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function buildBody({ fm, persona, rules, menu, capabilities }) {
|
|
101
|
+
const lines = [];
|
|
102
|
+
lines.push(`# ${fm.name}`);
|
|
103
|
+
lines.push('');
|
|
104
|
+
if (fm.description) {
|
|
105
|
+
lines.push(fm.description);
|
|
106
|
+
lines.push('');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (persona) {
|
|
110
|
+
lines.push('## Persona');
|
|
111
|
+
lines.push('');
|
|
112
|
+
lines.push(cleanXmlTags(persona));
|
|
113
|
+
lines.push('');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (rules && rules.length) {
|
|
117
|
+
lines.push('## Operating rules');
|
|
118
|
+
lines.push('');
|
|
119
|
+
for (const r of rules) lines.push(`- ${cleanXmlTags(r)}`);
|
|
120
|
+
lines.push('');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (capabilities && capabilities.length) {
|
|
124
|
+
lines.push('## Capabilities');
|
|
125
|
+
lines.push('');
|
|
126
|
+
for (const c of capabilities) lines.push(`- ${cleanXmlTags(c)}`);
|
|
127
|
+
lines.push('');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (menu && menu.length) {
|
|
131
|
+
lines.push('## Menu commands');
|
|
132
|
+
lines.push('');
|
|
133
|
+
for (const m of menu) lines.push(`- ${cleanXmlTags(m)}`);
|
|
134
|
+
lines.push('');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
lines.push('## Reporting contract');
|
|
138
|
+
lines.push('');
|
|
139
|
+
lines.push(
|
|
140
|
+
'When invoked via the Agent tool, stay in the persona above. Respond with a concise JSON report when the task completes : { status: "ok|partial|failed", summary: "<200 words", files_changed: [paths], next_steps: [] }.'
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return lines.join('\n');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function generateSubagent(stubPath, options = {}) {
|
|
147
|
+
const raw = fs.readFileSync(stubPath, 'utf8');
|
|
148
|
+
const fm = parseFrontmatter(raw);
|
|
149
|
+
if (!fm.name) throw new Error(`stub missing name frontmatter: ${stubPath}`);
|
|
150
|
+
|
|
151
|
+
const baseName = options.name || normalizeName(path.basename(stubPath));
|
|
152
|
+
const xml = extractXmlBlock(raw);
|
|
153
|
+
|
|
154
|
+
const persona = extractSection(xml, 'persona') || '';
|
|
155
|
+
const rules = extractListItems(xml, 'rules', 'r');
|
|
156
|
+
const menu = extractListItems(xml, 'menu', 'item');
|
|
157
|
+
const capabilities = extractListItems(xml, 'capabilities', 'cap');
|
|
158
|
+
|
|
159
|
+
const subagentName = options.subagentName || `bmad-${baseName}`;
|
|
160
|
+
const model = options.model || pickModel(baseName);
|
|
161
|
+
const color = options.color || ROLE_COLOR_MAP[model] || 'blue';
|
|
162
|
+
|
|
163
|
+
const description =
|
|
164
|
+
options.description ||
|
|
165
|
+
fm.description ||
|
|
166
|
+
`BMAD ${baseName} agent — invoke for ${baseName} role tasks`;
|
|
167
|
+
|
|
168
|
+
const body = buildBody({
|
|
169
|
+
fm: { ...fm, name: subagentName },
|
|
170
|
+
persona,
|
|
171
|
+
rules,
|
|
172
|
+
menu,
|
|
173
|
+
capabilities,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const content = `---\nname: ${subagentName}\ndescription: ${escapeYaml(description)}\nmodel: ${model}\ncolor: ${color}\n---\n\n${body}\n`;
|
|
177
|
+
|
|
178
|
+
const outPath =
|
|
179
|
+
options.outPath ||
|
|
180
|
+
path.join('.claude', 'agents', `${subagentName}.md`);
|
|
181
|
+
|
|
182
|
+
if (options.write !== false) {
|
|
183
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
184
|
+
fs.writeFileSync(outPath, content);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { outPath, subagentName, model, color, content };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function escapeYaml(v) {
|
|
191
|
+
if (typeof v !== 'string') return '';
|
|
192
|
+
if (/[:#\n]/.test(v)) return JSON.stringify(v);
|
|
193
|
+
return v;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = {
|
|
197
|
+
ROLE_MODEL_MAP,
|
|
198
|
+
ROLE_COLOR_MAP,
|
|
199
|
+
normalizeName,
|
|
200
|
+
pickModel,
|
|
201
|
+
parseFrontmatter,
|
|
202
|
+
extractXmlBlock,
|
|
203
|
+
extractSection,
|
|
204
|
+
extractListItems,
|
|
205
|
+
cleanXmlTags,
|
|
206
|
+
buildBody,
|
|
207
|
+
generateSubagent,
|
|
208
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BYAN token ledger.
|
|
3
|
+
*
|
|
4
|
+
* Reads _byan-output/tool-log.jsonl produced by the transparency hooks
|
|
5
|
+
* and aggregates per-session token estimates :
|
|
6
|
+
* - total tool calls
|
|
7
|
+
* - total est input + output tokens
|
|
8
|
+
* - per-tool breakdown (count + tokens)
|
|
9
|
+
* - failure summary
|
|
10
|
+
*
|
|
11
|
+
* Estimates are based on char/4 heuristic. Real token counts would need
|
|
12
|
+
* Anthropic's count_tokens API which requires authentication — skipped
|
|
13
|
+
* for now, good-enough for budget reasoning.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
function readLog(logPath) {
|
|
20
|
+
if (!fs.existsSync(logPath)) return [];
|
|
21
|
+
return fs
|
|
22
|
+
.readFileSync(logPath, 'utf8')
|
|
23
|
+
.split('\n')
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.map((line) => {
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(line);
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
.filter(Boolean);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function summarize(entries, { since } = {}) {
|
|
36
|
+
const cutoff = since ? Date.parse(since) : 0;
|
|
37
|
+
const filtered = entries.filter((e) => {
|
|
38
|
+
const t = Date.parse(e.timestamp);
|
|
39
|
+
return Number.isFinite(t) && t >= cutoff;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const stats = {
|
|
43
|
+
total_entries: filtered.length,
|
|
44
|
+
pre_count: 0,
|
|
45
|
+
post_count: 0,
|
|
46
|
+
est_input_tokens: 0,
|
|
47
|
+
est_output_tokens: 0,
|
|
48
|
+
by_tool: {},
|
|
49
|
+
failures: [],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
for (const e of filtered) {
|
|
53
|
+
if (e.phase === 'pre') {
|
|
54
|
+
stats.pre_count += 1;
|
|
55
|
+
stats.est_input_tokens += Number(e.est_input_tokens || 0);
|
|
56
|
+
} else if (e.phase === 'post') {
|
|
57
|
+
stats.post_count += 1;
|
|
58
|
+
stats.est_output_tokens += Number(e.est_output_tokens || 0);
|
|
59
|
+
if (e.ok === false) {
|
|
60
|
+
stats.failures.push({
|
|
61
|
+
timestamp: e.timestamp,
|
|
62
|
+
tool: e.tool,
|
|
63
|
+
kind: e.failure_kind,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const tool = e.tool || 'unknown';
|
|
69
|
+
if (!stats.by_tool[tool]) {
|
|
70
|
+
stats.by_tool[tool] = { calls: 0, input: 0, output: 0, failures: 0 };
|
|
71
|
+
}
|
|
72
|
+
if (e.phase === 'pre') stats.by_tool[tool].calls += 1;
|
|
73
|
+
stats.by_tool[tool].input += Number(e.est_input_tokens || 0);
|
|
74
|
+
stats.by_tool[tool].output += Number(e.est_output_tokens || 0);
|
|
75
|
+
if (e.ok === false) stats.by_tool[tool].failures += 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
stats.est_total_tokens = stats.est_input_tokens + stats.est_output_tokens;
|
|
79
|
+
return stats;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function renderReport(stats) {
|
|
83
|
+
const lines = [];
|
|
84
|
+
lines.push('BYAN token ledger');
|
|
85
|
+
lines.push('');
|
|
86
|
+
lines.push(` Tool calls : ${stats.pre_count}`);
|
|
87
|
+
lines.push(` Completed : ${stats.post_count}`);
|
|
88
|
+
lines.push(` Est. input tokens : ${stats.est_input_tokens.toLocaleString()}`);
|
|
89
|
+
lines.push(` Est. output tokens : ${stats.est_output_tokens.toLocaleString()}`);
|
|
90
|
+
lines.push(` Est. total tokens : ${stats.est_total_tokens.toLocaleString()}`);
|
|
91
|
+
lines.push(` Failures : ${stats.failures.length}`);
|
|
92
|
+
|
|
93
|
+
lines.push('');
|
|
94
|
+
lines.push(' Per tool :');
|
|
95
|
+
const rows = Object.entries(stats.by_tool).sort(
|
|
96
|
+
(a, b) => b[1].input + b[1].output - (a[1].input + a[1].output)
|
|
97
|
+
);
|
|
98
|
+
for (const [tool, s] of rows) {
|
|
99
|
+
const total = s.input + s.output;
|
|
100
|
+
const failNote = s.failures > 0 ? ` [${s.failures} fail]` : '';
|
|
101
|
+
lines.push(
|
|
102
|
+
` ${tool.padEnd(16)} calls=${String(s.calls).padStart(3)} in=${s.input
|
|
103
|
+
.toString()
|
|
104
|
+
.padStart(6)} out=${s.output.toString().padStart(6)} total=${total
|
|
105
|
+
.toString()
|
|
106
|
+
.padStart(7)}${failNote}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (stats.failures.length > 0) {
|
|
111
|
+
lines.push('');
|
|
112
|
+
lines.push(' Recent failures :');
|
|
113
|
+
for (const f of stats.failures.slice(-5)) {
|
|
114
|
+
lines.push(` ${f.timestamp} ${f.tool} (${f.kind})`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return lines.join('\n');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function defaultLogPath(projectRoot) {
|
|
122
|
+
const root = projectRoot || process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
123
|
+
return path.join(root, '_byan-output', 'tool-log.jsonl');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = {
|
|
127
|
+
readLog,
|
|
128
|
+
summarize,
|
|
129
|
+
renderReport,
|
|
130
|
+
defaultLogPath,
|
|
131
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bmad-bmad-master
|
|
3
|
+
description: bmad-master agent
|
|
4
|
+
model: opus
|
|
5
|
+
color: purple
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# bmad-bmad-master
|
|
9
|
+
|
|
10
|
+
bmad-master agent
|
|
11
|
+
|
|
12
|
+
## Reporting contract
|
|
13
|
+
|
|
14
|
+
When invoked via the Agent tool, stay in the persona above. Respond with a concise JSON report when the task completes : { status: "ok|partial|failed", summary: "<200 words", files_changed: [paths], next_steps: [] }.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bmad-bmb-agent-builder
|
|
3
|
+
description: agent-builder agent
|
|
4
|
+
model: sonnet
|
|
5
|
+
color: blue
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# bmad-bmb-agent-builder
|
|
9
|
+
|
|
10
|
+
agent-builder agent
|
|
11
|
+
|
|
12
|
+
## Reporting contract
|
|
13
|
+
|
|
14
|
+
When invoked via the Agent tool, stay in the persona above. Respond with a concise JSON report when the task completes : { status: "ok|partial|failed", summary: "<200 words", files_changed: [paths], next_steps: [] }.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bmad-bmb-module-builder
|
|
3
|
+
description: module-builder agent
|
|
4
|
+
model: sonnet
|
|
5
|
+
color: blue
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# bmad-bmb-module-builder
|
|
9
|
+
|
|
10
|
+
module-builder agent
|
|
11
|
+
|
|
12
|
+
## Reporting contract
|
|
13
|
+
|
|
14
|
+
When invoked via the Agent tool, stay in the persona above. Respond with a concise JSON report when the task completes : { status: "ok|partial|failed", summary: "<200 words", files_changed: [paths], next_steps: [] }.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bmad-bmb-workflow-builder
|
|
3
|
+
description: workflow-builder agent
|
|
4
|
+
model: sonnet
|
|
5
|
+
color: blue
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# bmad-bmb-workflow-builder
|
|
9
|
+
|
|
10
|
+
workflow-builder agent
|
|
11
|
+
|
|
12
|
+
## Reporting contract
|
|
13
|
+
|
|
14
|
+
When invoked via the Agent tool, stay in the persona above. Respond with a concise JSON report when the task completes : { status: "ok|partial|failed", summary: "<200 words", files_changed: [paths], next_steps: [] }.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bmad-bmm-analyst
|
|
3
|
+
description: analyst agent
|
|
4
|
+
model: sonnet
|
|
5
|
+
color: blue
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# bmad-bmm-analyst
|
|
9
|
+
|
|
10
|
+
analyst agent
|
|
11
|
+
|
|
12
|
+
## Reporting contract
|
|
13
|
+
|
|
14
|
+
When invoked via the Agent tool, stay in the persona above. Respond with a concise JSON report when the task completes : { status: "ok|partial|failed", summary: "<200 words", files_changed: [paths], next_steps: [] }.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bmad-bmm-architect
|
|
3
|
+
description: architect agent
|
|
4
|
+
model: opus
|
|
5
|
+
color: purple
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# bmad-bmm-architect
|
|
9
|
+
|
|
10
|
+
architect agent
|
|
11
|
+
|
|
12
|
+
## Reporting contract
|
|
13
|
+
|
|
14
|
+
When invoked via the Agent tool, stay in the persona above. Respond with a concise JSON report when the task completes : { status: "ok|partial|failed", summary: "<200 words", files_changed: [paths], next_steps: [] }.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bmad-bmm-dev
|
|
3
|
+
description: dev agent
|
|
4
|
+
model: sonnet
|
|
5
|
+
color: blue
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# bmad-bmm-dev
|
|
9
|
+
|
|
10
|
+
dev agent
|
|
11
|
+
|
|
12
|
+
## Reporting contract
|
|
13
|
+
|
|
14
|
+
When invoked via the Agent tool, stay in the persona above. Respond with a concise JSON report when the task completes : { status: "ok|partial|failed", summary: "<200 words", files_changed: [paths], next_steps: [] }.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bmad-bmm-pm
|
|
3
|
+
description: pm agent
|
|
4
|
+
model: sonnet
|
|
5
|
+
color: blue
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# bmad-bmm-pm
|
|
9
|
+
|
|
10
|
+
pm agent
|
|
11
|
+
|
|
12
|
+
## Reporting contract
|
|
13
|
+
|
|
14
|
+
When invoked via the Agent tool, stay in the persona above. Respond with a concise JSON report when the task completes : { status: "ok|partial|failed", summary: "<200 words", files_changed: [paths], next_steps: [] }.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bmad-bmm-quick-flow-solo-dev
|
|
3
|
+
description: quick-flow-solo-dev agent
|
|
4
|
+
model: sonnet
|
|
5
|
+
color: blue
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# bmad-bmm-quick-flow-solo-dev
|
|
9
|
+
|
|
10
|
+
quick-flow-solo-dev agent
|
|
11
|
+
|
|
12
|
+
## Reporting contract
|
|
13
|
+
|
|
14
|
+
When invoked via the Agent tool, stay in the persona above. Respond with a concise JSON report when the task completes : { status: "ok|partial|failed", summary: "<200 words", files_changed: [paths], next_steps: [] }.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bmad-bmm-quinn
|
|
3
|
+
description: quinn agent
|
|
4
|
+
model: opus
|
|
5
|
+
color: purple
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# bmad-bmm-quinn
|
|
9
|
+
|
|
10
|
+
quinn agent
|
|
11
|
+
|
|
12
|
+
## Reporting contract
|
|
13
|
+
|
|
14
|
+
When invoked via the Agent tool, stay in the persona above. Respond with a concise JSON report when the task completes : { status: "ok|partial|failed", summary: "<200 words", files_changed: [paths], next_steps: [] }.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bmad-bmm-sm
|
|
3
|
+
description: sm agent
|
|
4
|
+
model: sonnet
|
|
5
|
+
color: blue
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# bmad-bmm-sm
|
|
9
|
+
|
|
10
|
+
sm agent
|
|
11
|
+
|
|
12
|
+
## Reporting contract
|
|
13
|
+
|
|
14
|
+
When invoked via the Agent tool, stay in the persona above. Respond with a concise JSON report when the task completes : { status: "ok|partial|failed", summary: "<200 words", files_changed: [paths], next_steps: [] }.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bmad-bmm-tech-writer
|
|
3
|
+
description: tech-writer agent
|
|
4
|
+
model: sonnet
|
|
5
|
+
color: blue
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# bmad-bmm-tech-writer
|
|
9
|
+
|
|
10
|
+
tech-writer agent
|
|
11
|
+
|
|
12
|
+
## Reporting contract
|
|
13
|
+
|
|
14
|
+
When invoked via the Agent tool, stay in the persona above. Respond with a concise JSON report when the task completes : { status: "ok|partial|failed", summary: "<200 words", files_changed: [paths], next_steps: [] }.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bmad-bmm-ux-designer
|
|
3
|
+
description: ux-designer agent
|
|
4
|
+
model: sonnet
|
|
5
|
+
color: blue
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# bmad-bmm-ux-designer
|
|
9
|
+
|
|
10
|
+
ux-designer agent
|
|
11
|
+
|
|
12
|
+
## Reporting contract
|
|
13
|
+
|
|
14
|
+
When invoked via the Agent tool, stay in the persona above. Respond with a concise JSON report when the task completes : { status: "ok|partial|failed", summary: "<200 words", files_changed: [paths], next_steps: [] }.
|