create-byan-agent 2.25.0 → 2.26.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 +155 -0
- package/README.md +9 -12
- package/install/bin/create-byan-agent-v2.js +29 -169
- package/install/lib/agent-generator.js +5 -5
- package/install/lib/byan-web-integration.js +1 -1
- package/install/lib/claude-native-setup.js +1 -1
- package/install/lib/phase2-chat.js +3 -10
- package/install/lib/platforms/claude-code.js +2 -2
- package/install/lib/platforms/index.js +0 -2
- package/install/lib/project-agents-generator.js +3 -3
- package/install/lib/staging-consent.js +3 -3
- package/install/lib/subagent-generator.js +3 -3
- package/install/lib/yanstaller/agent-launcher.js +1 -27
- package/install/lib/yanstaller/detector.js +4 -4
- package/install/lib/yanstaller/installer.js +0 -2
- package/install/lib/yanstaller/interviewer.js +1 -1
- package/install/lib/yanstaller/platform-selector.js +1 -13
- package/install/package.json +1 -1
- package/install/src/byan-v2/context/session-state.js +2 -2
- package/install/src/byan-v2/index.js +1 -5
- package/install/src/byan-v2/orchestrator/generation-state.js +4 -4
- package/install/src/webui/api.js +0 -2
- package/install/src/webui/chat/bridge.js +1 -13
- package/install/src/webui/chat/cli-detector.js +0 -23
- package/install/src/webui/public/app.js +1 -3
- package/install/src/webui/public/chat.html +0 -2
- package/install/src/webui/public/chat.js +0 -1
- package/install/src/webui/public/index.html +2 -2
- package/install/templates/.claude/CLAUDE.md +13 -2
- package/install/templates/.claude/agents/bmad-byan.md +1 -1
- package/install/templates/.claude/hooks/autobench-stop-guard.js +286 -0
- package/install/templates/.claude/hooks/fact-check-absolutes.js +1 -61
- package/install/templates/.claude/hooks/fact-check-claims.js +69 -0
- package/install/templates/.claude/hooks/fd-response-check.js +37 -46
- package/install/templates/.claude/hooks/inject-soul.js +64 -25
- package/install/templates/.claude/hooks/leantime-fd-sync.js +216 -0
- package/install/templates/.claude/hooks/lib/autobench-config.json +81 -0
- package/install/templates/.claude/hooks/lib/autobench-fc-enrich.js +251 -0
- package/install/templates/.claude/hooks/lib/autobench-ledger-report.js +253 -0
- package/install/templates/.claude/hooks/lib/autobench-runtime.js +199 -0
- package/install/templates/.claude/hooks/lib/fact-check-core.js +69 -0
- package/install/templates/.claude/hooks/lib/transcript-read.js +137 -0
- package/install/templates/.claude/hooks/soul-memory-check.js +49 -25
- package/install/templates/.claude/hooks/soul-memory-triggers.js +27 -8
- package/install/templates/.claude/hooks/stage-to-byan.js +25 -7
- package/install/templates/.claude/hooks/strict-stop-guard.js +4 -16
- package/install/templates/.claude/rules/benchmark.md +251 -0
- package/install/templates/.claude/rules/byan-agents.md +0 -1
- package/install/templates/.claude/rules/byan-api.md +64 -0
- package/install/templates/.claude/rules/fact-check.md +1 -1
- package/install/templates/.claude/rules/strict-mode.md +10 -9
- package/install/templates/.claude/settings.json +12 -0
- package/install/templates/.claude/skills/byan-benchmark/SKILL.md +159 -0
- package/install/templates/.claude/skills/byan-byan/SKILL.md +73 -12
- package/install/templates/.claude/skills/byan-fact-check/SKILL.md +1 -1
- package/install/templates/.claude/skills/byan-hermes-dispatch/SKILL.md +5 -6
- package/install/templates/.claude/skills/byan-orchestrate/SKILL.md +11 -3
- package/install/templates/.claude/skills/byan-strict/SKILL.md +4 -1
- package/install/templates/.claude/workflows/INDEX.md +2 -1
- package/install/templates/.claude/workflows/byan-benchmark.js +328 -0
- package/install/templates/_byan/_config/agent-manifest.csv +1 -1
- package/install/templates/_byan/_config/autobench.yaml +510 -0
- package/install/templates/_byan/_config/strict-mode.yaml +9 -3
- package/install/templates/_byan/_config/workflow-manifest.csv +1 -0
- package/install/templates/_byan/agent/byan/byan.md +1 -3
- package/install/templates/_byan/agent/byan-flat/byan.md +1 -3
- package/install/templates/_byan/agent/byan-test/byan-test.md +2 -2
- package/install/templates/_byan/agent/byan-test-flat/byan-test.md +2 -2
- package/install/templates/_byan/agent/byan.optimized/byan.optimized.md +2 -2
- package/install/templates/_byan/agent/byan.optimized-v2/byan.optimized-v2.md +2 -2
- package/install/templates/_byan/agent/claude/claude.md +0 -2
- package/install/templates/_byan/agent/codex/codex.md +0 -2
- package/install/templates/_byan/agent/rachid/rachid.md +2 -10
- package/install/templates/_byan/agent/rachid-flat/rachid.md +2 -11
- package/install/templates/_byan/agent/turbo-whisper/turbo-whisper.md +2 -5
- package/install/templates/_byan/agent/turbo-whisper-integration/turbo-whisper-integration.md +5 -13
- package/install/templates/_byan/agent/yanstaller/yanstaller.md +2 -24
- package/install/templates/_byan/config.yaml +0 -1
- package/install/templates/_byan/mcp/byan-mcp-server/bin/byan-sync-rules.js +20 -4
- package/install/templates/_byan/mcp/byan-mcp-server/lib/advisory-autofeed.js +13 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/index-generator.js +1 -1
- package/install/templates/_byan/mcp/byan-mcp-server/lib/kanban.js +6 -3
- package/install/templates/_byan/mcp/byan-mcp-server/lib/leantime-fd-core.js +205 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/leantime-sync.js +415 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/precommit-gate.js +1 -1
- package/install/templates/_byan/mcp/byan-mcp-server/lib/strict-activation.js +1 -1
- package/install/templates/_byan/mcp/byan-mcp-server/lib/strict-mode.js +8 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/sync-rules.js +172 -23
- package/install/templates/_byan/mcp/byan-mcp-server/lib/workflows-generator.js +1 -0
- package/install/templates/_byan/mcp/byan-mcp-server/server.js +205 -82
- package/install/templates/_byan/worker/launchers/README.md +4 -24
- package/install/templates/_byan/worker/workers.md +0 -2
- package/install/templates/_byan/workflow/simple/bmb/byan-benchmark/workflow.md +86 -0
- package/install/templates/docs/leantime-integration.md +160 -0
- package/package.json +3 -7
- package/src/byan-v2/context/session-state.js +2 -2
- package/src/byan-v2/generation/mantra-validator.js +3 -3
- package/src/byan-v2/index.js +1 -5
- package/src/byan-v2/integration/voice-integration.js +1 -1
- package/src/byan-v2/orchestrator/generation-state.js +4 -4
- package/src/staging/staging.js +20 -6
- package/install/bin/build-copilot-stubs.js +0 -138
- package/install/lib/platforms/copilot-cli.js +0 -123
- package/install/lib/platforms/vscode.js +0 -51
- package/install/src/byan-v2/context/copilot-context.js +0 -79
- package/install/src/webui/chat/copilot-adapter.js +0 -68
- package/install/templates/.claude/agents/bmad-marc.md +0 -25
- package/install/templates/.claude/skills/byan-marc/SKILL.md +0 -20
- package/install/templates/.github/agents/bmad-agent-bmad-master.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmb-agent-builder.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmb-module-builder.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmb-workflow-builder.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-analyst.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-architect.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-dev.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-pm.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-quick-flow-solo-dev.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-quinn.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-sm.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-tech-writer.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-ux-designer.md +0 -16
- package/install/templates/.github/agents/bmad-agent-byan-test.md +0 -33
- package/install/templates/.github/agents/bmad-agent-byan-v2.md +0 -44
- package/install/templates/.github/agents/bmad-agent-byan.md +0 -1062
- package/install/templates/.github/agents/bmad-agent-carmack.md +0 -14
- package/install/templates/.github/agents/bmad-agent-cis-brainstorming-coach.md +0 -16
- package/install/templates/.github/agents/bmad-agent-cis-creative-problem-solver.md +0 -16
- package/install/templates/.github/agents/bmad-agent-cis-design-thinking-coach.md +0 -16
- package/install/templates/.github/agents/bmad-agent-cis-innovation-strategist.md +0 -16
- package/install/templates/.github/agents/bmad-agent-cis-presentation-master.md +0 -16
- package/install/templates/.github/agents/bmad-agent-cis-storyteller.md +0 -16
- package/install/templates/.github/agents/bmad-agent-claude.md +0 -49
- package/install/templates/.github/agents/bmad-agent-codex.md +0 -49
- package/install/templates/.github/agents/bmad-agent-drawio.md +0 -45
- package/install/templates/.github/agents/bmad-agent-fact-checker.md +0 -16
- package/install/templates/.github/agents/bmad-agent-forgeron.md +0 -15
- package/install/templates/.github/agents/bmad-agent-jimmy.md +0 -15
- package/install/templates/.github/agents/bmad-agent-marc.md +0 -49
- package/install/templates/.github/agents/bmad-agent-mike.md +0 -15
- package/install/templates/.github/agents/bmad-agent-patnote.md +0 -49
- package/install/templates/.github/agents/bmad-agent-rachid.md +0 -48
- package/install/templates/.github/agents/bmad-agent-skeptic.md +0 -16
- package/install/templates/.github/agents/bmad-agent-tao.md +0 -14
- package/install/templates/.github/agents/bmad-agent-tea-tea.md +0 -16
- package/install/templates/.github/agents/bmad-agent-test-dynamic.md +0 -22
- package/install/templates/.github/agents/bmad-agent-yanstaller-interview.md +0 -50
- package/install/templates/.github/agents/bmad-agent-yanstaller-phase2.md +0 -189
- package/install/templates/.github/agents/bmad-agent-yanstaller.md +0 -350
- package/install/templates/.github/agents/expert-merise-agile.md +0 -178
- package/install/templates/.github/agents/franck.md +0 -379
- package/install/templates/.github/agents/hermes.md +0 -575
- package/install/templates/.github/extensions/byan-staging/extension.mjs +0 -169
- package/install/templates/.github/extensions/byan-staging/package.json +0 -8
- package/install/templates/_byan/agent/marc/marc-soul.md +0 -47
- package/install/templates/_byan/agent/marc/marc-tao.md +0 -77
- package/install/templates/_byan/agent/marc/marc.md +0 -324
- package/install/templates/_byan/agent/marc-flat/marc.md +0 -387
- package/install/templates/_byan/mcp/byan-mcp-server/lib/copilot.js +0 -148
- package/install/templates/_byan/worker/launchers/launch-yanstaller-copilot.md +0 -173
- package/install/templates/workers/cost-optimizer.js +0 -169
- package/src/byan-v2/context/copilot-context.js +0 -79
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PostToolUse hook — mirror the BYAN FD lifecycle onto a Leantime board with no
|
|
3
|
+
// agent action. Fires on byan_fd_advance / byan_fd_update; reads the fd-state the
|
|
4
|
+
// tool echoed; drives lib/leantime-sync.js (ensure project, create tasks, move
|
|
5
|
+
// columns) per the pure decision core lib/leantime-fd-core.js.
|
|
6
|
+
//
|
|
7
|
+
// Best-effort and bounded by design:
|
|
8
|
+
// - exits 0 in every path (a sync issue does not block the turn; this hook
|
|
9
|
+
// does not use the exit-2 blocking path);
|
|
10
|
+
// - no-ops silently when the tool is not an FD tool, no FD is active, or
|
|
11
|
+
// Leantime is not configured (syncEnabled false);
|
|
12
|
+
// - it never WRITES fd-state.json (state-coupling); it reads the state the tool
|
|
13
|
+
// echoed, with a read-only fd-state.json fallback. The Leantime id map lives
|
|
14
|
+
// in the gitignored .byan-leantime/ sidecar;
|
|
15
|
+
// - a per-call timeout plus a hook wall-clock budget keep a slow Leantime from
|
|
16
|
+
// stalling the turn; a dropped call self-heals on the next phase event
|
|
17
|
+
// (reconcile-from-state, tracked by sidecar.moveFailed).
|
|
18
|
+
//
|
|
19
|
+
// CJS shell + ESM libs reached via dynamic import() (the drain-advisory.js bridge).
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const { pathToFileURL } = require('url');
|
|
24
|
+
|
|
25
|
+
const ROOT = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
26
|
+
const SIDECAR_DIR = path.join(ROOT, '.byan-leantime');
|
|
27
|
+
const MAP_PATH = path.join(SIDECAR_DIR, 'map.json');
|
|
28
|
+
const LOG_PATH = path.join(SIDECAR_DIR, 'sync.jsonl');
|
|
29
|
+
const FD_STATE_PATH = path.join(ROOT, '_byan-output', 'fd-state.json');
|
|
30
|
+
const PER_CALL_MS = 2500; // below the lib default (5000) so the hook bounds each call
|
|
31
|
+
const HOOK_BUDGET_MS = 8000; // between-stage advisory, checked at each stage boundary (not a hard ceiling); a move issues 2 RPCs so a late stage can overrun it, though no call hangs (each aborts at PER_CALL_MS)
|
|
32
|
+
|
|
33
|
+
// Reasons that deserve a one-line breadcrumb (a real wire/host issue, not "off").
|
|
34
|
+
const LOUD = new Set(['non_json', 'timeout', 'rpc_error']);
|
|
35
|
+
const isLoud = (reason) => typeof reason === 'string' && (LOUD.has(reason) || reason.startsWith('http_'));
|
|
36
|
+
|
|
37
|
+
function readStdin() {
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
if (process.stdin.isTTY) return resolve('');
|
|
40
|
+
let data = '';
|
|
41
|
+
process.stdin.on('data', (c) => (data += c));
|
|
42
|
+
process.stdin.on('end', () => resolve(data));
|
|
43
|
+
process.stdin.on('error', () => resolve(data));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function emit(additionalContext = '') {
|
|
48
|
+
process.stdout.write(
|
|
49
|
+
JSON.stringify({ hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext } }),
|
|
50
|
+
);
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function readMap() {
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(fs.readFileSync(MAP_PATH, 'utf8'));
|
|
57
|
+
} catch {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function writeMap(map) {
|
|
63
|
+
try {
|
|
64
|
+
fs.mkdirSync(SIDECAR_DIR, { recursive: true });
|
|
65
|
+
const tmp = `${MAP_PATH}.${process.pid}.tmp`;
|
|
66
|
+
fs.writeFileSync(tmp, JSON.stringify(map, null, 2));
|
|
67
|
+
fs.renameSync(tmp, MAP_PATH); // atomic swap so a crash mid-write keeps the old map
|
|
68
|
+
} catch {
|
|
69
|
+
// the sidecar is housekeeping; a write failure must not break the hook
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function logLine(entry) {
|
|
74
|
+
try {
|
|
75
|
+
fs.mkdirSync(SIDECAR_DIR, { recursive: true });
|
|
76
|
+
fs.appendFileSync(LOG_PATH, `${JSON.stringify(entry)}\n`);
|
|
77
|
+
} catch {
|
|
78
|
+
// the log is housekeeping; swallow
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
(async () => {
|
|
83
|
+
let payload = {};
|
|
84
|
+
try {
|
|
85
|
+
const raw = await readStdin();
|
|
86
|
+
payload = raw ? JSON.parse(raw) : {};
|
|
87
|
+
} catch {
|
|
88
|
+
return emit();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const toolName = payload.tool_name || payload.toolName || '';
|
|
93
|
+
const esm = (rel) => import(pathToFileURL(path.join(ROOT, rel)).href);
|
|
94
|
+
const core = await esm('_byan/mcp/byan-mcp-server/lib/leantime-fd-core.js');
|
|
95
|
+
if (!core.fdToolKind(toolName)) return emit(); // not an FD tool
|
|
96
|
+
|
|
97
|
+
// Read state from the tool's echoed result (state-coupling: no fd-state write).
|
|
98
|
+
const resp = payload.tool_response ?? payload.toolResponse ?? payload.response ?? null;
|
|
99
|
+
let state = core.parseFdState(resp);
|
|
100
|
+
if (!state) {
|
|
101
|
+
try {
|
|
102
|
+
state = JSON.parse(fs.readFileSync(FD_STATE_PATH, 'utf8'));
|
|
103
|
+
} catch {
|
|
104
|
+
state = null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (!state || typeof state.phase !== 'string') return emit();
|
|
108
|
+
|
|
109
|
+
const lt = await esm('_byan/mcp/byan-mcp-server/lib/leantime-sync.js');
|
|
110
|
+
if (!lt.syncEnabled()) return emit(); // Leantime not configured -> silent no-op
|
|
111
|
+
|
|
112
|
+
const fdId = state.fd_id || 'unknown';
|
|
113
|
+
const map = readMap();
|
|
114
|
+
const sidecar = map[fdId] || { tasks: {} };
|
|
115
|
+
sidecar.tasks = sidecar.tasks || {};
|
|
116
|
+
const assignUserConfigured = lt.assignUserId() != null;
|
|
117
|
+
|
|
118
|
+
const { intents, column } = core.decideActions({ toolName, state, sidecar, assignUserConfigured });
|
|
119
|
+
|
|
120
|
+
if (!intents || !intents.length) {
|
|
121
|
+
if (column && sidecar.lastColumn !== column) {
|
|
122
|
+
sidecar.lastColumn = column;
|
|
123
|
+
map[fdId] = sidecar;
|
|
124
|
+
writeMap(map);
|
|
125
|
+
}
|
|
126
|
+
return emit();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const opts = { timeoutMs: PER_CALL_MS };
|
|
130
|
+
const deadline = Date.now() + HOOK_BUDGET_MS;
|
|
131
|
+
const timeLeft = () => deadline - Date.now();
|
|
132
|
+
let firstLoud = null;
|
|
133
|
+
let moveFailed = false;
|
|
134
|
+
|
|
135
|
+
const record = (event, target, r) => {
|
|
136
|
+
const ok = !!(r && r.ok);
|
|
137
|
+
const synced = !!(r && r.synced);
|
|
138
|
+
logLine({ ts: new Date().toISOString(), fd_id: fdId, phase: state.phase, event, target, ok, synced, reason: (r && r.reason) || null });
|
|
139
|
+
if (!synced && isLoud(r && r.reason) && !firstLoud) firstLoud = r.reason;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// 1. Ensure the project (sequential — every later call needs the projectId).
|
|
143
|
+
const ensureIntent = intents.find((i) => i.op === 'project_ensure');
|
|
144
|
+
if (ensureIntent && !sidecar.projectId) {
|
|
145
|
+
const r = await lt.ensureProject({ name: ensureIntent.name, slug: ensureIntent.slug, details: ensureIntent.details }, opts);
|
|
146
|
+
record('project_ensure', ensureIntent.name, r);
|
|
147
|
+
if (r.ok && r.id) {
|
|
148
|
+
sidecar.projectId = r.id;
|
|
149
|
+
map[fdId] = sidecar;
|
|
150
|
+
writeMap(map); // persist immediately so a crash cannot re-create the project
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 2. Make the project visible to the configured human (best-effort).
|
|
155
|
+
if (intents.some((i) => i.op === 'assign_user') && sidecar.projectId && timeLeft() > 0) {
|
|
156
|
+
const r = await lt.assignUserToProject({ projectId: sidecar.projectId }, opts);
|
|
157
|
+
record('assign_user', sidecar.projectId, r);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 3. Create tasks (parallel, bounded by the wall-clock budget).
|
|
161
|
+
const createIntents = intents.filter((i) => i.op === 'task_create');
|
|
162
|
+
if (createIntents.length && sidecar.projectId && timeLeft() > 0) {
|
|
163
|
+
const results = await Promise.allSettled(
|
|
164
|
+
createIntents.map((i) =>
|
|
165
|
+
lt.createTask({ projectId: sidecar.projectId, headline: i.headline }, opts).then((r) => ({ i, r })),
|
|
166
|
+
),
|
|
167
|
+
);
|
|
168
|
+
for (const s of results) {
|
|
169
|
+
if (s.status === 'fulfilled') {
|
|
170
|
+
const { i, r } = s.value;
|
|
171
|
+
record('task_create', i.backlogId, r);
|
|
172
|
+
if (r.ok && r.id) sidecar.tasks[i.backlogId] = r.id;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
map[fdId] = sidecar;
|
|
176
|
+
writeMap(map);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 4. Move tasks to the current column (parallel, bounded).
|
|
180
|
+
const moveIntents = intents.filter((i) => i.op === 'task_move');
|
|
181
|
+
if (moveIntents.length && sidecar.projectId && timeLeft() > 0) {
|
|
182
|
+
const results = await Promise.allSettled(
|
|
183
|
+
moveIntents.map((i) => {
|
|
184
|
+
const taskId = sidecar.tasks[i.backlogId];
|
|
185
|
+
if (!taskId) return Promise.resolve({ i, r: { ok: false, synced: false, reason: 'no_task_id' } });
|
|
186
|
+
return lt.moveTask({ taskId, projectId: sidecar.projectId, column: i.column }, opts).then((r) => ({ i, r }));
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
for (const s of results) {
|
|
190
|
+
if (s.status === 'fulfilled') {
|
|
191
|
+
const { i, r } = s.value;
|
|
192
|
+
record('task_move', i.backlogId, r);
|
|
193
|
+
if (!(r && r.synced)) moveFailed = true;
|
|
194
|
+
} else {
|
|
195
|
+
moveFailed = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} else if (moveIntents.length) {
|
|
199
|
+
// could not run the moves this fire (budget/no project) -> retry next event
|
|
200
|
+
moveFailed = true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (column) sidecar.lastColumn = column;
|
|
204
|
+
sidecar.moveFailed = moveFailed;
|
|
205
|
+
sidecar.updatedAt = new Date().toISOString();
|
|
206
|
+
map[fdId] = sidecar;
|
|
207
|
+
writeMap(map);
|
|
208
|
+
|
|
209
|
+
if (firstLoud) {
|
|
210
|
+
return emit(`Leantime sync: ${firstLoud} on ${state.phase} (board may lag; retried next phase). Check LEANTIME_API_URL / token.`);
|
|
211
|
+
}
|
|
212
|
+
return emit();
|
|
213
|
+
} catch {
|
|
214
|
+
return emit(); // any failure degrades silently — the sync is housekeeping
|
|
215
|
+
}
|
|
216
|
+
})();
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_generated_by": "byan-sync-rules",
|
|
3
|
+
"_note": "Runtime subset read by autobench-stop-guard.js. Edit _byan/_config/autobench.yaml and regenerate; do not hand-edit. Regexes are {source, flags} pairs reconstructed into RegExp at load time.",
|
|
4
|
+
"version": 1,
|
|
5
|
+
"marker_patterns": {
|
|
6
|
+
"any": {
|
|
7
|
+
"source": "<!--\\s*BYAN-BENCH:(done|skip)\\b",
|
|
8
|
+
"flags": "i"
|
|
9
|
+
},
|
|
10
|
+
"done": {
|
|
11
|
+
"source": "<!--\\s*BYAN-BENCH:done\\b",
|
|
12
|
+
"flags": "i"
|
|
13
|
+
},
|
|
14
|
+
"skip": {
|
|
15
|
+
"source": "<!--\\s*BYAN-BENCH:skip\\b",
|
|
16
|
+
"flags": "i"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"marker_fields": {
|
|
20
|
+
"g1": {
|
|
21
|
+
"source": "g1=(\\d+)",
|
|
22
|
+
"flags": "i"
|
|
23
|
+
},
|
|
24
|
+
"g2": {
|
|
25
|
+
"source": "g2=(\\d+)",
|
|
26
|
+
"flags": "i"
|
|
27
|
+
},
|
|
28
|
+
"scope": {
|
|
29
|
+
"source": "scope=(internal|external)",
|
|
30
|
+
"flags": "i"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"never_list": [
|
|
34
|
+
{
|
|
35
|
+
"source": "\\b(yes/no|y/n|confirm|proceed\\?|continue\\?|ok\\?|on continue\\?|je confirme)\\b",
|
|
36
|
+
"flags": "i"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"source": "\\b(delete|drop|rm -rf|overwrite|force push|reset --hard|supprimer|écraser)\\b",
|
|
40
|
+
"flags": "i"
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"choice_language": [
|
|
44
|
+
{
|
|
45
|
+
"source": "\\boption\\s*[1-3a-c]\\b",
|
|
46
|
+
"flags": "ig",
|
|
47
|
+
"min_matches": 2
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"source": "^[ \\t]*[-*][ \\t]+[A-Z][^\\n]{0,80}(:|[ \\t]-[ \\t])",
|
|
51
|
+
"flags": "gm",
|
|
52
|
+
"min_matches": 2
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"source": "\\b(should I|veux-tu que je|do you want me to|préfères-tu|which (one|approach|option)|A or B|soit .* soit )\\b",
|
|
56
|
+
"flags": "i"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"source": "\\b(pros?|cons?|trade-?offs?|avantages?|inconvénients?)\\b",
|
|
60
|
+
"flags": "i",
|
|
61
|
+
"requires_candidates": 2
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"candidate_token": {
|
|
65
|
+
"source": "\\b(option|approach|approche|alternative|choix|solution|stack|library|librairie|vendor|standard)s?\\b",
|
|
66
|
+
"flags": "ig"
|
|
67
|
+
},
|
|
68
|
+
"escape_hatch": {
|
|
69
|
+
"session_flag": ".byan-autobench/off",
|
|
70
|
+
"disabled": false
|
|
71
|
+
},
|
|
72
|
+
"enforcement": {
|
|
73
|
+
"armed": false
|
|
74
|
+
},
|
|
75
|
+
"ledger": {
|
|
76
|
+
"path": "_byan-output/benchmark-ledger.jsonl"
|
|
77
|
+
},
|
|
78
|
+
"banners": {
|
|
79
|
+
"stop_block": "Auto-benchmark: you are presenting a choice between options but emitted no BYAN-BENCH marker. Re-present the fork as the compact 1-table benchmark (Option | criteria | Niv + best-first reco), then emit <!-- BYAN-BENCH:done g1=.. g2=.. scope=.. -->. If this is a confirm/destructive/obvious-default prompt, emit <!-- BYAN-BENCH:skip reason=.. -->. To disable for this session: touch .byan-autobench/off."
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// BYAN-only opt-in evidence enrichment for the byan-benchmark matrix (C5d).
|
|
2
|
+
//
|
|
3
|
+
// The native workflow (.claude/workflows/byan-benchmark.js) returns a DATA
|
|
4
|
+
// matrix where each cell carries a self-graded evidence `level` (L1..L5) and an
|
|
5
|
+
// `unverified` flag. That self-grade is the model judging its own claim. Inside
|
|
6
|
+
// ~/BYAN the orchestrating skill can do better: it can call the byan_fc_check
|
|
7
|
+
// MCP tool per factual cell and stamp an AUDITED evidence level onto the cell,
|
|
8
|
+
// turning the Niv column into a fact-checked authority rather than a self-grade.
|
|
9
|
+
//
|
|
10
|
+
// This module is the pure, testable core of that wiring. It does NOT know about
|
|
11
|
+
// MCP transport: the caller injects an async `check(text) -> { level, score, ...}`
|
|
12
|
+
// function (in BYAN, a thin adapter over mcp__byan__byan_fc_check; in tests, a
|
|
13
|
+
// mock). Without an injected checker the matrix is returned unchanged, which is
|
|
14
|
+
// why the layer is OPT-IN and BYAN-only by construction: a platform that cannot
|
|
15
|
+
// reach byan_fc_check simply does not pass a checker and gets the self-graded
|
|
16
|
+
// matrix back, untouched.
|
|
17
|
+
//
|
|
18
|
+
// Strict-domain floors (mirrors .claude/rules/fact-check.md and the engine's
|
|
19
|
+
// STRICT_FLOORS): a security/performance claim must reach L2, a compliance claim
|
|
20
|
+
// L1, or the cell stays flagged [UNVERIFIED] no matter what the checker returned.
|
|
21
|
+
// Enrichment can only RAISE authority or flag a shortfall; it never silently
|
|
22
|
+
// upgrades a cell past its domain floor.
|
|
23
|
+
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
// Strict-domain minimum evidence levels. Numeric so floor comparison is a plain
|
|
27
|
+
// `<=` (L1 is the strongest -> the smallest number). Kept in sync with the
|
|
28
|
+
// engine's STRICT_FLOORS map and the fact-check rule doc.
|
|
29
|
+
const STRICT_FLOORS = { security: 2, performance: 2, compliance: 1 };
|
|
30
|
+
|
|
31
|
+
// Default heuristic: which cells are "hard claims" worth fact-checking. A cell
|
|
32
|
+
// is a hard claim when it sits in a strict domain (every cell is then a claim
|
|
33
|
+
// because the floor applies) OR its verdict text uses an absolute / superlative
|
|
34
|
+
// the fact-check auto-detection also keys on. Low-stakes internal cells with a
|
|
35
|
+
// hedged verdict are skipped to keep latency down (anti-bloat, C4).
|
|
36
|
+
const ABSOLUTE_RE =
|
|
37
|
+
/\b(always|never|toujours|jamais|forcement|obviously|guaranteed|fastest|slowest|best|worst|optimal|superior|plus rapide|le plus|mieux|meilleur|fully|completely|zero|100%)\b/i;
|
|
38
|
+
|
|
39
|
+
const STRICT_DOMAINS = Object.keys(STRICT_FLOORS);
|
|
40
|
+
|
|
41
|
+
// Parse an "L3" / "l2" / 3 style level into the 1..5 integer, or null if absent.
|
|
42
|
+
function parseLevel(level) {
|
|
43
|
+
if (typeof level === 'number' && Number.isFinite(level)) {
|
|
44
|
+
return level >= 1 && level <= 5 ? Math.round(level) : null;
|
|
45
|
+
}
|
|
46
|
+
if (typeof level === 'string') {
|
|
47
|
+
const m = level.match(/L?\s*([1-5])\b/i);
|
|
48
|
+
if (m) return Number(m[1]);
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Render a numeric level back to the canonical "L{n}" the matrix uses.
|
|
54
|
+
function levelLabel(n) {
|
|
55
|
+
return n == null ? null : `L${n}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Decide whether a cell is a hard claim worth an fc_check call.
|
|
59
|
+
// - any cell in a strict domain is a hard claim (the floor must be enforced);
|
|
60
|
+
// - otherwise, a cell whose verdict uses an absolute/superlative is a claim;
|
|
61
|
+
// - an explicit isHardClaim flag on the cell forces inclusion.
|
|
62
|
+
// Returns false for hedged, low-stakes internal cells so enrichment stays cheap.
|
|
63
|
+
function isHardClaim(cell, domain) {
|
|
64
|
+
if (!cell) return false;
|
|
65
|
+
if (cell.isHardClaim === true) return true;
|
|
66
|
+
if (STRICT_DOMAINS.includes(domain)) return true;
|
|
67
|
+
const verdict = typeof cell.verdict === 'string' ? cell.verdict : '';
|
|
68
|
+
const claim = typeof cell.claim === 'string' ? cell.claim : '';
|
|
69
|
+
return ABSOLUTE_RE.test(verdict) || ABSOLUTE_RE.test(claim);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Build the text the checker scores for a cell. Prefer an explicit cell.claim
|
|
73
|
+
// (the factual basis the SOURCE leaf wrote); fall back to the qualitative
|
|
74
|
+
// verdict joined with the criterion so the checker has a self-contained claim.
|
|
75
|
+
function cellClaimText(cell) {
|
|
76
|
+
if (cell && typeof cell.claim === 'string' && cell.claim.trim()) return cell.claim.trim();
|
|
77
|
+
const criterion = cell && cell.criterion ? String(cell.criterion) : '';
|
|
78
|
+
const verdict = cell && cell.verdict ? String(cell.verdict) : '';
|
|
79
|
+
return [criterion, verdict].filter(Boolean).join(': ').trim();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Apply a single fc_check result to a cell. PURE given the result: returns a NEW
|
|
83
|
+
// cell object (never mutates the input), records the audited level/score, the
|
|
84
|
+
// fact-check status and assertionType, and re-evaluates the strict-domain floor.
|
|
85
|
+
function applyCheckToCell(cell, result, domain) {
|
|
86
|
+
const checkedLevel = result ? parseLevel(result.level) : null;
|
|
87
|
+
const floor = STRICT_FLOORS[domain] || null;
|
|
88
|
+
|
|
89
|
+
// Below the domain floor (or unscored) -> the cell stays unverified regardless
|
|
90
|
+
// of the prior self-grade. A claim that cannot be sourced to its floor is not
|
|
91
|
+
// trustworthy in a strict domain.
|
|
92
|
+
const belowFloor =
|
|
93
|
+
floor != null && (checkedLevel == null || checkedLevel > floor);
|
|
94
|
+
const blocked = result && result.status === 'BLOCKED';
|
|
95
|
+
|
|
96
|
+
const out = Object.assign({}, cell);
|
|
97
|
+
out.fcChecked = true;
|
|
98
|
+
if (result) {
|
|
99
|
+
out.fcStatus = result.status;
|
|
100
|
+
out.fcAssertionType = result.assertionType;
|
|
101
|
+
if (typeof result.score === 'number') out.fcScore = result.score;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (checkedLevel != null) {
|
|
105
|
+
out.level = levelLabel(checkedLevel);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (belowFloor || blocked) {
|
|
109
|
+
out.unverified = true;
|
|
110
|
+
out.fcFloor = floor != null ? `L${floor}` : null;
|
|
111
|
+
out.fcBelowFloor = true;
|
|
112
|
+
} else if (checkedLevel != null) {
|
|
113
|
+
// A genuine audited level at or above the floor clears the unverified flag
|
|
114
|
+
// ONLY when the checker actually classified it as a CLAIM/FACT (not a bare
|
|
115
|
+
// HYPOTHESIS/OPINION). Otherwise leave the flag as the engine set it.
|
|
116
|
+
if (result && (result.status === 'CLAIM' || result.status === 'VERIFIED')) {
|
|
117
|
+
out.unverified = false;
|
|
118
|
+
}
|
|
119
|
+
out.fcBelowFloor = false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Enrich a benchmark matrix in place-free fashion (returns a NEW matrix).
|
|
127
|
+
*
|
|
128
|
+
* @param {object} params
|
|
129
|
+
* @param {object} params.benchmark The DATA object the workflow returned
|
|
130
|
+
* ({ matrix, domain, scope, ... }).
|
|
131
|
+
* @param {(text: string) => Promise<object>} [params.check]
|
|
132
|
+
* Async checker; in BYAN a thin adapter over
|
|
133
|
+
* mcp__byan__byan_fc_check. If omitted, the
|
|
134
|
+
* matrix is returned unchanged (opt-in).
|
|
135
|
+
* @param {boolean} [params.enabled=true] Master opt-in switch.
|
|
136
|
+
* @param {(cell, domain) => boolean} [params.claimSelector]
|
|
137
|
+
* Override the hard-claim heuristic.
|
|
138
|
+
* @returns {Promise<object>} A new benchmark object with enriched matrix and an
|
|
139
|
+
* `enrichment` report ({ enabled, checked, raised,
|
|
140
|
+
* flagged, skipped }).
|
|
141
|
+
*/
|
|
142
|
+
async function enrichMatrix(params) {
|
|
143
|
+
const {
|
|
144
|
+
benchmark,
|
|
145
|
+
check,
|
|
146
|
+
enabled = true,
|
|
147
|
+
claimSelector = isHardClaim,
|
|
148
|
+
} = params || {};
|
|
149
|
+
|
|
150
|
+
if (!benchmark || typeof benchmark !== 'object') {
|
|
151
|
+
throw new Error('enrichMatrix requires a benchmark object');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const domain = benchmark.domain || 'general';
|
|
155
|
+
const matrix = Array.isArray(benchmark.matrix) ? benchmark.matrix : [];
|
|
156
|
+
|
|
157
|
+
// Opt-in guard: no checker, disabled, or a degenerate (un-tabled) benchmark ->
|
|
158
|
+
// return the input untouched with an honest report. This is the BYAN-only
|
|
159
|
+
// gate: other platforms never inject a checker, so they get this branch.
|
|
160
|
+
if (!enabled || typeof check !== 'function' || benchmark.degenerate) {
|
|
161
|
+
return Object.assign({}, benchmark, {
|
|
162
|
+
enrichment: {
|
|
163
|
+
enabled: false,
|
|
164
|
+
reason: !enabled
|
|
165
|
+
? 'disabled'
|
|
166
|
+
: typeof check !== 'function'
|
|
167
|
+
? 'no-checker'
|
|
168
|
+
: 'degenerate',
|
|
169
|
+
checked: 0,
|
|
170
|
+
raised: 0,
|
|
171
|
+
flagged: 0,
|
|
172
|
+
skipped: countCells(matrix),
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let checked = 0;
|
|
178
|
+
let raised = 0;
|
|
179
|
+
let flagged = 0;
|
|
180
|
+
let skipped = 0;
|
|
181
|
+
|
|
182
|
+
const newMatrix = [];
|
|
183
|
+
for (const row of matrix) {
|
|
184
|
+
const cells = Array.isArray(row && row.cells) ? row.cells : [];
|
|
185
|
+
const newCells = [];
|
|
186
|
+
for (const cell of cells) {
|
|
187
|
+
if (!claimSelector(cell, domain)) {
|
|
188
|
+
skipped += 1;
|
|
189
|
+
newCells.push(cell);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const text = cellClaimText(cell);
|
|
194
|
+
if (!text) {
|
|
195
|
+
skipped += 1;
|
|
196
|
+
newCells.push(cell);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let result = null;
|
|
201
|
+
try {
|
|
202
|
+
result = await check(text);
|
|
203
|
+
} catch {
|
|
204
|
+
// A checker failure must never break the benchmark: fall back to the
|
|
205
|
+
// self-graded cell, flagged so the gap is auditable, and keep going.
|
|
206
|
+
const fallback = Object.assign({}, cell, { fcChecked: false, fcError: true });
|
|
207
|
+
newCells.push(fallback);
|
|
208
|
+
skipped += 1;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const beforeLevel = parseLevel(cell && cell.level);
|
|
213
|
+
const enriched = applyCheckToCell(cell, result, domain);
|
|
214
|
+
checked += 1;
|
|
215
|
+
|
|
216
|
+
const afterLevel = parseLevel(enriched.level);
|
|
217
|
+
if (afterLevel != null && (beforeLevel == null || afterLevel < beforeLevel)) {
|
|
218
|
+
// A smaller number is a STRONGER level -> authority was raised.
|
|
219
|
+
raised += 1;
|
|
220
|
+
}
|
|
221
|
+
if (enriched.fcBelowFloor === true) flagged += 1;
|
|
222
|
+
|
|
223
|
+
newCells.push(enriched);
|
|
224
|
+
}
|
|
225
|
+
newMatrix.push(Object.assign({}, row, { cells: newCells }));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return Object.assign({}, benchmark, {
|
|
229
|
+
matrix: newMatrix,
|
|
230
|
+
enrichment: { enabled: true, checked, raised, flagged, skipped },
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function countCells(matrix) {
|
|
235
|
+
if (!Array.isArray(matrix)) return 0;
|
|
236
|
+
return matrix.reduce(
|
|
237
|
+
(n, row) => n + (Array.isArray(row && row.cells) ? row.cells.length : 0),
|
|
238
|
+
0
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
module.exports = {
|
|
243
|
+
STRICT_FLOORS,
|
|
244
|
+
parseLevel,
|
|
245
|
+
levelLabel,
|
|
246
|
+
isHardClaim,
|
|
247
|
+
cellClaimText,
|
|
248
|
+
applyCheckToCell,
|
|
249
|
+
enrichMatrix,
|
|
250
|
+
countCells,
|
|
251
|
+
};
|