the-high-council 0.6.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/.env.example +19 -0
- package/CONTRIBUTING.md +41 -0
- package/LICENSE +21 -0
- package/PERSONAS.md +51 -0
- package/README.md +499 -0
- package/TROUBLESHOOTING.md +45 -0
- package/chains/cheap.json +35 -0
- package/chains/gp-judge-v1.json +80 -0
- package/chains/idea-open-c2.json +106 -0
- package/chains/mock-budget.json +15 -0
- package/chains/mock-debate.json +59 -0
- package/chains/mock-external.json +59 -0
- package/chains/mock-open.json +60 -0
- package/chains/mock-proposals.json +57 -0
- package/chains/mock-questions-wait.json +63 -0
- package/chains/mock-questions.json +63 -0
- package/chains/mock-relay.json +42 -0
- package/chains/mock-unanimous.json +42 -0
- package/chains/mock.json +17 -0
- package/chains/plan-auto-mistral.json +101 -0
- package/chains/plan-auto.json +131 -0
- package/chains/plan-cheap.json +82 -0
- package/chains/plan-debate-c2.json +86 -0
- package/chains/plan-debate-open-c2.json +98 -0
- package/chains/plan-debate-roles-c1.json +149 -0
- package/chains/plan-debate.json +116 -0
- package/chains/plan-fast.json +66 -0
- package/chains/plan-proposals-best-of-5.json +98 -0
- package/chains/plan-proposals.json +96 -0
- package/chains/plan-relay.json +83 -0
- package/chains/plan-thorough.json +82 -0
- package/chains/plan-unanimous.json +82 -0
- package/chains/seven-cheap.json +67 -0
- package/chains/seven.json +60 -0
- package/chains/smo-v2-cheap.json +67 -0
- package/chains/smo.json +50 -0
- package/chains/verify.json +40 -0
- package/package.json +55 -0
- package/src/board-export.js +77 -0
- package/src/budget.js +126 -0
- package/src/buys.js +44 -0
- package/src/cache-integrity.js +38 -0
- package/src/chain-lint.js +107 -0
- package/src/chain.js +880 -0
- package/src/cli.js +1031 -0
- package/src/cost-forecast.js +70 -0
- package/src/cost.js +139 -0
- package/src/errors.js +82 -0
- package/src/http-server.js +349 -0
- package/src/integrity.js +48 -0
- package/src/key-redaction.js +97 -0
- package/src/mcp/server.js +319 -0
- package/src/name-lint.js +186 -0
- package/src/partial-deliverable.js +38 -0
- package/src/peer-claim.js +98 -0
- package/src/personas.js +54 -0
- package/src/preflight.js +84 -0
- package/src/pricing.json +120 -0
- package/src/providers.js +268 -0
- package/src/replay.js +40 -0
- package/src/resume-brief.js +114 -0
- package/src/role-diagnostics.js +181 -0
- package/src/roles.js +520 -0
- package/src/schema-version.js +39 -0
- package/src/scope-freeze.js +35 -0
- package/src/seat-role.js +96 -0
- package/src/shape-rounds.js +58 -0
- package/src/spend.js +193 -0
- package/src/stage-contract.js +223 -0
- package/src/stage-submission.js +53 -0
- package/src/tie-break.js +94 -0
- package/src/ui/app.js +139 -0
- package/src/ui/index.html +43 -0
- package/src/ui/parse.js +101 -0
- package/src/ui/server.js +117 -0
- package/src/ui/style.css +37 -0
- package/src/verdict-stats.js +267 -0
- package/src/withdrawal-ledger.js +47 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// Cross-run verdict/quality accounting: how the debate mechanism itself is
|
|
2
|
+
// behaving, per chain and per lab. Same convention as spend.js - DERIVED
|
|
3
|
+
// from runs/*/report.json and the per-stage *.usage.json files on disk,
|
|
4
|
+
// no ledger, nothing transmitted, nothing that survives a deleted run
|
|
5
|
+
// folder. Never changes a chain or the debate mechanism; it only reads.
|
|
6
|
+
//
|
|
7
|
+
// Privacy: chain/lab names, counts, cost and timing only. Never task
|
|
8
|
+
// content, proposal text or prompt bodies.
|
|
9
|
+
|
|
10
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { runIdToDate } from './spend.js';
|
|
13
|
+
import { shapeRounds } from './shape-rounds.js';
|
|
14
|
+
|
|
15
|
+
const readJson = p => { try { return JSON.parse(readFileSync(p, 'utf8')); } catch { return null; } };
|
|
16
|
+
|
|
17
|
+
function emptyChainAgg() {
|
|
18
|
+
return {
|
|
19
|
+
runs: 0,
|
|
20
|
+
signedOff: 0,
|
|
21
|
+
roundsToSignoff: [], // per run that signed off
|
|
22
|
+
objections: 0,
|
|
23
|
+
withdrawals: 0,
|
|
24
|
+
accepted: 0,
|
|
25
|
+
dropouts: 0,
|
|
26
|
+
unparseable: 0,
|
|
27
|
+
costs: [],
|
|
28
|
+
wallMs: [],
|
|
29
|
+
shapeOnlyRounds: 0,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function emptyLabAgg() {
|
|
34
|
+
return {
|
|
35
|
+
proposed: 0,
|
|
36
|
+
accepted: 0,
|
|
37
|
+
withdrawn: 0,
|
|
38
|
+
cut: 0,
|
|
39
|
+
dropouts: 0,
|
|
40
|
+
unparseable: 0,
|
|
41
|
+
objections: 0,
|
|
42
|
+
novelObjections: 0,
|
|
43
|
+
signoffs: 0,
|
|
44
|
+
soloSignoffs: 0,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Independence skew: does a lab mostly echo objections other labs already
|
|
49
|
+
// raised, and does it sign off even when another lab is still objecting?
|
|
50
|
+
// Descriptive only - this never reweights a panel or changes a verdict, it
|
|
51
|
+
// only makes a pattern visible that was previously invisible without
|
|
52
|
+
// reading every debate post by hand.
|
|
53
|
+
function accumulateIndependence(report, byLab) {
|
|
54
|
+
const posts = report.debate?.posts;
|
|
55
|
+
if (Array.isArray(posts)) {
|
|
56
|
+
const objectorsByTarget = new Map(); // target ("on") -> Set(lab)
|
|
57
|
+
const objectionsByLab = new Map(); // lab -> [target, ...]
|
|
58
|
+
for (const p of posts) {
|
|
59
|
+
if (p.stance !== 'object' || !p.by || !p.on) continue;
|
|
60
|
+
if (!objectorsByTarget.has(p.on)) objectorsByTarget.set(p.on, new Set());
|
|
61
|
+
objectorsByTarget.get(p.on).add(p.by);
|
|
62
|
+
if (!objectionsByLab.has(p.by)) objectionsByLab.set(p.by, []);
|
|
63
|
+
objectionsByLab.get(p.by).push(p.on);
|
|
64
|
+
}
|
|
65
|
+
for (const [labName, targets] of objectionsByLab) {
|
|
66
|
+
const lab = byLab.get(labName) || emptyLabAgg();
|
|
67
|
+
for (const target of targets) {
|
|
68
|
+
lab.objections += 1;
|
|
69
|
+
if (objectorsByTarget.get(target).size === 1) lab.novelObjections += 1;
|
|
70
|
+
}
|
|
71
|
+
byLab.set(labName, lab);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const signoff = Array.isArray(report.signoff) ? report.signoff : [];
|
|
76
|
+
for (const s of signoff) {
|
|
77
|
+
if (s.signedOff !== true || !s.provider) continue;
|
|
78
|
+
const lab = byLab.get(s.provider) || emptyLabAgg();
|
|
79
|
+
lab.signoffs += 1;
|
|
80
|
+
const anotherStillObjects = signoff.some(o =>
|
|
81
|
+
o.provider !== s.provider && (o.signedOff === false || (Array.isArray(o.objections) && o.objections.length > 0)));
|
|
82
|
+
if (anotherStillObjects) lab.soloSignoffs += 1;
|
|
83
|
+
byLab.set(s.provider, lab);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Round number a stage label belongs to, e.g. "panel-2-glm" -> 2,
|
|
88
|
+
// "revise-1" -> 1. Stages with no round (criteria, skeleton, build, ...)
|
|
89
|
+
// return null and don't count toward round totals.
|
|
90
|
+
function roundOf(label) {
|
|
91
|
+
const m = label.match(/^(?:panel|critique|revise)-(\d+)/);
|
|
92
|
+
return m ? Number(m[1]) : null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function largestPromptByStageType(dir, files, out) {
|
|
96
|
+
for (const f of files) {
|
|
97
|
+
if (!f.endsWith('.md')) continue;
|
|
98
|
+
const type = f.replace(/^NEEDS-/, '').replace(/-[a-z0-9]+\.md$/, '').replace(/\.md$/, '').replace(/-\d+$/, '');
|
|
99
|
+
let size = 0;
|
|
100
|
+
try { size = statSync(join(dir, f)).size; } catch { continue; }
|
|
101
|
+
const cur = out.get(type);
|
|
102
|
+
if (!cur || size > cur.bytes) out.set(type, { bytes: size, file: f });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Verdict/quality stats across every run in `runsDir` within the last `days`.
|
|
108
|
+
* Never throws - a missing or unreadable runs/ means no data, not an error.
|
|
109
|
+
*/
|
|
110
|
+
export function verdictStats(runsDir, { days = 30, now = Date.now(), novelObjectionFloor = 0.10, soloSignoffCeiling = 0.60 } = {}) {
|
|
111
|
+
const cutoff = now - days * 24 * 3600 * 1000;
|
|
112
|
+
const byChain = new Map();
|
|
113
|
+
const byLab = new Map();
|
|
114
|
+
const largestPrompt = new Map(); // stage type -> { bytes, file, run }
|
|
115
|
+
let runsSeen = 0;
|
|
116
|
+
let unreadable = 0;
|
|
117
|
+
|
|
118
|
+
let ids = [];
|
|
119
|
+
try {
|
|
120
|
+
ids = existsSync(runsDir) ? readdirSync(runsDir) : [];
|
|
121
|
+
} catch {
|
|
122
|
+
return { runsDir, days, since: new Date(cutoff), chains: [], labs: [], largestPrompts: [],
|
|
123
|
+
runsSeen: 0, unreadable: 0, note: 'runs directory could not be read' };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (const id of ids) {
|
|
127
|
+
const when = runIdToDate(id);
|
|
128
|
+
if (!when || when.getTime() < cutoff) continue;
|
|
129
|
+
const dir = join(runsDir, id);
|
|
130
|
+
let files;
|
|
131
|
+
try {
|
|
132
|
+
if (!statSync(dir).isDirectory()) continue;
|
|
133
|
+
files = readdirSync(dir);
|
|
134
|
+
} catch { unreadable += 1; continue; }
|
|
135
|
+
|
|
136
|
+
const runJson = readJson(join(dir, 'run.json'));
|
|
137
|
+
const report = readJson(join(dir, 'report.json'));
|
|
138
|
+
const chainName = report?.chain ?? runJson?.chain ?? 'unknown';
|
|
139
|
+
runsSeen += 1;
|
|
140
|
+
|
|
141
|
+
for (const [type, entry] of (() => {
|
|
142
|
+
const local = new Map();
|
|
143
|
+
largestPromptByStageType(dir, files, local);
|
|
144
|
+
return local;
|
|
145
|
+
})()) {
|
|
146
|
+
const cur = largestPrompt.get(type);
|
|
147
|
+
if (!cur || entry.bytes > cur.bytes) largestPrompt.set(type, { ...entry, run: id });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!report) continue; // incomplete/paused run: no verdict to score yet
|
|
151
|
+
|
|
152
|
+
const chain = byChain.get(chainName) || emptyChainAgg();
|
|
153
|
+
chain.runs += 1;
|
|
154
|
+
|
|
155
|
+
const signoff = Array.isArray(report.signoff) ? report.signoff : [];
|
|
156
|
+
const anySignedOff = signoff.length > 0 && signoff.every(s => s.signedOff === true || s.signedOff === null);
|
|
157
|
+
const allSignedOff = signoff.length > 0 && signoff.some(s => s.signedOff === true) &&
|
|
158
|
+
signoff.every(s => s.signedOff === true || s.signedOff === null);
|
|
159
|
+
if (report.passed === true || allSignedOff) chain.signedOff += 1;
|
|
160
|
+
|
|
161
|
+
for (const s of signoff) {
|
|
162
|
+
if (s.signedOff === null) chain.unparseable += 1; // abstention
|
|
163
|
+
else if (Array.isArray(s.objections)) chain.objections += s.objections.length;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const rounds = (report.stages || []).map(s => roundOf(s.label)).filter(n => n !== null);
|
|
167
|
+
if (rounds.length && (report.passed === true || allSignedOff)) {
|
|
168
|
+
chain.roundsToSignoff.push(Math.max(...rounds));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
chain.dropouts += (report.dropouts || []).length;
|
|
172
|
+
chain.shapeOnlyRounds += shapeRounds(dir).shapeOnlyRounds;
|
|
173
|
+
|
|
174
|
+
const rows = report.scoreboard?.rows || [];
|
|
175
|
+
for (const row of rows) {
|
|
176
|
+
if (row.withdrawn) chain.withdrawals += 1;
|
|
177
|
+
if (row.status === 'accepted') chain.accepted += 1;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (report.totals?.usd !== undefined && report.totals.usd !== null) chain.costs.push(report.totals.usd);
|
|
181
|
+
const wallMs = (report.stages || []).reduce((s, st) => s + (st.ms || 0), 0);
|
|
182
|
+
if (wallMs) chain.wallMs.push(wallMs);
|
|
183
|
+
|
|
184
|
+
byChain.set(chainName, chain);
|
|
185
|
+
accumulateIndependence(report, byLab);
|
|
186
|
+
|
|
187
|
+
for (const l of report.scoreboard?.labs || []) {
|
|
188
|
+
const lab = byLab.get(l.lab) || emptyLabAgg();
|
|
189
|
+
lab.proposed += l.proposed || 0;
|
|
190
|
+
lab.accepted += l.accepted || 0;
|
|
191
|
+
lab.withdrawn += l.withdrawn || 0;
|
|
192
|
+
lab.cut += l.cut || 0;
|
|
193
|
+
byLab.set(l.lab, lab);
|
|
194
|
+
}
|
|
195
|
+
for (const d of report.dropouts || []) {
|
|
196
|
+
const lab = byLab.get(d.lab) || emptyLabAgg();
|
|
197
|
+
lab.dropouts += 1;
|
|
198
|
+
byLab.set(d.lab, lab);
|
|
199
|
+
}
|
|
200
|
+
for (const s of signoff) {
|
|
201
|
+
if (s.signedOff === null) {
|
|
202
|
+
const lab = byLab.get(s.provider) || emptyLabAgg();
|
|
203
|
+
lab.unparseable += 1;
|
|
204
|
+
byLab.set(s.provider, lab);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const mean = xs => xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : null;
|
|
210
|
+
|
|
211
|
+
const chains = [...byChain.entries()].map(([name, c]) => ({
|
|
212
|
+
chain: name,
|
|
213
|
+
runs: c.runs,
|
|
214
|
+
signoffRate: c.runs ? c.signedOff / c.runs : null,
|
|
215
|
+
meanRoundsToSignoff: mean(c.roundsToSignoff),
|
|
216
|
+
objections: c.objections,
|
|
217
|
+
withdrawals: c.withdrawals,
|
|
218
|
+
accepted: c.accepted,
|
|
219
|
+
dropouts: c.dropouts,
|
|
220
|
+
shapeOnlyRounds: c.shapeOnlyRounds,
|
|
221
|
+
unparseable: c.unparseable,
|
|
222
|
+
meanCostUsd: mean(c.costs),
|
|
223
|
+
meanWallMs: mean(c.wallMs),
|
|
224
|
+
})).sort((a, b) => b.runs - a.runs);
|
|
225
|
+
|
|
226
|
+
const labs = [...byLab.entries()].map(([name, l]) => {
|
|
227
|
+
const novelObjectionRate = l.objections ? l.novelObjections / l.objections : null;
|
|
228
|
+
const soloSignoffRate = l.signoffs ? l.soloSignoffs / l.signoffs : null;
|
|
229
|
+
const lowIndependence = novelObjectionRate !== null && soloSignoffRate !== null &&
|
|
230
|
+
novelObjectionRate < novelObjectionFloor && soloSignoffRate > soloSignoffCeiling;
|
|
231
|
+
return {
|
|
232
|
+
lab: name,
|
|
233
|
+
proposed: l.proposed,
|
|
234
|
+
accepted: l.accepted,
|
|
235
|
+
withdrawn: l.withdrawn,
|
|
236
|
+
cut: l.cut,
|
|
237
|
+
dropouts: l.dropouts,
|
|
238
|
+
unparseable: l.unparseable,
|
|
239
|
+
objections: l.objections,
|
|
240
|
+
novelObjections: l.novelObjections,
|
|
241
|
+
novelObjectionRate,
|
|
242
|
+
signoffs: l.signoffs,
|
|
243
|
+
soloSignoffs: l.soloSignoffs,
|
|
244
|
+
soloSignoffRate,
|
|
245
|
+
lowIndependence,
|
|
246
|
+
};
|
|
247
|
+
}).sort((a, b) => b.proposed - a.proposed);
|
|
248
|
+
|
|
249
|
+
const largestPrompts = [...largestPrompt.entries()]
|
|
250
|
+
.map(([type, v]) => ({ stageType: type, bytes: v.bytes, tokensApprox: Math.round(v.bytes / 4), file: v.file, run: v.run }))
|
|
251
|
+
.sort((a, b) => b.bytes - a.bytes);
|
|
252
|
+
|
|
253
|
+
return { runsDir, days, since: new Date(cutoff), chains, labs, largestPrompts, runsSeen, unreadable };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const csvNum = x => (x === null ? '' : String(x));
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* `labs` from verdictStats() as an independence-skew CSV: one row per lab,
|
|
260
|
+
* numbers only, no interpretation strings.
|
|
261
|
+
*/
|
|
262
|
+
export function independenceStatsCsv(labs) {
|
|
263
|
+
const header = 'lab,objections,novelObjections,novelObjectionRate,signoffs,soloSignoffs,soloSignoffRate,lowIndependence';
|
|
264
|
+
const rows = labs.map(l =>
|
|
265
|
+
[l.lab, l.objections, l.novelObjections, csvNum(l.novelObjectionRate), l.signoffs, l.soloSignoffs, csvNum(l.soloSignoffRate), l.lowIndependence].join(','));
|
|
266
|
+
return [header, ...rows].join('\n');
|
|
267
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// v5 §1 candidate 3: does every withdrawn proposal actually resolve to a
|
|
2
|
+
// surviving owner? A lab can withdraw "in favour of" another proposal
|
|
3
|
+
// (`replaced_by`); if two or more withdrawals point around in a circle, or
|
|
4
|
+
// a chain of withdrawals dead-ends without ever reaching a proposal that
|
|
5
|
+
// is still standing, the section that chain was covering has nobody left
|
|
6
|
+
// to write it - a real run's own debate hit exactly this (KIMI-4 and
|
|
7
|
+
// MISTRAL-2 mutually withdrew in each other's favour) and it went
|
|
8
|
+
// undetected. Pure function over `report.proposals` - no ledger file, no
|
|
9
|
+
// state beyond what a run already records.
|
|
10
|
+
export function withdrawalLedger(proposals) {
|
|
11
|
+
const byId = new Map((proposals || []).map(p => [p.id, p]));
|
|
12
|
+
const orphanSections = new Set();
|
|
13
|
+
const cycleSignatures = new Set();
|
|
14
|
+
|
|
15
|
+
for (const p of proposals || []) {
|
|
16
|
+
if (!p.withdrawn) continue;
|
|
17
|
+
const path = [];
|
|
18
|
+
let cur = p.id;
|
|
19
|
+
// eslint-disable-next-line no-constant-condition
|
|
20
|
+
while (true) {
|
|
21
|
+
const idx = path.indexOf(cur);
|
|
22
|
+
if (idx !== -1) {
|
|
23
|
+
const cycle = path.slice(idx);
|
|
24
|
+
cycle.forEach(id => orphanSections.add(id));
|
|
25
|
+
cycleSignatures.add([...cycle].sort().join(','));
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
path.push(cur);
|
|
29
|
+
const node = byId.get(cur);
|
|
30
|
+
if (!node) { // dangling replaced_by reference: the chain so far has no surviving owner
|
|
31
|
+
path.forEach(id => orphanSections.add(id));
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
if (!node.withdrawn) break; // resolved to a proposal that is still standing - not orphaned
|
|
35
|
+
if (!node.replaced_by) { // withdrawn with nothing named in its place - a dead end
|
|
36
|
+
path.forEach(id => orphanSections.add(id));
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
cur = node.replaced_by;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
orphanSections: [...orphanSections].sort(),
|
|
45
|
+
withdrawalCycles: cycleSignatures.size,
|
|
46
|
+
};
|
|
47
|
+
}
|