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,38 @@
|
|
|
1
|
+
// Cache-hit staleness detection (v2 plan §7.2, GLM-6 primary design with the scoped
|
|
2
|
+
// input-hash amendment MISTRAL-6/DEEPSEEK-6/QWEN-5 converged on).
|
|
3
|
+
//
|
|
4
|
+
// Same class of bug as the lab-dropout incident: a "successful" result is shown that does
|
|
5
|
+
// not correspond to a full, current execution. Stage caching replays a completed stage's
|
|
6
|
+
// output for free on resume - excellent when nothing changed, silently wrong when the task
|
|
7
|
+
// text or chain config changed between a pause and a resume. Nothing today checks that a
|
|
8
|
+
// cached answer still matches what would be asked now.
|
|
9
|
+
//
|
|
10
|
+
// Scope, deliberately narrow: the fingerprint covers the task's own text and the chain
|
|
11
|
+
// config - not the full --context document bundle, which would fire on every irrelevant
|
|
12
|
+
// context-doc edit and defeat the point of caching (this was an explicit board decision,
|
|
13
|
+
// not an oversight - see DECISIONS.md).
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
15
|
+
|
|
16
|
+
/** A short fingerprint of the inputs a cached stage's validity depends on. */
|
|
17
|
+
export function fingerprintInputs(taskText, config) {
|
|
18
|
+
return createHash('sha256').update(JSON.stringify({ taskText, config })).digest('hex').slice(0, 12);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Wrap a base cache getter (label -> { text, inputsFingerprint, ... } | null) so that a
|
|
23
|
+
* cache hit computed against different inputs than `currentFingerprint` is treated as a
|
|
24
|
+
* miss - forcing chain.js to re-run that stage - rather than silently replayed as valid.
|
|
25
|
+
* `onStale(label)` is called once per invalidated hit, for logging/warning side effects;
|
|
26
|
+
* this module has no file or console dependency of its own, so it stays unit-testable.
|
|
27
|
+
*/
|
|
28
|
+
export function withStalenessCheck(baseGet, currentFingerprint, onStale = () => {}) {
|
|
29
|
+
return label => {
|
|
30
|
+
const cached = baseGet(label);
|
|
31
|
+
if (!cached) return null;
|
|
32
|
+
if (cached.inputsFingerprint && cached.inputsFingerprint !== currentFingerprint) {
|
|
33
|
+
onStale(label);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return cached;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// v5 §1 candidate 5: fail-loud pre-flight for a chain config, before any
|
|
2
|
+
// metered call. Three checks, each with an actionable fix naming the real
|
|
3
|
+
// file. The "every model is priced" check two labs (GLM, Kimi) flagged is
|
|
4
|
+
// deliberately excluded - that's the already-logged v4 unpriced-model
|
|
5
|
+
// defect, not new scope for this candidate.
|
|
6
|
+
import { providerNames } from './providers.js';
|
|
7
|
+
import { validateSeatRole } from './seat-role.js';
|
|
8
|
+
|
|
9
|
+
const KNOWN_SEAT_KEYS = ['criteria', 'builder', 'reviser', 'finalist', 'skeleton', 'handoff', 'questions', 'judge', 'proposers', 'critics'];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Lint findings for a chain config, each `{ kind, message, fix }`. Never
|
|
13
|
+
* throws - a malformed config just produces findings, same as any other
|
|
14
|
+
* lint issue. Empty array means the chain passed.
|
|
15
|
+
*/
|
|
16
|
+
export function lintChain(config, filePath = '<chain>') {
|
|
17
|
+
const findings = [];
|
|
18
|
+
const seats = config?.seats || {};
|
|
19
|
+
|
|
20
|
+
// 1. Missing stage contract: a seat under an unrecognized key is wired to
|
|
21
|
+
// no stage at all - chain.js only ever reads the known keys, so a typo'd
|
|
22
|
+
// key (e.g. "critic" instead of "critics") silently never runs, and the
|
|
23
|
+
// chain author has no way to notice short of reading chain.js itself.
|
|
24
|
+
for (const key of Object.keys(seats)) {
|
|
25
|
+
if (!KNOWN_SEAT_KEYS.includes(key)) {
|
|
26
|
+
findings.push({
|
|
27
|
+
kind: 'missing-stage-contract',
|
|
28
|
+
message: `seats.${key} is not a recognized stage seat and will never run.`,
|
|
29
|
+
fix: `Rename "seats.${key}" to one of: ${KNOWN_SEAT_KEYS.join(', ')} in ${filePath}, or remove it if it isn't meant to run.`,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 2. Unreachable stage: the critique/panel round needs at least one
|
|
35
|
+
// critic seat. Missing crashes the run outright (chain.js reads
|
|
36
|
+
// config.seats.critics.length unconditionally); an empty array either
|
|
37
|
+
// crashes (round-robin signoff, a divide-by-zero index) or trivially
|
|
38
|
+
// "passes" with nothing actually reviewed (unanimous signoff, vacuously
|
|
39
|
+
// true over zero critics) - both are a review stage nobody can reach.
|
|
40
|
+
if (!Array.isArray(seats.critics) || seats.critics.length === 0) {
|
|
41
|
+
findings.push({
|
|
42
|
+
kind: 'unreachable-stage',
|
|
43
|
+
message: `seats.critics is missing or empty, so the critique/panel round can never run a real review.`,
|
|
44
|
+
fix: `Add at least one seat to "seats.critics" in ${filePath}, e.g. { "provider": "anthropic", "model": "claude-sonnet-5" }.`,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 3. Missing tool reference: a seat naming a provider this codebase
|
|
49
|
+
// doesn't know fails at call time with "Unknown provider" - worth
|
|
50
|
+
// catching before spending, not after the first API call.
|
|
51
|
+
const allSeats = [
|
|
52
|
+
seats.criteria, seats.builder, seats.reviser, seats.finalist, seats.skeleton,
|
|
53
|
+
seats.handoff, seats.questions, seats.judge,
|
|
54
|
+
...(seats.proposers || []), ...(seats.critics || []),
|
|
55
|
+
].filter(Boolean);
|
|
56
|
+
const known = new Set([...providerNames(), 'mock', 'external']);
|
|
57
|
+
const seenUnknown = new Set();
|
|
58
|
+
for (const s of allSeats) {
|
|
59
|
+
if (s.provider && !known.has(s.provider) && !seenUnknown.has(s.provider)) {
|
|
60
|
+
seenUnknown.add(s.provider);
|
|
61
|
+
findings.push({
|
|
62
|
+
kind: 'missing-tool-reference',
|
|
63
|
+
message: `a seat references an unrecognized provider "${s.provider}".`,
|
|
64
|
+
fix: `Check the spelling against a known provider (${[...known].join(', ')}) in ${filePath}, or add "${s.provider}" to src/providers.js if it's meant to be new.`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 4. Invalid seat role (v6 §1): an unknown `role.lens`, a `role.persona`
|
|
70
|
+
// of the wrong type, an empty `role: {}`, or a stray field on `role`
|
|
71
|
+
// would otherwise only surface as a wrong or missing debate-stage
|
|
72
|
+
// prompt, silently, well after the config was accepted.
|
|
73
|
+
for (const s of allSeats) {
|
|
74
|
+
if (!s.role) continue;
|
|
75
|
+
for (const problem of validateSeatRole(s.role)) {
|
|
76
|
+
findings.push({
|
|
77
|
+
kind: 'invalid-seat-role',
|
|
78
|
+
message: `a seat's role is invalid: ${problem}`,
|
|
79
|
+
fix: `Fix "role" on the affected seat in ${filePath}. A role is optional; set either "lens" (one of the fixed enum values) or "persona" (a string), or both, or omit "role" entirely.`,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 5. Role on a seat kind that never reaches the debate stage (v6 phase
|
|
85
|
+
// 7 bug-audit fix): chain.js's debate stage looks a seat's role up
|
|
86
|
+
// exclusively via seats.proposers - a role set on any other seat kind
|
|
87
|
+
// (criteria, builder, reviser, finalist, skeleton, handoff, questions,
|
|
88
|
+
// judge, critics) is accepted by validateSeatRole but has no effect at
|
|
89
|
+
// all, silently, since nothing ever reads it there.
|
|
90
|
+
const nonProposerSeats = [
|
|
91
|
+
['criteria', seats.criteria], ['builder', seats.builder], ['reviser', seats.reviser],
|
|
92
|
+
['finalist', seats.finalist], ['skeleton', seats.skeleton], ['handoff', seats.handoff],
|
|
93
|
+
['questions', seats.questions], ['judge', seats.judge],
|
|
94
|
+
...(seats.critics || []).map(s => ['critics', s]),
|
|
95
|
+
];
|
|
96
|
+
for (const [kind, s] of nonProposerSeats) {
|
|
97
|
+
if (s?.role) {
|
|
98
|
+
findings.push({
|
|
99
|
+
kind: 'role-on-non-proposer-seat',
|
|
100
|
+
message: `seats.${kind} has a "role" set, but only seats.proposers ever reach the debate stage where a role has any effect - this role is a silent no-op.`,
|
|
101
|
+
fix: `Move "role" to the matching seat under "seats.proposers" in ${filePath}, or remove it from seats.${kind} if it was set by mistake.`,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return findings;
|
|
107
|
+
}
|