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,156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* BYAN cleanup CLI.
|
|
4
|
+
*
|
|
5
|
+
* Scans the current project for stale Claude Code skills and stale
|
|
6
|
+
* root-level doc files, then archives them (safe default) or hard
|
|
7
|
+
* deletes (with --hard).
|
|
8
|
+
*
|
|
9
|
+
* Flags :
|
|
10
|
+
* --dry-run list candidates, do nothing
|
|
11
|
+
* --yes skip confirmation (batch mode)
|
|
12
|
+
* --hard delete instead of archive
|
|
13
|
+
* --skills-only | --docs-only restrict scope
|
|
14
|
+
* --root <dir> override project root (default cwd)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { findStaleSkills, findStaleDocs } = require('../lib/cleanup/detector');
|
|
19
|
+
const { archive, deleteHard } = require('../lib/cleanup/executor');
|
|
20
|
+
|
|
21
|
+
function parseArgs(argv) {
|
|
22
|
+
const out = {
|
|
23
|
+
dryRun: false,
|
|
24
|
+
yes: false,
|
|
25
|
+
hard: false,
|
|
26
|
+
skillsOnly: false,
|
|
27
|
+
docsOnly: false,
|
|
28
|
+
root: process.cwd(),
|
|
29
|
+
};
|
|
30
|
+
for (let i = 2; i < argv.length; i++) {
|
|
31
|
+
const a = argv[i];
|
|
32
|
+
if (a === '--dry-run') out.dryRun = true;
|
|
33
|
+
else if (a === '--yes' || a === '-y') out.yes = true;
|
|
34
|
+
else if (a === '--hard') out.hard = true;
|
|
35
|
+
else if (a === '--skills-only') out.skillsOnly = true;
|
|
36
|
+
else if (a === '--docs-only') out.docsOnly = true;
|
|
37
|
+
else if (a === '--root') out.root = argv[++i];
|
|
38
|
+
else if (a === '-h' || a === '--help') out.help = true;
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function usage() {
|
|
44
|
+
console.log(
|
|
45
|
+
[
|
|
46
|
+
'byan-cleanup — scan a BYAN project for stale skills + docs',
|
|
47
|
+
'',
|
|
48
|
+
'Usage:',
|
|
49
|
+
' byan-cleanup [--dry-run] [--yes] [--hard] [--skills-only|--docs-only] [--root <dir>]',
|
|
50
|
+
'',
|
|
51
|
+
'Flags:',
|
|
52
|
+
' --dry-run list candidates, do nothing',
|
|
53
|
+
' --yes skip confirmation (batch mode)',
|
|
54
|
+
' --hard delete instead of archive (default: archive)',
|
|
55
|
+
' --skills-only scan only .claude/skills/',
|
|
56
|
+
' --docs-only scan only root *.md / *.txt',
|
|
57
|
+
' --root <dir> project root (default: cwd)',
|
|
58
|
+
'',
|
|
59
|
+
'Default behavior: archive to _byan-output/archive/<timestamp>/',
|
|
60
|
+
].join('\n')
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function fmtSize(n) {
|
|
65
|
+
if (n < 1024) return `${n}B`;
|
|
66
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`;
|
|
67
|
+
return `${(n / 1024 / 1024).toFixed(1)}MB`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function printGroup(label, items) {
|
|
71
|
+
if (items.length === 0) {
|
|
72
|
+
console.log(`\n ${label}: none`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
console.log(`\n ${label} (${items.length}):`);
|
|
76
|
+
for (const it of items) {
|
|
77
|
+
console.log(` - ${it.name} ${fmtSize(it.size || 0)}`);
|
|
78
|
+
for (const r of it.reasons) console.log(` · ${r}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function confirm(prompt) {
|
|
83
|
+
if (process.stdin.isTTY) {
|
|
84
|
+
const readline = require('readline');
|
|
85
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
rl.question(prompt, (ans) => {
|
|
88
|
+
rl.close();
|
|
89
|
+
resolve(/^y(es)?$/i.test((ans || '').trim()));
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
// non-TTY : refuse action unless --yes
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function main() {
|
|
98
|
+
const args = parseArgs(process.argv);
|
|
99
|
+
if (args.help) {
|
|
100
|
+
usage();
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const root = path.resolve(args.root);
|
|
105
|
+
const skillsDir = path.join(root, '.claude', 'skills');
|
|
106
|
+
|
|
107
|
+
const skills = args.docsOnly ? [] : findStaleSkills(skillsDir);
|
|
108
|
+
const docs = args.skillsOnly ? [] : findStaleDocs(root);
|
|
109
|
+
|
|
110
|
+
console.log(`BYAN cleanup scan — root: ${root}`);
|
|
111
|
+
printGroup('Stale skills', skills);
|
|
112
|
+
printGroup('Stale docs', docs);
|
|
113
|
+
const total = skills.length + docs.length;
|
|
114
|
+
console.log(`\n Total candidates: ${total}`);
|
|
115
|
+
|
|
116
|
+
if (args.dryRun) {
|
|
117
|
+
console.log('\n--dry-run: no action taken.');
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (total === 0) {
|
|
122
|
+
console.log('Nothing to clean.');
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const action = args.hard ? 'HARD DELETE (irreversible)' : 'archive to _byan-output/archive/<timestamp>/';
|
|
127
|
+
console.log(`\nAction: ${action}`);
|
|
128
|
+
|
|
129
|
+
let proceed = args.yes;
|
|
130
|
+
if (!proceed) {
|
|
131
|
+
proceed = await confirm(`Proceed with ${total} item(s) ? (y/N): `);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!proceed) {
|
|
135
|
+
console.log('Aborted.');
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const all = [...skills, ...docs];
|
|
140
|
+
if (args.hard) {
|
|
141
|
+
const r = deleteHard(all);
|
|
142
|
+
console.log(`\nDeleted ${r.deleted.length} items.`);
|
|
143
|
+
if (r.errors.length) console.log(`Errors: ${r.errors.length}`);
|
|
144
|
+
for (const e of r.errors) console.log(` ! ${e.path} — ${e.error}`);
|
|
145
|
+
} else {
|
|
146
|
+
const r = archive(all, { archiveRoot: path.join(root, '_byan-output', 'archive') });
|
|
147
|
+
console.log(`\nArchived ${r.moved.length} items to ${r.archiveDir}`);
|
|
148
|
+
if (r.errors.length) console.log(`Errors: ${r.errors.length}`);
|
|
149
|
+
for (const e of r.errors) console.log(` ! ${e.path} — ${e.error}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
main().catch((err) => {
|
|
154
|
+
console.error('byan-cleanup failed:', err.message);
|
|
155
|
+
process.exit(1);
|
|
156
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* BYAN kanban CLI — view a party-mode-session board and stand-up feed.
|
|
4
|
+
*
|
|
5
|
+
* Usage :
|
|
6
|
+
* byan-kanban <session-id> # show board + recent stand-ups
|
|
7
|
+
* byan-kanban <session-id> --json
|
|
8
|
+
* byan-kanban <session-id> --standups-only
|
|
9
|
+
* byan-kanban list # list known sessions with boards
|
|
10
|
+
* byan-kanban <session-id> --root <dir>
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const COLORS = { todo: 90, doing: 33, blocked: 31, review: 36, done: 32, dim: 2, reset: 0 };
|
|
17
|
+
const FG = (n, s) => `\x1b[${n}m${s}\x1b[0m`;
|
|
18
|
+
|
|
19
|
+
function parseArgs(argv) {
|
|
20
|
+
const out = { json: false, standupsOnly: false, root: process.cwd(), sessionId: null, list: false };
|
|
21
|
+
for (let i = 2; i < argv.length; i++) {
|
|
22
|
+
const a = argv[i];
|
|
23
|
+
if (a === '--json') out.json = true;
|
|
24
|
+
else if (a === '--standups-only') out.standupsOnly = true;
|
|
25
|
+
else if (a === '--root') out.root = argv[++i];
|
|
26
|
+
else if (a === 'list') out.list = true;
|
|
27
|
+
else if (!out.sessionId) out.sessionId = a;
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function sessionsRoot(root) {
|
|
33
|
+
return path.join(root, '_byan-output', 'party-mode-sessions');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function listSessions(root) {
|
|
37
|
+
const dir = sessionsRoot(root);
|
|
38
|
+
if (!fs.existsSync(dir)) return [];
|
|
39
|
+
return fs
|
|
40
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
41
|
+
.filter((e) => e.isDirectory())
|
|
42
|
+
.map((e) => {
|
|
43
|
+
const kanban = path.join(dir, e.name, 'kanban.json');
|
|
44
|
+
const standup = path.join(dir, e.name, 'standup.jsonl');
|
|
45
|
+
return {
|
|
46
|
+
session_id: e.name,
|
|
47
|
+
has_kanban: fs.existsSync(kanban),
|
|
48
|
+
has_standup: fs.existsSync(standup),
|
|
49
|
+
};
|
|
50
|
+
})
|
|
51
|
+
.sort((a, b) => (a.session_id < b.session_id ? 1 : -1));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function renderBoard(board) {
|
|
55
|
+
if (!board) return '(no kanban for this session)';
|
|
56
|
+
const cols = board.columns || ['todo', 'doing', 'blocked', 'review', 'done'];
|
|
57
|
+
const rows = cols.map((c) => {
|
|
58
|
+
const cards = Object.values(board.cards || {}).filter((card) => card.column === c);
|
|
59
|
+
return { col: c, cards };
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const lines = [];
|
|
63
|
+
lines.push(FG(COLORS.dim, `session ${board.session_id} — updated ${board.updated_at}`));
|
|
64
|
+
lines.push('');
|
|
65
|
+
for (const r of rows) {
|
|
66
|
+
const color = COLORS[r.col] || 0;
|
|
67
|
+
lines.push(FG(color, `[${r.col.toUpperCase()}] (${r.cards.length})`));
|
|
68
|
+
if (r.cards.length === 0) {
|
|
69
|
+
lines.push(FG(COLORS.dim, ' (empty)'));
|
|
70
|
+
} else {
|
|
71
|
+
for (const c of r.cards) {
|
|
72
|
+
const assignee = c.assignee ? ` @${c.assignee}` : '';
|
|
73
|
+
const blocker = c.column === 'blocked' && c.blocker_reason ? ` — blocked: ${c.blocker_reason}` : '';
|
|
74
|
+
lines.push(` - [${c.priority}] ${c.id} ${c.title}${assignee}${blocker}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
lines.push('');
|
|
78
|
+
}
|
|
79
|
+
return lines.join('\n');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function renderStandups(entries) {
|
|
83
|
+
if (entries.length === 0) return FG(COLORS.dim, '(no stand-ups yet)');
|
|
84
|
+
return entries
|
|
85
|
+
.map((e) => {
|
|
86
|
+
const blockers = e.blockers && e.blockers.length > 0
|
|
87
|
+
? FG(COLORS.blocked, ` blockers=${e.blockers.join('|')}`)
|
|
88
|
+
: '';
|
|
89
|
+
return `${e.timestamp} ${e.agent}: ${e.did || '-'}${blockers} → ${e.next || '-'}`;
|
|
90
|
+
})
|
|
91
|
+
.join('\n');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function main() {
|
|
95
|
+
const args = parseArgs(process.argv);
|
|
96
|
+
const root = path.resolve(args.root);
|
|
97
|
+
|
|
98
|
+
if (args.list || !args.sessionId) {
|
|
99
|
+
const sessions = listSessions(root);
|
|
100
|
+
if (args.json) {
|
|
101
|
+
process.stdout.write(JSON.stringify(sessions, null, 2) + '\n');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (sessions.length === 0) {
|
|
105
|
+
console.log('(no party-mode sessions found)');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
console.log('sessions :');
|
|
109
|
+
for (const s of sessions) {
|
|
110
|
+
const flags =
|
|
111
|
+
(s.has_kanban ? 'K' : '-') + (s.has_standup ? 'S' : '-');
|
|
112
|
+
console.log(` [${flags}] ${s.session_id}`);
|
|
113
|
+
}
|
|
114
|
+
console.log('\npass a session id to view its board.');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const sessionDir = path.join(sessionsRoot(root), args.sessionId);
|
|
119
|
+
const kanbanPath = path.join(sessionDir, 'kanban.json');
|
|
120
|
+
const standupPath = path.join(sessionDir, 'standup.jsonl');
|
|
121
|
+
|
|
122
|
+
let board = null;
|
|
123
|
+
if (fs.existsSync(kanbanPath)) {
|
|
124
|
+
try {
|
|
125
|
+
board = JSON.parse(fs.readFileSync(kanbanPath, 'utf8'));
|
|
126
|
+
} catch {
|
|
127
|
+
board = null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let standups = [];
|
|
132
|
+
if (fs.existsSync(standupPath)) {
|
|
133
|
+
standups = fs
|
|
134
|
+
.readFileSync(standupPath, 'utf8')
|
|
135
|
+
.split('\n')
|
|
136
|
+
.filter(Boolean)
|
|
137
|
+
.map((l) => {
|
|
138
|
+
try {
|
|
139
|
+
return JSON.parse(l);
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
.filter(Boolean);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (args.json) {
|
|
148
|
+
process.stdout.write(JSON.stringify({ board, standups }, null, 2) + '\n');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!args.standupsOnly) {
|
|
153
|
+
console.log(renderBoard(board));
|
|
154
|
+
}
|
|
155
|
+
console.log('stand-ups :');
|
|
156
|
+
console.log(renderStandups(standups.slice(-10)));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
main();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* BYAN token ledger CLI.
|
|
4
|
+
*
|
|
5
|
+
* Usage :
|
|
6
|
+
* byan-ledger # full session summary
|
|
7
|
+
* byan-ledger --json # machine-readable
|
|
8
|
+
* byan-ledger --since 2026-04-19T15:00:00Z
|
|
9
|
+
* byan-ledger --root <dir> # override project root
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { readLog, summarize, renderReport, defaultLogPath } = require('../lib/token-ledger');
|
|
14
|
+
|
|
15
|
+
function parseArgs(argv) {
|
|
16
|
+
const out = { json: false, since: null, root: process.cwd() };
|
|
17
|
+
for (let i = 2; i < argv.length; i++) {
|
|
18
|
+
const a = argv[i];
|
|
19
|
+
if (a === '--json') out.json = true;
|
|
20
|
+
else if (a === '--since') out.since = argv[++i];
|
|
21
|
+
else if (a === '--root') out.root = argv[++i];
|
|
22
|
+
else if (a === '-h' || a === '--help') out.help = true;
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function main() {
|
|
28
|
+
const args = parseArgs(process.argv);
|
|
29
|
+
if (args.help) {
|
|
30
|
+
console.log('byan-ledger [--json] [--since <iso>] [--root <dir>]');
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const logPath = defaultLogPath(path.resolve(args.root));
|
|
35
|
+
const entries = readLog(logPath);
|
|
36
|
+
const stats = summarize(entries, { since: args.since });
|
|
37
|
+
|
|
38
|
+
if (args.json) {
|
|
39
|
+
process.stdout.write(JSON.stringify(stats, null, 2) + '\n');
|
|
40
|
+
} else {
|
|
41
|
+
process.stdout.write(renderReport(stats) + '\n');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
main();
|
|
@@ -16,6 +16,7 @@ const { generateProjectAgentsDoc } = require('../lib/project-agents-generator');
|
|
|
16
16
|
const { launchPhase2Chat, generateDefaultConfig } = require('../lib/phase2-chat');
|
|
17
17
|
const { setupByanWebIntegration } = require('../lib/byan-web-integration');
|
|
18
18
|
const { setupClaudeNative } = require('../lib/claude-native-setup');
|
|
19
|
+
const { setupStagingConsent } = require('../lib/staging-consent');
|
|
19
20
|
|
|
20
21
|
const BYAN_VERSION = require('../package.json').version;
|
|
21
22
|
|
|
@@ -1355,11 +1356,24 @@ async function install() {
|
|
|
1355
1356
|
if (needsClaude || needsCopilot) {
|
|
1356
1357
|
console.log();
|
|
1357
1358
|
console.log(chalk.cyan('byan_web integration (optional — service payant)'));
|
|
1359
|
+
let byanWebResult = { configured: false };
|
|
1358
1360
|
try {
|
|
1359
|
-
await setupByanWebIntegration(projectRoot);
|
|
1361
|
+
byanWebResult = await setupByanWebIntegration(projectRoot);
|
|
1360
1362
|
} catch (error) {
|
|
1361
1363
|
console.log(chalk.yellow(` ⚠ byan_web setup skipped: ${error.message}`));
|
|
1362
1364
|
}
|
|
1365
|
+
|
|
1366
|
+
if (byanWebResult && byanWebResult.configured) {
|
|
1367
|
+
console.log();
|
|
1368
|
+
console.log(chalk.cyan('byan_web memory-sync opt-in (consent)'));
|
|
1369
|
+
try {
|
|
1370
|
+
await setupStagingConsent(projectRoot, {
|
|
1371
|
+
byanWebConfigured: true,
|
|
1372
|
+
});
|
|
1373
|
+
} catch (error) {
|
|
1374
|
+
console.log(chalk.yellow(` ⚠ memory-sync setup skipped: ${error.message}`));
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1363
1377
|
}
|
|
1364
1378
|
|
|
1365
1379
|
// Step 8: Create config.yaml
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BYAN cleanup detector.
|
|
3
|
+
*
|
|
4
|
+
* Two scans :
|
|
5
|
+
* - findStaleSkills(skillsDir) : Claude Code skills under .claude/skills/
|
|
6
|
+
* that look like auto-generated stubs with no real BYAN value (test
|
|
7
|
+
* agents, self-referential, platform skills, or tiny SKILL.md files).
|
|
8
|
+
* - findStaleDocs(projectRoot) : root-level .md files that look like
|
|
9
|
+
* historical artifacts (old version docs, sprint reports, test plans,
|
|
10
|
+
* interview summaries, announcement guides).
|
|
11
|
+
*
|
|
12
|
+
* All detection is pattern-based with a hard allowlist ; no file is
|
|
13
|
+
* flagged without a named reason.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
const SKILL_ALLOWLIST = new Set([
|
|
20
|
+
'byan-fact-check',
|
|
21
|
+
'byan-elo-trust',
|
|
22
|
+
'byan-merise-agile',
|
|
23
|
+
'byan-hermes-dispatch',
|
|
24
|
+
'byan-forge',
|
|
25
|
+
'byan-byan', // BYAN itself as a skill — core, never flag
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
const SKILL_PATTERNS = [
|
|
29
|
+
{ re: /(^|-)test(ing|-dynamic)?$/i, reason: 'test/placeholder agent (not a real BYAN capability)' },
|
|
30
|
+
{ re: /^byan-byan-(v2|test)$/i, reason: 'versioned or test stub of BYAN itself' },
|
|
31
|
+
{ re: /^byan-(claude|codex)$/i, reason: 'platform name (not an agent role)' },
|
|
32
|
+
{ re: /^byan-skeptic$/i, reason: 'experimental agent not wired in menus' },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const DOC_ALLOWLIST = new Set([
|
|
36
|
+
'README.md',
|
|
37
|
+
'LICENSE',
|
|
38
|
+
'LICENSE.md',
|
|
39
|
+
'CHANGELOG.md',
|
|
40
|
+
'CONTRIBUTING.md',
|
|
41
|
+
'CODE_OF_CONDUCT.md',
|
|
42
|
+
'CLAUDE.md',
|
|
43
|
+
'GUIDE-UTILISATION.md',
|
|
44
|
+
'NOTES.md',
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
const DOC_PATTERNS = [
|
|
48
|
+
{ re: /^SPRINT\d+-.*\.md$/i, reason: 'sprint report' },
|
|
49
|
+
{ re: /^BYAN-V\d+(\.\d+)*-.*\.md$/i, reason: 'old version snapshot doc' },
|
|
50
|
+
{ re: /^CHANGELOG-v\d+(\.\d+)*\.md$/i, reason: 'versioned changelog (current is CHANGELOG.md)' },
|
|
51
|
+
{ re: /^MIGRATION-.*\.md$/i, reason: 'migration guide for an older version' },
|
|
52
|
+
{ re: /^README-BYAN-V\d+(\.\d+)*\.md$/i, reason: 'versioned README (current is README.md)' },
|
|
53
|
+
{ re: /^API-BYAN-V\d+(\.\d+)*\.md$/i, reason: 'versioned API doc' },
|
|
54
|
+
{ re: /^TEST-GUIDE-.*\.md$/i, reason: 'historical test guide' },
|
|
55
|
+
{ re: /-MANUAL-TEST-PLAN\.md$/i, reason: 'manual test plan' },
|
|
56
|
+
{ re: /-STATUS-REPORT\.md$/i, reason: 'status report' },
|
|
57
|
+
{ re: /-COMPLETE\.md$/i, reason: 'completion report' },
|
|
58
|
+
{ re: /-SUMMARY(\.txt|\.md)$/i, reason: 'summary artifact' },
|
|
59
|
+
{ re: /-DELIVERY-SUMMARY\.md$/i, reason: 'delivery summary' },
|
|
60
|
+
{ re: /^interview-summary-.*\.md$/i, reason: 'interview summary' },
|
|
61
|
+
{ re: /^fd-.*\.md$/i, reason: 'forge-persona session artifact' },
|
|
62
|
+
{ re: /^AGENT-LAUNCHER-DOC.*\.md$/i, reason: 'agent launcher legacy doc' },
|
|
63
|
+
{ re: /^ANNOUNCEMENT-GUIDE-.*\.md$/i, reason: 'announcement guide for older version' },
|
|
64
|
+
{ re: /^SESSION-RESUME-.*\.md$/i, reason: 'session resume artifact' },
|
|
65
|
+
{ re: /^BYAN-V2-.*\.md$/i, reason: 'v2-specific doc (keep only if still referenced)' },
|
|
66
|
+
{ re: /^BMAD-QUICK-REFERENCE.*\.md$/i, reason: 'legacy quick reference' },
|
|
67
|
+
{ re: /^100-PERCENT-.*\.md$/i, reason: 'milestone completion report' },
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
function readFrontmatter(filePath) {
|
|
71
|
+
try {
|
|
72
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
73
|
+
const m = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
74
|
+
if (!m) return {};
|
|
75
|
+
const out = {};
|
|
76
|
+
for (const line of m[1].split('\n')) {
|
|
77
|
+
const kv = line.match(/^(\w[\w-]*)\s*:\s*(.*)$/);
|
|
78
|
+
if (kv) out[kv[1]] = kv[2].replace(/^["']|["']$/g, '').trim();
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
} catch {
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function findStaleSkills(skillsDir) {
|
|
87
|
+
if (!fs.existsSync(skillsDir)) return [];
|
|
88
|
+
const found = [];
|
|
89
|
+
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
90
|
+
for (const e of entries) {
|
|
91
|
+
if (!e.isDirectory()) continue;
|
|
92
|
+
if (SKILL_ALLOWLIST.has(e.name)) continue;
|
|
93
|
+
|
|
94
|
+
const skillFile = path.join(skillsDir, e.name, 'SKILL.md');
|
|
95
|
+
if (!fs.existsSync(skillFile)) continue;
|
|
96
|
+
|
|
97
|
+
const reasons = [];
|
|
98
|
+
|
|
99
|
+
for (const { re, reason } of SKILL_PATTERNS) {
|
|
100
|
+
if (re.test(e.name)) reasons.push(reason);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const size = fs.statSync(skillFile).size;
|
|
104
|
+
if (size < 200) reasons.push(`tiny SKILL.md (${size} bytes — likely placeholder stub)`);
|
|
105
|
+
|
|
106
|
+
const fm = readFrontmatter(skillFile);
|
|
107
|
+
if (!fm.description || fm.description.length < 40) {
|
|
108
|
+
reasons.push('missing or too-short description in frontmatter');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (reasons.length > 0) {
|
|
112
|
+
found.push({
|
|
113
|
+
path: path.join(skillsDir, e.name),
|
|
114
|
+
name: e.name,
|
|
115
|
+
reasons,
|
|
116
|
+
size,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return found;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function findStaleDocs(projectRoot) {
|
|
124
|
+
if (!fs.existsSync(projectRoot)) return [];
|
|
125
|
+
const found = [];
|
|
126
|
+
const entries = fs.readdirSync(projectRoot, { withFileTypes: true });
|
|
127
|
+
for (const e of entries) {
|
|
128
|
+
if (!e.isFile()) continue;
|
|
129
|
+
if (!/\.(md|txt)$/i.test(e.name)) continue;
|
|
130
|
+
if (DOC_ALLOWLIST.has(e.name)) continue;
|
|
131
|
+
|
|
132
|
+
const reasons = [];
|
|
133
|
+
for (const { re, reason } of DOC_PATTERNS) {
|
|
134
|
+
if (re.test(e.name)) reasons.push(reason);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (reasons.length > 0) {
|
|
138
|
+
const p = path.join(projectRoot, e.name);
|
|
139
|
+
const size = fs.statSync(p).size;
|
|
140
|
+
found.push({ path: p, name: e.name, reasons, size });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return found;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
SKILL_ALLOWLIST,
|
|
148
|
+
SKILL_PATTERNS,
|
|
149
|
+
DOC_ALLOWLIST,
|
|
150
|
+
DOC_PATTERNS,
|
|
151
|
+
findStaleSkills,
|
|
152
|
+
findStaleDocs,
|
|
153
|
+
readFrontmatter,
|
|
154
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BYAN cleanup executor.
|
|
3
|
+
*
|
|
4
|
+
* Two removal modes :
|
|
5
|
+
* - archive(items, archiveDir) : move each item into a timestamped
|
|
6
|
+
* subdir of archiveDir (reversible).
|
|
7
|
+
* - deleteHard(items) : rm -rf each item (irreversible).
|
|
8
|
+
*
|
|
9
|
+
* Both return per-item results so the caller can print a clean summary.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
function timestampDir() {
|
|
16
|
+
const d = new Date();
|
|
17
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
18
|
+
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function archive(items, { archiveRoot, now = new Date() } = {}) {
|
|
22
|
+
if (!Array.isArray(items) || items.length === 0) return { moved: [], errors: [] };
|
|
23
|
+
|
|
24
|
+
const stamp =
|
|
25
|
+
now instanceof Date
|
|
26
|
+
? (() => {
|
|
27
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
28
|
+
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
29
|
+
})()
|
|
30
|
+
: String(now);
|
|
31
|
+
|
|
32
|
+
const baseRoot = archiveRoot || path.join('_byan-output', 'archive');
|
|
33
|
+
const dest = path.join(baseRoot, stamp);
|
|
34
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
35
|
+
|
|
36
|
+
const moved = [];
|
|
37
|
+
const errors = [];
|
|
38
|
+
for (const item of items) {
|
|
39
|
+
const src = item.path || item;
|
|
40
|
+
const name = path.basename(src);
|
|
41
|
+
const target = path.join(dest, name);
|
|
42
|
+
try {
|
|
43
|
+
fs.renameSync(src, target);
|
|
44
|
+
moved.push({ from: src, to: target });
|
|
45
|
+
} catch (err) {
|
|
46
|
+
errors.push({ path: src, error: err.message });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { moved, errors, archiveDir: dest };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function deleteHard(items) {
|
|
53
|
+
if (!Array.isArray(items) || items.length === 0) return { deleted: [], errors: [] };
|
|
54
|
+
const deleted = [];
|
|
55
|
+
const errors = [];
|
|
56
|
+
for (const item of items) {
|
|
57
|
+
const src = item.path || item;
|
|
58
|
+
try {
|
|
59
|
+
fs.rmSync(src, { recursive: true, force: true });
|
|
60
|
+
deleted.push(src);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
errors.push({ path: src, error: err.message });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { deleted, errors };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
archive,
|
|
70
|
+
deleteHard,
|
|
71
|
+
timestampDir,
|
|
72
|
+
};
|