create-byan-agent 2.23.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 +230 -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 +2 -6
- 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/drain-advisory.js +85 -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/failure-detector.js +18 -4
- 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 +16 -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-insight/SKILL.md +56 -0
- 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/.claude/workflows/check-implementation-readiness.js +1 -1
- 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/core/activation/soul-activation.md +3 -3
- package/install/templates/_byan/mcp/byan-mcp-server/bin/byan-insight-digest.js +31 -0
- 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 +96 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/index-generator.js +1 -1
- package/install/templates/_byan/mcp/byan-mcp-server/lib/insight-harvest.js +220 -0
- 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/outcome-buffer.js +64 -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 +262 -81
- package/install/templates/_byan/worker/launchers/README.md +4 -24
- package/install/templates/_byan/worker/workers.md +8 -9
- package/install/templates/_byan/workflow/simple/bmb/byan-benchmark/workflow.md +86 -0
- package/install/templates/_byan/workflow/simple/byan/feature-workflow.md +2 -2
- 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/loadbalancer/loadbalancer.js +1 -1
- 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
- package/src/core/dispatcher/execution-router.js +0 -66
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// BYAN Auto-Benchmark miss-ledger reader / aggregator (C5e).
|
|
3
|
+
//
|
|
4
|
+
// The Stop hook (autobench-stop-guard.js) appends ONE JSONL line per invocation
|
|
5
|
+
// to _byan-output/benchmark-ledger.jsonl. Each line is an audit record of one
|
|
6
|
+
// end-of-turn decision: did the agent benchmark a fork it should have, or did it
|
|
7
|
+
// MISS. This module reads that trail and aggregates it into the fires / misses /
|
|
8
|
+
// miss-rate summary required by the acceptance criteria, plus a small CLI-ish
|
|
9
|
+
// `main()` so a human (or CI) can run a one-shot report.
|
|
10
|
+
//
|
|
11
|
+
// Event vocabulary is OWNED by the hook; this reader treats it as the contract:
|
|
12
|
+
// fired-block -> a MISS (the agent offered a fork without a
|
|
13
|
+
// benchmark marker; the hook forced a regen).
|
|
14
|
+
// satisfied-marker -> a HIT (a real benchmark was presented).
|
|
15
|
+
// satisfied-skip -> a deliberate skip (fork considered, not tabled).
|
|
16
|
+
// satisfied-never -> exempt (y/n confirm / destructive prompt).
|
|
17
|
+
// satisfied-escape -> exempt (escape-hatch active).
|
|
18
|
+
// satisfied-already-blocked -> the forced regen pass (block-once accounting).
|
|
19
|
+
// no-choice -> no fork was present (the common case).
|
|
20
|
+
// Anything else is bucketed under `unknown` so a future event type is surfaced,
|
|
21
|
+
// not silently dropped.
|
|
22
|
+
//
|
|
23
|
+
// Pure read-only: this module NEVER writes the ledger. It reads what the hook
|
|
24
|
+
// wrote. Robust to a partially-written / corrupt JSONL file: a malformed line is
|
|
25
|
+
// counted under `malformed` and skipped, never thrown.
|
|
26
|
+
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
const fs = require('fs');
|
|
30
|
+
const path = require('path');
|
|
31
|
+
|
|
32
|
+
// Events that count as a genuine MISS the agent must fix. Kept narrow on
|
|
33
|
+
// purpose: only `fired-block` is a real failure. Everything else is either a
|
|
34
|
+
// hit, an exempt case, or accounting.
|
|
35
|
+
const MISS_EVENTS = new Set(['fired-block']);
|
|
36
|
+
|
|
37
|
+
// Events that count as a real benchmark HIT (a fork was tabled).
|
|
38
|
+
const HIT_EVENTS = new Set(['satisfied-marker']);
|
|
39
|
+
|
|
40
|
+
// Events that mean "a fork was considered and deliberately not tabled".
|
|
41
|
+
const SKIP_EVENTS = new Set(['satisfied-skip']);
|
|
42
|
+
|
|
43
|
+
// Exempt / accounting events: present in the trail but neither a miss nor a hit.
|
|
44
|
+
const EXEMPT_EVENTS = new Set([
|
|
45
|
+
'satisfied-never',
|
|
46
|
+
'satisfied-escape',
|
|
47
|
+
'satisfied-already-blocked',
|
|
48
|
+
'no-choice',
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
function projectRoot() {
|
|
52
|
+
return process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function defaultLedgerPath() {
|
|
56
|
+
return path.join(projectRoot(), '_byan-output', 'benchmark-ledger.jsonl');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Read and parse the ledger file into an array of entries.
|
|
61
|
+
* Returns { entries, malformed, missing } where `malformed` counts unparseable
|
|
62
|
+
* lines and `missing` is true when the file does not exist (a fresh repo where
|
|
63
|
+
* the hook never fired -> not an error, just an empty trail).
|
|
64
|
+
*
|
|
65
|
+
* @param {string} [filePath] absolute path; defaults to the project ledger.
|
|
66
|
+
*/
|
|
67
|
+
function readLedger(filePath) {
|
|
68
|
+
const p = filePath || defaultLedgerPath();
|
|
69
|
+
let raw;
|
|
70
|
+
try {
|
|
71
|
+
if (!fs.existsSync(p)) return { entries: [], malformed: 0, missing: true };
|
|
72
|
+
raw = fs.readFileSync(p, 'utf8');
|
|
73
|
+
} catch {
|
|
74
|
+
// An unreadable ledger is treated as empty rather than thrown: a reporting
|
|
75
|
+
// tool must never crash the caller over a permissions blip.
|
|
76
|
+
return { entries: [], malformed: 0, missing: true };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const entries = [];
|
|
80
|
+
let malformed = 0;
|
|
81
|
+
for (const line of raw.split('\n')) {
|
|
82
|
+
const trimmed = line.trim();
|
|
83
|
+
if (!trimmed) continue;
|
|
84
|
+
try {
|
|
85
|
+
const obj = JSON.parse(trimmed);
|
|
86
|
+
if (obj && typeof obj === 'object') entries.push(obj);
|
|
87
|
+
else malformed += 1;
|
|
88
|
+
} catch {
|
|
89
|
+
malformed += 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { entries, malformed, missing: false };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Aggregate a list of ledger entries into a summary.
|
|
97
|
+
*
|
|
98
|
+
* @param {Array<object>} entries
|
|
99
|
+
* @returns {object} {
|
|
100
|
+
* total, fires, misses, hits, skips, exempt, unknown, missRate,
|
|
101
|
+
* byEvent:{event:count}, byScope:{internal,external,unknown},
|
|
102
|
+
* gates:{g1Total,g2Total,countWithGates}
|
|
103
|
+
* }
|
|
104
|
+
* - fires = decisions where a fork was present and acted on (hits + misses).
|
|
105
|
+
* - misses = fired-block events (the agent must fix these).
|
|
106
|
+
* - missRate = misses / fires (0 when no fork was ever present).
|
|
107
|
+
*/
|
|
108
|
+
function aggregate(entries) {
|
|
109
|
+
const list = Array.isArray(entries) ? entries : [];
|
|
110
|
+
|
|
111
|
+
const byEvent = {};
|
|
112
|
+
const byScope = { internal: 0, external: 0, unknown: 0 };
|
|
113
|
+
let misses = 0;
|
|
114
|
+
let hits = 0;
|
|
115
|
+
let skips = 0;
|
|
116
|
+
let exempt = 0;
|
|
117
|
+
let unknown = 0;
|
|
118
|
+
let g1Total = 0;
|
|
119
|
+
let g2Total = 0;
|
|
120
|
+
let countWithGates = 0;
|
|
121
|
+
|
|
122
|
+
for (const e of list) {
|
|
123
|
+
const event = e && typeof e.event === 'string' ? e.event : 'unknown';
|
|
124
|
+
byEvent[event] = (byEvent[event] || 0) + 1;
|
|
125
|
+
|
|
126
|
+
if (MISS_EVENTS.has(event)) misses += 1;
|
|
127
|
+
else if (HIT_EVENTS.has(event)) hits += 1;
|
|
128
|
+
else if (SKIP_EVENTS.has(event)) skips += 1;
|
|
129
|
+
else if (EXEMPT_EVENTS.has(event)) exempt += 1;
|
|
130
|
+
else unknown += 1;
|
|
131
|
+
|
|
132
|
+
// Scope tally (only the marker-bearing hits/skips carry a scope field).
|
|
133
|
+
const scope = e && e.scope;
|
|
134
|
+
if (scope === 'internal' || scope === 'external') byScope[scope] += 1;
|
|
135
|
+
else if (scope != null) byScope.unknown += 1;
|
|
136
|
+
|
|
137
|
+
// Gate totals: only the satisfied-marker entries carry g1/g2 (the marker
|
|
138
|
+
// fields the hook parsed). Average gate counts hint at fork complexity.
|
|
139
|
+
if (typeof e.g1 === 'number' || typeof e.g2 === 'number') {
|
|
140
|
+
if (typeof e.g1 === 'number') g1Total += e.g1;
|
|
141
|
+
if (typeof e.g2 === 'number') g2Total += e.g2;
|
|
142
|
+
countWithGates += 1;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// A "fire" is a turn where a fork was genuinely present and the doctrine
|
|
147
|
+
// applied: a HIT (tabled) or a MISS (should have, didn't). Skips, exempts and
|
|
148
|
+
// no-choice turns are NOT fires, so the miss-rate is not diluted by the vast
|
|
149
|
+
// majority of turns that have no fork at all.
|
|
150
|
+
const fires = hits + misses;
|
|
151
|
+
const missRate = fires > 0 ? misses / fires : 0;
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
total: list.length,
|
|
155
|
+
fires,
|
|
156
|
+
misses,
|
|
157
|
+
hits,
|
|
158
|
+
skips,
|
|
159
|
+
exempt,
|
|
160
|
+
unknown,
|
|
161
|
+
missRate,
|
|
162
|
+
byEvent,
|
|
163
|
+
byScope,
|
|
164
|
+
gates: {
|
|
165
|
+
g1Total,
|
|
166
|
+
g2Total,
|
|
167
|
+
countWithGates,
|
|
168
|
+
g1Avg: countWithGates > 0 ? g1Total / countWithGates : 0,
|
|
169
|
+
g2Avg: countWithGates > 0 ? g2Total / countWithGates : 0,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Convenience: read + aggregate in one call.
|
|
176
|
+
* @param {string} [filePath]
|
|
177
|
+
* @returns {object} aggregate(...) plus { malformed, missing, path }.
|
|
178
|
+
*/
|
|
179
|
+
function report(filePath) {
|
|
180
|
+
const p = filePath || defaultLedgerPath();
|
|
181
|
+
const { entries, malformed, missing } = readLedger(p);
|
|
182
|
+
return Object.assign(aggregate(entries), { malformed, missing, path: p });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Render a percentage with one decimal, no trailing-zero noise (e.g. "12.5%").
|
|
186
|
+
function pct(ratio) {
|
|
187
|
+
return `${(ratio * 100).toFixed(1)}%`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Render a human-readable summary block (no color, no emoji). Returns a string
|
|
192
|
+
* so it is testable; main() writes it to stdout.
|
|
193
|
+
*/
|
|
194
|
+
function formatReport(rep) {
|
|
195
|
+
const lines = [];
|
|
196
|
+
lines.push('BYAN Auto-Benchmark ledger report');
|
|
197
|
+
lines.push(` file : ${rep.path}`);
|
|
198
|
+
if (rep.missing) {
|
|
199
|
+
lines.push(' status : ledger not found (the Stop hook has not fired yet)');
|
|
200
|
+
return lines.join('\n');
|
|
201
|
+
}
|
|
202
|
+
lines.push(` records : ${rep.total}${rep.malformed ? ` (+${rep.malformed} malformed, skipped)` : ''}`);
|
|
203
|
+
lines.push(` forks (fires): ${rep.fires} hits: ${rep.hits} misses: ${rep.misses}`);
|
|
204
|
+
lines.push(` miss-rate : ${pct(rep.missRate)}${rep.fires === 0 ? ' (no fork seen)' : ''}`);
|
|
205
|
+
lines.push(` skips : ${rep.skips} exempt: ${rep.exempt} unknown: ${rep.unknown}`);
|
|
206
|
+
lines.push(
|
|
207
|
+
` scope : internal=${rep.byScope.internal} external=${rep.byScope.external}`
|
|
208
|
+
);
|
|
209
|
+
if (rep.gates.countWithGates > 0) {
|
|
210
|
+
lines.push(
|
|
211
|
+
` avg gates : g1=${rep.gates.g1Avg.toFixed(1)} g2=${rep.gates.g2Avg.toFixed(1)} (over ${rep.gates.countWithGates} benchmarks)`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
const events = Object.keys(rep.byEvent).sort();
|
|
215
|
+
if (events.length) {
|
|
216
|
+
lines.push(' by event :');
|
|
217
|
+
for (const ev of events) lines.push(` ${ev.padEnd(26)} ${rep.byEvent[ev]}`);
|
|
218
|
+
}
|
|
219
|
+
return lines.join('\n');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// CLI entry: `node autobench-ledger-report.js [path]` or `--json` for raw data.
|
|
223
|
+
// Exit 0 always (a report tool never fails the shell over a read).
|
|
224
|
+
function main(argv) {
|
|
225
|
+
const args = Array.isArray(argv) ? argv : process.argv.slice(2);
|
|
226
|
+
const asJson = args.includes('--json');
|
|
227
|
+
const fileArg = args.find((a) => a && !a.startsWith('--'));
|
|
228
|
+
const rep = report(fileArg);
|
|
229
|
+
if (asJson) {
|
|
230
|
+
process.stdout.write(JSON.stringify(rep, null, 2) + '\n');
|
|
231
|
+
} else {
|
|
232
|
+
process.stdout.write(formatReport(rep) + '\n');
|
|
233
|
+
}
|
|
234
|
+
return rep;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (require.main === module) {
|
|
238
|
+
main();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
module.exports = {
|
|
242
|
+
MISS_EVENTS,
|
|
243
|
+
HIT_EVENTS,
|
|
244
|
+
SKIP_EVENTS,
|
|
245
|
+
EXEMPT_EVENTS,
|
|
246
|
+
defaultLedgerPath,
|
|
247
|
+
readLedger,
|
|
248
|
+
aggregate,
|
|
249
|
+
report,
|
|
250
|
+
formatReport,
|
|
251
|
+
pct,
|
|
252
|
+
main,
|
|
253
|
+
};
|