champollion-mcp-server 0.1.0 → 0.2.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 +468 -0
- package/README.md +135 -82
- package/bin/server.js +4 -3
- package/instructions.md +188 -232
- package/package.json +9 -5
- package/src/index.js +1364 -297
- package/src/tools/args.js +90 -0
- package/src/tools/contests.js +474 -0
- package/src/tools/corpora.js +423 -0
- package/src/tools/forge.js +1076 -78
- package/src/tools/harness-fst.js +404 -0
- package/src/tools/harness.js +1918 -188
- package/src/tools/job-supervisor.js +96 -0
- package/src/tools/jobs.js +408 -0
- package/src/tools/language-card.js +697 -0
- package/src/tools/languages.js +1361 -42
- package/src/tools/metrics-plan.js +287 -0
- package/src/tools/output-trim.js +177 -0
- package/src/tools/overview.js +423 -0
- package/src/tools/plan-notes.js +203 -0
- package/src/tools/plural.js +14 -0
- package/src/tools/publish-preview.js +191 -0
- package/src/tools/publish-report.js +382 -0
- package/src/tools/queue.js +413 -82
- package/src/tools/register-corpus-hint.js +77 -0
- package/src/tools/releasable.js +80 -0
- package/src/tools/reliability.js +114 -21
- package/src/tools/results.js +187 -20
- package/src/tools/run-plan.js +608 -0
- package/src/tools/state.js +40 -0
- package/src/tools/training.js +76 -18
- package/src/tools/translate.js +1612 -77
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Can the eval harness HERE use a language's FST? — asked of the harness.
|
|
3
|
+
*
|
|
4
|
+
* Two different facts, never merged:
|
|
5
|
+
*
|
|
6
|
+
* the card resources.fsts[] says an FST EXISTS (resource existence,
|
|
7
|
+
* cited on the card). Kalaallisut's card lists giellalt's
|
|
8
|
+
* lang-kal — true, and says nothing about evaluation.
|
|
9
|
+
* the harness mt_eval_harness/data/fst-pins.json says WHICH build the
|
|
10
|
+
* harness installs and measures with. No pin → `mt-eval` can
|
|
11
|
+
* neither download nor load an FST for the language, and the
|
|
12
|
+
* FST metrics do not run there (plugin_discovery skips them).
|
|
13
|
+
*
|
|
14
|
+
* The pins are a property of the installed HARNESS VERSION ("a harness
|
|
15
|
+
* version pins its FST version" — the file ships inside the wheel, and
|
|
16
|
+
* MT_EVAL_FST_PINS can replace it on a federated or air-gapped host). No copy
|
|
17
|
+
* travels with the champollion npm package, and a bundled copy would be wrong
|
|
18
|
+
* by design: it would describe whichever harness the CLI was built beside, not
|
|
19
|
+
* the one `run_benchmark` spawns. So this asks that one: the `mt-eval` on PATH
|
|
20
|
+
* (what run_benchmark runs), through the Python interpreter its launcher
|
|
21
|
+
* names, with the same environment — bounded, no shell. No harness, or no
|
|
22
|
+
* answer, is reported as "cannot tell", never as "no pin".
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { spawn } from 'node:child_process';
|
|
26
|
+
import { readFileSync } from 'node:fs';
|
|
27
|
+
|
|
28
|
+
import { whichSync } from './forge.js';
|
|
29
|
+
import { stripAbsolutePaths } from './harness.js';
|
|
30
|
+
import { displayPath } from './state.js';
|
|
31
|
+
|
|
32
|
+
export const FST_PROBE_TIMEOUT_MS = 20_000;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Pin formats the harness downloads by itself — mirrors install_fst() in
|
|
36
|
+
* arena/mt_eval_harness/plugins/fst_installer.py and the same list in
|
|
37
|
+
* config.py's automation path. Any other format ("divvun", "manual", …) is
|
|
38
|
+
* pinned but has to be installed by hand.
|
|
39
|
+
*/
|
|
40
|
+
export const AUTO_INSTALL_FORMATS = new Set(['giellalt-nightly-apt', 'legacy-zip', 'divvun-macos-pkg']);
|
|
41
|
+
|
|
42
|
+
const SENTINEL = 'CHAMPOLLION_FST_PROBE ';
|
|
43
|
+
|
|
44
|
+
// Works against every harness version: get_fst_install_info() exists in all
|
|
45
|
+
// of them (0.1.x read it from the cards, which the 2026-08-12 cutover emptied;
|
|
46
|
+
// 0.2.0+ reads the shipped pins file). Every lookup is guarded so one missing
|
|
47
|
+
// helper degrades to null instead of losing the answer.
|
|
48
|
+
const PROBE_SCRIPT = `
|
|
49
|
+
import json, os, sys
|
|
50
|
+
def emit(doc):
|
|
51
|
+
sys.stdout.write(${JSON.stringify(SENTINEL)} + json.dumps(doc) + "\\n")
|
|
52
|
+
out = {}
|
|
53
|
+
try:
|
|
54
|
+
from importlib.metadata import version as _version
|
|
55
|
+
try:
|
|
56
|
+
out["version"] = _version("mt-eval-harness")
|
|
57
|
+
except Exception:
|
|
58
|
+
out["version"] = None
|
|
59
|
+
from mt_eval_harness import language_cards as lc
|
|
60
|
+
except Exception as exc:
|
|
61
|
+
emit({"error": "the harness does not import: %s: %s" % (type(exc).__name__, exc)})
|
|
62
|
+
sys.exit(0)
|
|
63
|
+
out["pinsShipped"] = hasattr(lc, "fst_pinned_codes")
|
|
64
|
+
out["pinsOverride"] = bool(os.environ.get("MT_EVAL_FST_PINS", "").strip())
|
|
65
|
+
# which Python answered — the one run_benchmark spawns; another tool (forge)
|
|
66
|
+
# asking another interpreter can see another runtime (Round 13)
|
|
67
|
+
out["python"] = sys.executable
|
|
68
|
+
# The runtime, as the harness's own fst_state reads it (it IMPORTS pyhfst — a
|
|
69
|
+
# present-but-broken install is missing, as the run will find it) when this
|
|
70
|
+
# harness has it; find_spec only for an older one, and said so.
|
|
71
|
+
try:
|
|
72
|
+
from mt_eval_harness import config as _C
|
|
73
|
+
except Exception:
|
|
74
|
+
_C = None
|
|
75
|
+
_has_state = _C is not None and hasattr(_C, "fst_state")
|
|
76
|
+
out["runtimeFrom"] = "fst_state" if _has_state else "find_spec"
|
|
77
|
+
if not _has_state:
|
|
78
|
+
try:
|
|
79
|
+
import importlib.util as _util
|
|
80
|
+
out["pyhfst"] = _util.find_spec("pyhfst") is not None
|
|
81
|
+
except Exception:
|
|
82
|
+
out["pyhfst"] = None
|
|
83
|
+
else:
|
|
84
|
+
out["pyhfst"] = None
|
|
85
|
+
try:
|
|
86
|
+
from mt_eval_harness.plugins import fst_installer as fi
|
|
87
|
+
except Exception:
|
|
88
|
+
fi = None
|
|
89
|
+
langs = {}
|
|
90
|
+
for code in sys.argv[1:]:
|
|
91
|
+
try:
|
|
92
|
+
info = lc.get_fst_install_info(code)
|
|
93
|
+
except Exception as exc:
|
|
94
|
+
emit({"error": "the harness could not read its FST pins: %s" % exc})
|
|
95
|
+
sys.exit(0)
|
|
96
|
+
pin = None
|
|
97
|
+
if isinstance(info, dict):
|
|
98
|
+
pin = {k: info.get(k) for k in ("repo", "format", "maturity", "kind", "langCommit", "releaseTag")}
|
|
99
|
+
try:
|
|
100
|
+
meta = lc.get_fst_pin(code) if hasattr(lc, "get_fst_pin") else None
|
|
101
|
+
except Exception:
|
|
102
|
+
meta = None
|
|
103
|
+
if isinstance(meta, dict):
|
|
104
|
+
pin["name"] = meta.get("name")
|
|
105
|
+
pin["url"] = meta.get("url")
|
|
106
|
+
rec = {"pin": pin}
|
|
107
|
+
st = None
|
|
108
|
+
if pin and _has_state:
|
|
109
|
+
try:
|
|
110
|
+
st = _C.fst_state(code)
|
|
111
|
+
except Exception as exc:
|
|
112
|
+
rec["stateError"] = "%s: %s" % (type(exc).__name__, exc)
|
|
113
|
+
if isinstance(st, dict):
|
|
114
|
+
# the harness's one reading of this machine's FST lane (config.fst_state)
|
|
115
|
+
rec["installed"] = bool(st.get("analyzer_installed"))
|
|
116
|
+
rec["runtime"] = bool(st.get("runtime_installed"))
|
|
117
|
+
rec["missing"] = list(st.get("missing") or [])
|
|
118
|
+
rec["setupCommand"] = st.get("setup_command")
|
|
119
|
+
rec["stateLine"] = st.get("line")
|
|
120
|
+
if out["pyhfst"] is None:
|
|
121
|
+
out["pyhfst"] = rec["runtime"]
|
|
122
|
+
else:
|
|
123
|
+
try:
|
|
124
|
+
rec["installed"] = bool(fi.is_fst_installed(code)) if fi else None
|
|
125
|
+
except Exception:
|
|
126
|
+
rec["installed"] = None
|
|
127
|
+
try:
|
|
128
|
+
rec["stale"] = (bool(fi.installed_pin_mismatch(code))
|
|
129
|
+
if rec["installed"] and pin and hasattr(fi, "installed_pin_mismatch") else False)
|
|
130
|
+
except Exception:
|
|
131
|
+
rec["stale"] = None
|
|
132
|
+
try:
|
|
133
|
+
pack = lc.get_eval_pack(code)
|
|
134
|
+
rec["evalPackFst"] = bool(pack and pack.get("requiresFst"))
|
|
135
|
+
except Exception:
|
|
136
|
+
rec["evalPackFst"] = None
|
|
137
|
+
rec.setdefault("stateLine", None)
|
|
138
|
+
langs[code] = rec
|
|
139
|
+
out["langs"] = langs
|
|
140
|
+
if out["pyhfst"] is None and _has_state:
|
|
141
|
+
# no pinned language asked: the runtime by the same import fst_state makes
|
|
142
|
+
try:
|
|
143
|
+
__import__("pyhfst")
|
|
144
|
+
out["pyhfst"] = True
|
|
145
|
+
except ImportError:
|
|
146
|
+
out["pyhfst"] = False
|
|
147
|
+
except Exception:
|
|
148
|
+
out["pyhfst"] = None
|
|
149
|
+
emit(out)
|
|
150
|
+
`;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The interpreter a console-script launcher runs, from its first lines.
|
|
154
|
+
* Handles `#!/abs/python [flags]`, `#!/usr/bin/env python3`, and pip's
|
|
155
|
+
* `#!/bin/sh` + `'''exec' "<python>" "$0" "$@"` form (used when the
|
|
156
|
+
* interpreter path contains spaces). Null when it cannot tell — a Windows
|
|
157
|
+
* .exe launcher, or not a Python script at all.
|
|
158
|
+
*
|
|
159
|
+
* @param {string} text the launcher's head
|
|
160
|
+
* @returns {{cmd: string, args: string[]}|null}
|
|
161
|
+
*/
|
|
162
|
+
export function interpreterFromLauncher(text) {
|
|
163
|
+
const lines = String(text || '').split(/\r?\n/);
|
|
164
|
+
const m = /^#!\s*(\S+)(?:[ \t]+(.*))?$/.exec(lines[0] || '');
|
|
165
|
+
if (!m) return null;
|
|
166
|
+
const prog = m[1];
|
|
167
|
+
const rest = (m[2] || '').trim().split(/\s+/).filter(Boolean);
|
|
168
|
+
const base = prog.split(/[\\/]/).pop();
|
|
169
|
+
if (base === 'env') {
|
|
170
|
+
const cmd = rest.find((a) => !a.startsWith('-'));
|
|
171
|
+
return cmd ? { cmd, args: [] } : null;
|
|
172
|
+
}
|
|
173
|
+
if (base === 'sh' || base === 'bash') {
|
|
174
|
+
const ex = /^'''exec'\s+(?:"([^"]+)"|'([^']+)'|(\S+))/.exec(lines[1] || '');
|
|
175
|
+
const cmd = ex && (ex[1] || ex[2] || ex[3]);
|
|
176
|
+
return cmd ? { cmd, args: [] } : null;
|
|
177
|
+
}
|
|
178
|
+
if (/python/i.test(base)) return { cmd: prog, args: rest };
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Run a command, bounded, no shell, stdin closed. Never throws. */
|
|
183
|
+
export function runBounded(cmd, args, { env, timeout }) {
|
|
184
|
+
return new Promise((done) => {
|
|
185
|
+
let proc;
|
|
186
|
+
try {
|
|
187
|
+
proc = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'], env, timeout });
|
|
188
|
+
} catch (err) {
|
|
189
|
+
done({ code: null, stdout: '', stderr: '', spawnError: err.message });
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
let stdout = '';
|
|
193
|
+
let stderr = '';
|
|
194
|
+
proc.stdout.on('data', (d) => { stdout += d; });
|
|
195
|
+
proc.stderr.on('data', (d) => { stderr += d; });
|
|
196
|
+
proc.on('error', (err) => done({ code: null, stdout, stderr, spawnError: err.message }));
|
|
197
|
+
proc.on('close', (code, signal) => done({ code, stdout, stderr, timedOut: code === null && Boolean(signal) }));
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const CODE_RE = /^[A-Za-z]{2,8}(?:[-_][A-Za-z0-9]{1,8})*$/;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Ask the installed harness which of `codes` it has an FST pin for, whether
|
|
205
|
+
* that FST is installed, and whether the pyhfst runtime is. Never throws.
|
|
206
|
+
*
|
|
207
|
+
* @param {string[]} codes
|
|
208
|
+
* @param {object} [deps] env, which, readLauncher, python ({cmd, args}), run, timeoutMs
|
|
209
|
+
* @returns {Promise<
|
|
210
|
+
* {status: 'ok', how: string, version: string|null, pinsShipped: boolean,
|
|
211
|
+
* pinsOverride: boolean, pyhfst: boolean|null,
|
|
212
|
+
* langs: Object<string, {pin: object|null, installed: boolean|null, stale: boolean|null,
|
|
213
|
+
* evalPackFst: boolean|null}>}
|
|
214
|
+
* | {status: 'not-installed', how: string}
|
|
215
|
+
* | {status: 'error', how: string, error: string}>}
|
|
216
|
+
*/
|
|
217
|
+
/**
|
|
218
|
+
* The Python interpreter the `mt-eval` on PATH runs — the one run_benchmark
|
|
219
|
+
* spawns — read from its launcher (PYTHON_BIN when the launcher names none).
|
|
220
|
+
*
|
|
221
|
+
* @returns {{interp: {cmd: string, args: string[]}, how: string}
|
|
222
|
+
* | {status: 'not-installed', how: string} | {status: 'error', how: string, error: string}}
|
|
223
|
+
*/
|
|
224
|
+
export function harnessInterpreter({
|
|
225
|
+
env = process.env,
|
|
226
|
+
which = (cmd) => whichSync(cmd, env),
|
|
227
|
+
readLauncher = (p) => readFileSync(p, 'utf8').slice(0, 4096),
|
|
228
|
+
python = null,
|
|
229
|
+
} = {}) {
|
|
230
|
+
if (python) return { interp: python, how: 'the given Python' };
|
|
231
|
+
const launcher = which('mt-eval');
|
|
232
|
+
if (!launcher) return { status: 'not-installed', how: 'no `mt-eval` on PATH' };
|
|
233
|
+
let how = '`mt-eval` on PATH';
|
|
234
|
+
let head = '';
|
|
235
|
+
try { head = readLauncher(launcher); } catch { head = ''; }
|
|
236
|
+
let interp = interpreterFromLauncher(head);
|
|
237
|
+
if (!interp && env.PYTHON_BIN) {
|
|
238
|
+
interp = { cmd: env.PYTHON_BIN, args: [] };
|
|
239
|
+
how = `PYTHON_BIN (the Python behind \`mt-eval\` could not be read from its launcher)`;
|
|
240
|
+
}
|
|
241
|
+
if (!interp) {
|
|
242
|
+
return { status: 'error', how, error: 'could not tell which Python `mt-eval` runs (its launcher names none); set PYTHON_BIN to it' };
|
|
243
|
+
}
|
|
244
|
+
return { interp, how };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export async function probeHarnessFst(codes, {
|
|
248
|
+
env = process.env,
|
|
249
|
+
which = (cmd) => whichSync(cmd, env),
|
|
250
|
+
readLauncher = (p) => readFileSync(p, 'utf8').slice(0, 4096),
|
|
251
|
+
python = null,
|
|
252
|
+
run = runBounded,
|
|
253
|
+
timeoutMs = FST_PROBE_TIMEOUT_MS,
|
|
254
|
+
} = {}) {
|
|
255
|
+
const wanted = (codes || []).filter((c) => CODE_RE.test(String(c)));
|
|
256
|
+
const found = harnessInterpreter({ env, which, readLauncher, python });
|
|
257
|
+
if (!found.interp) return found;
|
|
258
|
+
const { interp, how } = found;
|
|
259
|
+
const r = await run(interp.cmd, [...(interp.args || []), '-c', PROBE_SCRIPT, ...wanted], { env, timeout: timeoutMs });
|
|
260
|
+
if (r.timedOut) return { status: 'error', how, error: `the harness did not answer within ${Math.round(timeoutMs / 1000)}s` };
|
|
261
|
+
if (r.spawnError) return { status: 'error', how, error: stripAbsolutePaths(`could not start the harness's Python: ${r.spawnError}`) };
|
|
262
|
+
const line = String(r.stdout || '').split(/\r?\n/).reverse().find((l) => l.startsWith(SENTINEL));
|
|
263
|
+
if (!line) {
|
|
264
|
+
const tail = String(r.stderr || '').trim().split(/\r?\n/).filter(Boolean).pop() || `exit ${r.code}`;
|
|
265
|
+
return { status: 'error', how, error: stripAbsolutePaths(`the harness gave no answer (${tail.slice(0, 200)})`) };
|
|
266
|
+
}
|
|
267
|
+
let doc;
|
|
268
|
+
try { doc = JSON.parse(line.slice(SENTINEL.length)); } catch (err) {
|
|
269
|
+
return { status: 'error', how, error: `unreadable harness answer (${err.message})` };
|
|
270
|
+
}
|
|
271
|
+
if (doc.error) return { status: 'error', how, error: stripAbsolutePaths(String(doc.error)) };
|
|
272
|
+
return {
|
|
273
|
+
status: 'ok',
|
|
274
|
+
how,
|
|
275
|
+
version: doc.version ?? null,
|
|
276
|
+
pinsShipped: doc.pinsShipped === true,
|
|
277
|
+
pinsOverride: doc.pinsOverride === true,
|
|
278
|
+
pyhfst: typeof doc.pyhfst === 'boolean' ? doc.pyhfst : null,
|
|
279
|
+
runtimeFrom: doc.runtimeFrom === 'fst_state' ? 'fst_state' : (doc.runtimeFrom ? 'find_spec' : null),
|
|
280
|
+
python: typeof doc.python === 'string' ? doc.python : null,
|
|
281
|
+
langs: doc.langs && typeof doc.langs === 'object' ? doc.langs : {},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
// Rendering — the card fact and the harness capability, side by side
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
/** owner/repo of a GitHub URL or an `owner/repo` slug, lowercased; else null. */
|
|
290
|
+
function repoKey(s) {
|
|
291
|
+
const t = String(s || '').trim().toLowerCase()
|
|
292
|
+
.replace(/^https?:\/\//, '').replace(/^(www\.)?github\.com\//, '')
|
|
293
|
+
.replace(/\.git$/, '').replace(/\/+$/, '');
|
|
294
|
+
const parts = t.split('/');
|
|
295
|
+
return parts.length === 2 && parts[0] && parts[1] ? t : null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Does this card FST entry name the build the harness pins? */
|
|
299
|
+
function isPinnedBuild(fst, pin) {
|
|
300
|
+
const card = repoKey(fst.url);
|
|
301
|
+
return Boolean(card) && [pin.repo, pin.url].some((p) => repoKey(p) === card);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function buildLabel(pin) {
|
|
305
|
+
const ver = pin.langCommit ? ` @ ${String(pin.langCommit).slice(0, 8)}` : (pin.releaseTag ? ` ${pin.releaseTag}` : '');
|
|
306
|
+
const tags = [
|
|
307
|
+
pin.maturity && `GiellaLT maturity: ${pin.maturity}`,
|
|
308
|
+
pin.kind === 'acceptor' && 'acceptor only — FST acceptance, not morphological accuracy',
|
|
309
|
+
].filter(Boolean);
|
|
310
|
+
return `${pin.repo || pin.name || 'pinned build'}${ver}${tags.length ? `; ${tags.join('; ')}` : ''}`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** What the harness can do with its pin for `code`, given the probe. */
|
|
314
|
+
function pinCapability(code, rec, probe) {
|
|
315
|
+
const pin = rec.pin;
|
|
316
|
+
const runtime = probe.pyhfst === false
|
|
317
|
+
? ` The FST runtime (pyhfst) is not installed: \`${rec.evalPackFst ? `mt-eval setup --lang ${code}` : 'mt-eval setup --fst'}\`.`
|
|
318
|
+
: '';
|
|
319
|
+
if (!AUTO_INSTALL_FORMATS.has(pin.format)) {
|
|
320
|
+
return `the harness pins it (${buildLabel(pin)}), but its "${pin.format || 'unstated'}" format cannot be downloaded automatically — `
|
|
321
|
+
+ (rec.installed
|
|
322
|
+
? 'installed here by hand.'
|
|
323
|
+
: 'until it is installed by hand, FST metrics will not run for this language (`mt-eval setup --status` lists it as a manual install).')
|
|
324
|
+
+ runtime;
|
|
325
|
+
}
|
|
326
|
+
if (rec.installed && rec.stale) {
|
|
327
|
+
return `the harness pins this build (${buildLabel(pin)}); an FST is installed here, but the harness cannot confirm it is the pinned build — `
|
|
328
|
+
+ 'runs measure with the installed one (the run card records which) until that cache is removed and the pin re-installed '
|
|
329
|
+
+ `(the harness's run-time warning names the directory).${runtime}`;
|
|
330
|
+
}
|
|
331
|
+
// The harness's own sentence when it gives one (one wording everywhere:
|
|
332
|
+
// setup --status, the eval-pack lines, the run's advisory). This line used
|
|
333
|
+
// to say the FST "downloads on the first evaluation" — nothing downloads by
|
|
334
|
+
// itself (synthetic school persona, Round 8).
|
|
335
|
+
if (rec.stateLine) return `the harness pins this build (${buildLabel(pin)}). ${rec.stateLine}`;
|
|
336
|
+
if (rec.installed) return `the harness pins this build (${buildLabel(pin)}); installed here.${runtime}`;
|
|
337
|
+
return `the harness pins this build (${buildLabel(pin)}); not installed here — nothing downloads by itself: `
|
|
338
|
+
+ `\`mt-eval setup --lang ${code}\` installs it; until then a run proceeds with FST acceptance marked not computed.`
|
|
339
|
+
+ runtime;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* The FST lines for the overview: one per FST the card lists, each with the
|
|
344
|
+
* card fact first and the harness's answer after it. Empty when the card
|
|
345
|
+
* lists none and the harness pins none (the Tooling line already says so).
|
|
346
|
+
*
|
|
347
|
+
* @param {string} code the card's canonical code
|
|
348
|
+
* @param {object[]} fsts summary.resources.fsts (name, url, publisher, …)
|
|
349
|
+
* @param {{ok: boolean, value?: object, error?: string}|null} section the probe, as overview's section() returns it
|
|
350
|
+
* @returns {string[]}
|
|
351
|
+
*/
|
|
352
|
+
export function formatFstLines(code, fsts, section) {
|
|
353
|
+
const list = Array.isArray(fsts) ? fsts : [];
|
|
354
|
+
const probe = section?.ok ? section.value : null;
|
|
355
|
+
const rec = probe?.status === 'ok' ? (probe.langs?.[code] || { pin: null }) : null;
|
|
356
|
+
const pin = rec?.pin || null;
|
|
357
|
+
// Which harness answered, and in which Python (the one run_benchmark
|
|
358
|
+
// spawns): another tool asking another interpreter — or the same one after
|
|
359
|
+
// an install — can read the FST lane differently (Round 13: the overview
|
|
360
|
+
// and forge_discover disagreed on pyhfst while another session installed it).
|
|
361
|
+
const who = probe?.status === 'ok'
|
|
362
|
+
? `mt-eval-harness ${probe.version || '(version unknown)'}`
|
|
363
|
+
+ `${probe.python ? ` in ${displayPath(probe.python)}` : ''}`
|
|
364
|
+
+ `${probe.pinsOverride ? ', pins from MT_EVAL_FST_PINS' : ''}`
|
|
365
|
+
: null;
|
|
366
|
+
|
|
367
|
+
if (!list.length) {
|
|
368
|
+
if (!pin) return [];
|
|
369
|
+
return [`FSTs: the card lists none, but the harness here (${who}) pins one for ${code} — ${pinCapability(code, rec, probe)}`];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
let cannotTell = null;
|
|
373
|
+
if (!section?.ok) {
|
|
374
|
+
cannotTell = `cannot tell whether the harness can use it — asking the harness failed (${section?.error || 'no answer'}).`;
|
|
375
|
+
} else if (probe.status === 'not-installed') {
|
|
376
|
+
cannotTell = 'harness not installed — cannot tell whether it can use this FST (`python3 -m pip install mt-eval-harness`; '
|
|
377
|
+
+ 'then `mt-eval setup --status` lists the FSTs it pins).';
|
|
378
|
+
} else if (probe.status !== 'ok') {
|
|
379
|
+
cannotTell = `cannot tell whether the harness can use it — the harness could not report its FST pins (${probe.error}).`;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const out = [`FSTs — the card records that each exists; whether the eval harness here${who ? ` (${who})` : ''} can download and load it is a separate check:`];
|
|
383
|
+
const matched = pin ? list.filter((f) => isPinnedBuild(f, pin)) : [];
|
|
384
|
+
for (const f of list) {
|
|
385
|
+
const fact = `${f.name || 'FST'} (${f.publisher || 'publisher not stated'})${f.url ? ` <${f.url}>` : ''}: recorded on the card`;
|
|
386
|
+
let harness;
|
|
387
|
+
if (cannotTell) {
|
|
388
|
+
harness = `; ${cannotTell}`;
|
|
389
|
+
} else if (!pin) {
|
|
390
|
+
harness = `, but the harness has no pin for ${code} yet — it cannot download or load it, `
|
|
391
|
+
+ 'so FST metrics will not run for this language.'
|
|
392
|
+
+ (probe.pinsShipped ? '' : ' (This harness predates the FST pins that ship inside it — `python3 -m pip install -U mt-eval-harness`, then re-check.)');
|
|
393
|
+
} else if (matched.includes(f)) {
|
|
394
|
+
harness = `; ${pinCapability(code, rec, probe)}`;
|
|
395
|
+
} else {
|
|
396
|
+
harness = `; not the build the harness pins for ${code} (${pin.repo || pin.name || 'a different build'}).`;
|
|
397
|
+
}
|
|
398
|
+
out.push(` - ${fact}${harness}`);
|
|
399
|
+
}
|
|
400
|
+
if (pin && !matched.length) {
|
|
401
|
+
out.push(` - harness pin for ${code}, not among the card's entries: ${pinCapability(code, rec, probe)}`);
|
|
402
|
+
}
|
|
403
|
+
return out;
|
|
404
|
+
}
|