catherd-cli 1.5.0 → 2.0.0-beta.20261022113309
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/README.md +164 -208
- package/THIRD_PARTY_NOTICES.md +217 -35
- package/catalog/jev.json +27 -1
- package/catalog/models.json +56 -247
- package/package.json +6 -8
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/bin/catherd-mcp +1 -1
- package/plugin/commands/catherd.md +2 -2
- package/plugin/skills/catherd/SKILL.md +97 -421
- package/plugin/skills/catherd-setup/SKILL.md +20 -19
- package/src/adapters/all.ts +0 -4
- package/src/adapters/backend.ts +1 -2
- package/src/adapters/cursor/home.ts +1 -1
- package/src/adapters/cursor/index.ts +4 -4
- package/src/cli.ts +22 -27
- package/src/domain/bars.ts +33 -107
- package/src/domain/blocked.ts +72 -0
- package/src/domain/branch.ts +51 -0
- package/src/domain/brief.ts +21 -6
- package/src/domain/catalog.ts +69 -324
- package/src/domain/cost.ts +15 -30
- package/src/domain/errors.ts +2 -2
- package/src/domain/fail-lines.ts +26 -0
- package/src/domain/failover.ts +37 -87
- package/src/domain/findings.ts +47 -0
- package/src/domain/gate-brief.ts +1 -0
- package/src/domain/ids.ts +13 -2
- package/src/domain/jev.ts +47 -2
- package/src/domain/judge.ts +124 -0
- package/src/domain/lane.ts +22 -3
- package/src/domain/milestone-memory.ts +53 -0
- package/src/domain/notice.ts +25 -8
- package/src/domain/notify.ts +32 -0
- package/src/domain/plan-doc.ts +198 -0
- package/src/domain/plan-lint.ts +141 -0
- package/src/domain/profile-rules.ts +101 -160
- package/src/domain/profile.ts +116 -63
- package/src/domain/record.ts +53 -2
- package/src/domain/role-prompts.ts +131 -45
- package/src/domain/role-scope.ts +4 -34
- package/src/domain/role-tools.ts +2 -2
- package/src/domain/roles.ts +2 -0
- package/src/domain/route.ts +17 -0
- package/src/domain/rules.ts +33 -0
- package/src/domain/run-events.ts +298 -0
- package/src/domain/run-machine.ts +668 -0
- package/src/domain/run-state.ts +1310 -0
- package/src/domain/scorecard.ts +197 -0
- package/src/domain/select.ts +151 -146
- package/src/domain/slopalytics.ts +72 -0
- package/src/entry/answer-command.ts +39 -0
- package/src/entry/catalog-command.ts +25 -174
- package/src/entry/check-bin.ts +17 -0
- package/src/entry/cli-kit.ts +1 -1
- package/src/entry/daemon-bin.ts +22 -0
- package/src/entry/glyphs.ts +15 -2
- package/src/entry/init-command.ts +18 -68
- package/src/entry/knowledge-command.ts +45 -4
- package/src/entry/lock-command.ts +6 -7
- package/src/entry/mcp/role-server.ts +18 -0
- package/src/entry/mcp/server.ts +13 -40
- package/src/entry/mcp/session-tools.ts +149 -0
- package/src/entry/panel/openapi.ts +189 -0
- package/src/entry/panel/server.ts +410 -0
- package/src/entry/panel-command.ts +68 -0
- package/src/entry/profile-command.ts +10 -14
- package/src/entry/role-cli-command.ts +47 -3
- package/src/entry/runs-command.ts +3 -19
- package/src/entry/scorecard-command.ts +187 -0
- package/src/entry/supervise-bin.ts +1 -1
- package/src/infra/env.ts +0 -2
- package/src/infra/git.ts +64 -0
- package/src/infra/heavy-lock.ts +45 -7
- package/src/infra/launch.ts +63 -7
- package/src/infra/memory.ts +45 -0
- package/src/infra/ntfy.ts +40 -0
- package/src/infra/open-url.ts +42 -0
- package/src/infra/paths.ts +2 -2
- package/src/infra/slopalytics.ts +30 -0
- package/src/infra/store.ts +6 -1
- package/src/services/admission.ts +24 -9
- package/src/services/backends.ts +4 -6
- package/src/services/capture.ts +1 -22
- package/src/services/catalog-service.ts +155 -262
- package/src/services/credentials.ts +4 -20
- package/src/services/daemon.ts +181 -0
- package/src/services/dispatch-service.ts +5 -1
- package/src/services/doctor-backends.ts +1 -1
- package/src/services/doctor-checks.ts +47 -0
- package/src/services/doctor.ts +4 -9
- package/src/services/failover-rungs.ts +109 -0
- package/src/services/finalize.ts +12 -0
- package/src/services/gate-env.ts +1 -2
- package/src/services/gate-service.ts +21 -6
- package/src/services/jev-service.ts +2 -4
- package/src/services/lane-service.ts +34 -3
- package/src/services/milestone-check.ts +433 -0
- package/src/services/milestone-tree.ts +170 -0
- package/src/services/milestones.ts +35 -8
- package/src/services/notifier.ts +69 -2
- package/src/services/notify-service.ts +26 -0
- package/src/services/panel-setup.ts +116 -0
- package/src/services/plan-input.ts +151 -0
- package/src/services/ports.ts +30 -4
- package/src/services/preflight.ts +82 -8
- package/src/services/profile-service.ts +30 -3
- package/src/services/profile-store.ts +8 -3
- package/src/services/protocol.ts +4 -0
- package/src/services/provenance.ts +31 -90
- package/src/services/questions.ts +9 -0
- package/src/services/reconcile.ts +11 -0
- package/src/services/routing-service.ts +26 -15
- package/src/services/run-briefs.ts +477 -0
- package/src/services/run-client.ts +217 -0
- package/src/services/run-effects.ts +837 -0
- package/src/services/run-events-store.ts +174 -0
- package/src/services/run-evidence.ts +1 -1
- package/src/services/run-read.ts +221 -0
- package/src/services/run-report.ts +176 -0
- package/src/services/run-service.ts +55 -6
- package/src/services/run-store.ts +54 -7
- package/src/services/scorecard-metrics.ts +86 -0
- package/src/services/scorecard.ts +382 -0
- package/src/services/summary.ts +27 -3
- package/src/services/tree-path.ts +27 -0
- package/src/services/workspace-admission.ts +5 -13
- package/src/services/workspace-service.ts +5 -0
- package/web/dist/assets/Brand-PKlUX5TE.js +1 -0
- package/web/dist/assets/Brand-PKlUX5TE.js.br +0 -0
- package/web/dist/assets/Brand-PKlUX5TE.js.gz +0 -0
- package/web/dist/assets/Modal-Dxc5so53.js +1 -0
- package/web/dist/assets/Modal-Dxc5so53.js.br +0 -0
- package/web/dist/assets/Modal-Dxc5so53.js.gz +0 -0
- package/web/dist/assets/Pressable-C8-T6Ed_.js +1 -0
- package/web/dist/assets/StatusChip-B5xfI_Mn.js +153 -0
- package/web/dist/assets/StatusChip-B5xfI_Mn.js.br +0 -0
- package/web/dist/assets/StatusChip-B5xfI_Mn.js.gz +0 -0
- package/web/dist/assets/_app-Ea5cpyKe.js +1 -0
- package/web/dist/assets/_app-Ea5cpyKe.js.br +0 -0
- package/web/dist/assets/_app-Ea5cpyKe.js.gz +0 -0
- package/web/dist/assets/_runId-pTG0fXVo.js +1 -0
- package/web/dist/assets/_runId-pTG0fXVo.js.br +0 -0
- package/web/dist/assets/_runId-pTG0fXVo.js.gz +0 -0
- package/web/dist/assets/drawer-Du3cdlok.js +1 -0
- package/web/dist/assets/drawer-Du3cdlok.js.br +0 -0
- package/web/dist/assets/drawer-Du3cdlok.js.gz +0 -0
- package/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/web/dist/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
- package/web/dist/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
- package/web/dist/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
- package/web/dist/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
- package/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/web/dist/assets/index-B6GiwDkh.js +11 -0
- package/web/dist/assets/index-B6GiwDkh.js.br +0 -0
- package/web/dist/assets/index-B6GiwDkh.js.gz +0 -0
- package/web/dist/assets/index-C5IbylvM.css +2 -0
- package/web/dist/assets/index-C5IbylvM.css.br +0 -0
- package/web/dist/assets/index-C5IbylvM.css.gz +0 -0
- package/web/dist/assets/label-DFSYBc62.js +1 -0
- package/web/dist/assets/label-DFSYBc62.js.br +0 -0
- package/web/dist/assets/label-DFSYBc62.js.gz +0 -0
- package/web/dist/assets/link-D8EcKZUt.js +1 -0
- package/web/dist/assets/link-D8EcKZUt.js.br +0 -0
- package/web/dist/assets/link-D8EcKZUt.js.gz +0 -0
- package/web/dist/assets/login-DaN5HPPW.js +1 -0
- package/web/dist/assets/login-DaN5HPPW.js.br +0 -0
- package/web/dist/assets/login-DaN5HPPW.js.gz +0 -0
- package/web/dist/assets/query-Cu--ccT3.js +1 -0
- package/web/dist/assets/questions-shoO9OVj.js +1 -0
- package/web/dist/assets/questions-shoO9OVj.js.br +0 -0
- package/web/dist/assets/questions-shoO9OVj.js.gz +0 -0
- package/web/dist/assets/react-query.gen-Bu2yV-qJ.js +12 -0
- package/web/dist/assets/react-query.gen-Bu2yV-qJ.js.br +0 -0
- package/web/dist/assets/react-query.gen-Bu2yV-qJ.js.gz +0 -0
- package/web/dist/assets/rolldown-runtime-XKyaHtl9.js +1 -0
- package/web/dist/assets/run-status-C0DRuV7U.js +1 -0
- package/web/dist/assets/runs-C3rg2zZc.js +1 -0
- package/web/dist/assets/runs-C3rg2zZc.js.br +0 -0
- package/web/dist/assets/runs-C3rg2zZc.js.gz +0 -0
- package/web/dist/assets/setup-5I4Vl9fR.js +1 -0
- package/web/dist/assets/setup-5I4Vl9fR.js.br +0 -0
- package/web/dist/assets/setup-5I4Vl9fR.js.gz +0 -0
- package/web/dist/assets/table-BOxXhqS4.js +1 -0
- package/web/dist/assets/table-BOxXhqS4.js.br +0 -0
- package/web/dist/assets/table-BOxXhqS4.js.gz +0 -0
- package/web/dist/assets/textfield-BzRFkxBF.js +1 -0
- package/web/dist/assets/textfield-BzRFkxBF.js.br +0 -0
- package/web/dist/assets/textfield-BzRFkxBF.js.gz +0 -0
- package/web/dist/assets/time--9kSQbbp.js +1 -0
- package/web/dist/assets/use-css-variable-CpnN9WtA.js +5 -0
- package/web/dist/assets/use-css-variable-CpnN9WtA.js.br +0 -0
- package/web/dist/assets/use-css-variable-CpnN9WtA.js.gz +0 -0
- package/web/dist/assets/useMutation-CVPKlR34.js +1 -0
- package/web/dist/assets/useMutation-CVPKlR34.js.br +0 -0
- package/web/dist/assets/useMutation-CVPKlR34.js.gz +0 -0
- package/web/dist/index.html +15 -0
- package/catalog/ATTRIBUTION.md +0 -44
- package/catalog/scores.json +0 -3202
- package/catalog/sources.json +0 -97
- package/src/adapters/antigravity/events.ts +0 -131
- package/src/adapters/antigravity/home.ts +0 -46
- package/src/adapters/antigravity/index.ts +0 -335
- package/src/adapters/antigravity/models.ts +0 -37
- package/src/adapters/grok/events.ts +0 -112
- package/src/adapters/grok/home.ts +0 -187
- package/src/adapters/grok/index.ts +0 -358
- package/src/adapters/grok/models.ts +0 -48
- package/src/domain/calibration.ts +0 -132
- package/src/domain/sources.ts +0 -144
- package/src/entry/mcp/dispatch-tools.ts +0 -52
- package/src/entry/mcp/lane-tools.ts +0 -131
- package/src/entry/mcp/protocol-tools.ts +0 -63
- package/src/entry/mcp/push-tools.ts +0 -16
- package/src/entry/mcp/run-tools.ts +0 -164
- package/src/entry/mcp/setup-tools.ts +0 -109
- package/src/entry/mcp/workspace-tools.ts +0 -142
- package/src/entry/tui/commands.ts +0 -563
- package/src/entry/tui/effects.ts +0 -385
- package/src/entry/tui/fixtures.ts +0 -637
- package/src/entry/tui/profile-edits.ts +0 -146
- package/src/entry/tui/profile-tree.ts +0 -429
- package/src/entry/tui/providers/app.tsx +0 -162
- package/src/entry/tui/providers/boundary.tsx +0 -57
- package/src/entry/tui/providers/data.tsx +0 -160
- package/src/entry/tui/providers/keymap.tsx +0 -166
- package/src/entry/tui/providers/root.tsx +0 -45
- package/src/entry/tui/providers/theme.tsx +0 -17
- package/src/entry/tui/providers/toast.tsx +0 -77
- package/src/entry/tui/run.tsx +0 -114
- package/src/entry/tui/state.ts +0 -400
- package/src/entry/tui/stories.ts +0 -114
- package/src/entry/tui/text.ts +0 -130
- package/src/entry/tui/theme.ts +0 -140
- package/src/entry/tui/views/app.tsx +0 -208
- package/src/entry/tui/views/dialogs.tsx +0 -41
- package/src/entry/tui/views/profile-actions.ts +0 -331
- package/src/entry/tui/views/profiles.tsx +0 -520
- package/src/entry/tui/views/runs.tsx +0 -619
- package/src/entry/tui/views/save-dialog.tsx +0 -344
- package/src/entry/tui/views/status.tsx +0 -213
- package/src/entry/tui/widgets/chrome.tsx +0 -114
- package/src/entry/tui/widgets/dialog-confirm.tsx +0 -58
- package/src/entry/tui/widgets/dialog-prompt.tsx +0 -52
- package/src/entry/tui/widgets/dialog-select.tsx +0 -212
- package/src/entry/tui/widgets/dialog.tsx +0 -68
- package/src/entry/tui/widgets/line.tsx +0 -92
- package/src/entry/tui/widgets/list.tsx +0 -197
- package/src/entry/tui/widgets/spinner.tsx +0 -49
- package/src/entry/tui/widgets/toast.tsx +0 -62
- package/src/infra/sources/arena.ts +0 -95
- package/src/infra/sources/artificial-analysis.ts +0 -119
- package/src/infra/sources/cache.ts +0 -126
- package/src/infra/sources/csv.ts +0 -38
- package/src/infra/sources/epoch.ts +0 -82
- package/src/infra/sources/http.ts +0 -75
- package/src/infra/sources/litellm.ts +0 -58
- package/src/infra/sources/models-dev.ts +0 -59
- package/src/infra/sources/openrouter-endpoints.ts +0 -52
- package/src/infra/sources/openrouter-models.ts +0 -60
- package/src/infra/sources/rows.ts +0 -38
- package/src/infra/sources/vectara.ts +0 -69
- package/src/infra/sources/zip.ts +0 -48
- package/src/services/catalog-refresh.ts +0 -201
- package/src/services/doctor-sources.ts +0 -76
- package/src/services/runs-page.ts +0 -290
- package/src/services/source-derive.ts +0 -397
- package/src/services/source-sync.ts +0 -312
- package/src/services/standins.ts +0 -238
- package/src/services/treat-likes.ts +0 -113
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type { DiscoveredModel } from "../backend.ts";
|
|
2
|
-
|
|
3
|
-
/** A model's efforts and context as the catalog ships them for grok (`on.grok`). */
|
|
4
|
-
export type ShippedGrok = Record<string, { efforts: string[]; context: number | null }>;
|
|
5
|
-
|
|
6
|
-
export interface GrokModels {
|
|
7
|
-
/** null when `grok models` printed none of its auth lines */
|
|
8
|
-
loggedIn: boolean | null;
|
|
9
|
-
/** how: a Grok login (SuperGrok, X Premium+) or `XAI_API_KEY` */
|
|
10
|
-
login?: "Grok" | "API key";
|
|
11
|
-
defaultModel: string | null;
|
|
12
|
-
models: DiscoveredModel[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* `grok models` (research §3.4 [run]): an auth line first ("You are logged in with …", "You are using
|
|
17
|
-
* XAI_API_KEY.", "You are not authenticated."), `Default model: <id>`, then `Available models:` with
|
|
18
|
-
* ` * <id> (default)` or ` - <id>` lines. It lists no efforts: they come from the catalog's `on.grok`
|
|
19
|
-
* (spec 1.3 §5.2), and a model the catalog lacks lists none.
|
|
20
|
-
*/
|
|
21
|
-
export function parseGrokModels(text: string, shipped: ShippedGrok = {}): GrokModels {
|
|
22
|
-
const login = /You are using XAI_API_KEY/.test(text)
|
|
23
|
-
? "API key"
|
|
24
|
-
: /You are logged in with/.test(text)
|
|
25
|
-
? "Grok"
|
|
26
|
-
: undefined;
|
|
27
|
-
const loggedIn = login ? true : /You are not authenticated/.test(text) ? false : null;
|
|
28
|
-
const at = text.indexOf("Available models:");
|
|
29
|
-
const ids =
|
|
30
|
-
at === -1
|
|
31
|
-
? []
|
|
32
|
-
: text
|
|
33
|
-
.slice(at)
|
|
34
|
-
.split("\n")
|
|
35
|
-
.map((l) => /^\s*[*-]\s+(\S+)/.exec(l)?.[1])
|
|
36
|
-
.filter((id): id is string => id !== undefined);
|
|
37
|
-
return {
|
|
38
|
-
loggedIn,
|
|
39
|
-
...(login ? { login } : {}),
|
|
40
|
-
defaultModel: /Default model:\s*(\S+)/.exec(text)?.[1] ?? null,
|
|
41
|
-
models: ids.map((id) => ({
|
|
42
|
-
id,
|
|
43
|
-
efforts: [...(shipped[id]?.efforts ?? [])],
|
|
44
|
-
context: shipped[id]?.context ?? null,
|
|
45
|
-
imageIn: false,
|
|
46
|
-
})),
|
|
47
|
-
};
|
|
48
|
-
}
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import type { Dim } from "./catalog.ts";
|
|
2
|
-
import type { SourceId } from "./sources.ts";
|
|
3
|
-
|
|
4
|
-
/** Spec 1.2 §4.2: a source needs this many rungs shared with the anchor for a dimension. */
|
|
5
|
-
export const MIN_SHARED = 5;
|
|
6
|
-
/** Spec 1.2 §4.2: a fit below this R² is not used. */
|
|
7
|
-
export const MIN_R2 = 0.5;
|
|
8
|
-
|
|
9
|
-
/** One number a source parser returns: `field` of `source`, and the benchmark it names. */
|
|
10
|
-
export interface FieldRef {
|
|
11
|
-
source: SourceId;
|
|
12
|
-
field: string;
|
|
13
|
-
benchmark: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Spec 1.2 §4.1: each dimension's anchor, the unit its bars are in, and the sources calibrated onto it.
|
|
18
|
-
* `shipped` is the hand-typed value `catalog/scores.json` carries, published for that rung (every anchor is
|
|
19
|
-
* keyless, spec 1.2 §4.1).
|
|
20
|
-
*/
|
|
21
|
-
export const DIM_SOURCES: Record<Dim, { anchor: FieldRef | "shipped"; others: FieldRef[] }> = {
|
|
22
|
-
repo_code: {
|
|
23
|
-
anchor: "shipped",
|
|
24
|
-
others: [
|
|
25
|
-
{
|
|
26
|
-
source: "artificial-analysis",
|
|
27
|
-
field: "livecodebench",
|
|
28
|
-
benchmark: "LiveCodeBench (Artificial Analysis)",
|
|
29
|
-
},
|
|
30
|
-
{ source: "artificial-analysis", field: "scicode", benchmark: "SciCode (Artificial Analysis)" },
|
|
31
|
-
{
|
|
32
|
-
source: "artificial-analysis",
|
|
33
|
-
field: "artificial_analysis_coding_index",
|
|
34
|
-
benchmark: "Artificial Analysis Coding Index",
|
|
35
|
-
},
|
|
36
|
-
{ source: "epoch", field: "frontiercode", benchmark: "FrontierCode (Epoch AI)" },
|
|
37
|
-
// spec 1.5 plan 24: per model and effort, it scores a new release the day it ships (the identity run's
|
|
38
|
-
// GPT-6.1 Sol: medium 47.8, about Astra low)
|
|
39
|
-
{
|
|
40
|
-
source: "artificial-analysis",
|
|
41
|
-
field: "artificial_analysis_intelligence_index",
|
|
42
|
-
benchmark: "Artificial Analysis Intelligence Index",
|
|
43
|
-
},
|
|
44
|
-
],
|
|
45
|
-
},
|
|
46
|
-
// plan 14 Ruling C-2: the vendors' Terminal-Bench 4.0 values, not Epoch's Terminal-Bench 2.0, which shares
|
|
47
|
-
// no rung with them; Epoch is fitted onto them once it covers five of their rungs
|
|
48
|
-
terminal: {
|
|
49
|
-
anchor: "shipped",
|
|
50
|
-
others: [
|
|
51
|
-
{ source: "epoch", field: "terminalbench", benchmark: "Terminal-Bench (Epoch AI)" },
|
|
52
|
-
{
|
|
53
|
-
source: "artificial-analysis",
|
|
54
|
-
field: "terminalbench_v2_1",
|
|
55
|
-
benchmark: "Terminal-Bench 2.1 (Artificial Analysis)",
|
|
56
|
-
},
|
|
57
|
-
],
|
|
58
|
-
},
|
|
59
|
-
honesty: {
|
|
60
|
-
anchor: "shipped",
|
|
61
|
-
others: [
|
|
62
|
-
{ source: "vectara", field: "factual_consistency", benchmark: "Vectara factual consistency rate" },
|
|
63
|
-
{ source: "arena", field: "agent_tool_hallucination", benchmark: "Arena agent tool hallucination" },
|
|
64
|
-
],
|
|
65
|
-
},
|
|
66
|
-
agentic: {
|
|
67
|
-
anchor: { source: "arena", field: "agent", benchmark: "Arena agent, net improvement" },
|
|
68
|
-
others: [
|
|
69
|
-
{ source: "arena", field: "agent_task_outcome_explicit", benchmark: "Arena agent task outcome" },
|
|
70
|
-
{ source: "arena", field: "agent_bash_recovery_steps", benchmark: "Arena agent bash recovery" },
|
|
71
|
-
{ source: "artificial-analysis", field: "tau2", benchmark: "tau2-bench (Artificial Analysis)" },
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
steer: {
|
|
75
|
-
anchor: { source: "arena", field: "agent_steerability", benchmark: "Arena agent steerability" },
|
|
76
|
-
others: [],
|
|
77
|
-
},
|
|
78
|
-
frontend: {
|
|
79
|
-
anchor: { source: "arena", field: "webdev", benchmark: "Arena WebDev rating" },
|
|
80
|
-
others: [{ source: "epoch", field: "webdev", benchmark: "WebDev Arena (Epoch AI)" }],
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
export interface LineFit {
|
|
85
|
-
a: number;
|
|
86
|
-
b: number;
|
|
87
|
-
r2: number;
|
|
88
|
-
n: number;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Least squares `anchor = a·x + b` over `[x, anchor]` pairs; null with fewer than 2 pairs or no spread in x. */
|
|
92
|
-
export function fitLine(pairs: readonly (readonly [number, number])[]): LineFit | null {
|
|
93
|
-
const n = pairs.length;
|
|
94
|
-
if (n < 2) return null;
|
|
95
|
-
const mx = pairs.reduce((s, [x]) => s + x, 0) / n;
|
|
96
|
-
const my = pairs.reduce((s, [, y]) => s + y, 0) / n;
|
|
97
|
-
let sxx = 0;
|
|
98
|
-
let sxy = 0;
|
|
99
|
-
let syy = 0;
|
|
100
|
-
for (const [x, y] of pairs) {
|
|
101
|
-
sxx += (x - mx) ** 2;
|
|
102
|
-
sxy += (x - mx) * (y - my);
|
|
103
|
-
syy += (y - my) ** 2;
|
|
104
|
-
}
|
|
105
|
-
if (sxx === 0) return null;
|
|
106
|
-
const a = sxy / sxx;
|
|
107
|
-
const b = my - a * mx;
|
|
108
|
-
const res = pairs.reduce((s, [x, y]) => s + (y - (a * x + b)) ** 2, 0);
|
|
109
|
-
return { a, b, r2: syy === 0 ? 0 : 1 - res / syy, n };
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Spec 1.2 §4.2: the fit of `other` onto `anchor` over the keys both hold, and whether it may be used: at
|
|
114
|
-
* least MIN_SHARED shared rungs and an R² of at least MIN_R2. `why` says why not.
|
|
115
|
-
*/
|
|
116
|
-
export function calibrate(
|
|
117
|
-
anchor: ReadonlyMap<string, number>,
|
|
118
|
-
other: ReadonlyMap<string, number>,
|
|
119
|
-
): { fit: LineFit | null; n: number; used: boolean; why?: string } {
|
|
120
|
-
const pairs: [number, number][] = [];
|
|
121
|
-
for (const [k, x] of other) {
|
|
122
|
-
const y = anchor.get(k);
|
|
123
|
-
if (y !== undefined) pairs.push([x, y]);
|
|
124
|
-
}
|
|
125
|
-
const n = pairs.length;
|
|
126
|
-
if (n < MIN_SHARED)
|
|
127
|
-
return { fit: null, n, used: false, why: `${n} shared rungs; a fit needs ${MIN_SHARED}` };
|
|
128
|
-
const fit = fitLine(pairs);
|
|
129
|
-
if (!fit) return { fit: null, n, used: false, why: "the source gives every shared rung the same value" };
|
|
130
|
-
if (fit.r2 < MIN_R2) return { fit, n, used: false, why: `R² ${fit.r2.toFixed(2)} is below ${MIN_R2}` };
|
|
131
|
-
return { fit, n, used: true };
|
|
132
|
-
}
|
package/src/domain/sources.ts
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { DIMS, type Family, FamilyFactsSchema, ScoreSchema } from "./catalog.ts";
|
|
3
|
-
|
|
4
|
-
/** Spec 1.2 §3.1: the sources `catherd catalog sync` reads, one cache file each in `<data>/sources/`. */
|
|
5
|
-
export const SOURCE_IDS = [
|
|
6
|
-
"models-dev",
|
|
7
|
-
"openrouter-models",
|
|
8
|
-
"openrouter-endpoints",
|
|
9
|
-
"litellm",
|
|
10
|
-
"arena",
|
|
11
|
-
"vectara",
|
|
12
|
-
"epoch",
|
|
13
|
-
"artificial-analysis",
|
|
14
|
-
] as const;
|
|
15
|
-
export type SourceId = (typeof SOURCE_IDS)[number];
|
|
16
|
-
|
|
17
|
-
/** `catalog/sources.json`: each source with its license and attribution line, the id aliases, default efforts. */
|
|
18
|
-
export const SourcesFileSchema = z.looseObject({
|
|
19
|
-
schema: z.literal(1),
|
|
20
|
-
version: z.string(),
|
|
21
|
-
sources: z.array(
|
|
22
|
-
z.object({
|
|
23
|
-
id: z.enum(SOURCE_IDS),
|
|
24
|
-
name: z.string(),
|
|
25
|
-
url: z.url(),
|
|
26
|
-
license: z.string(),
|
|
27
|
-
attribution: z.string(),
|
|
28
|
-
keyed: z.boolean(),
|
|
29
|
-
}),
|
|
30
|
-
),
|
|
31
|
-
/** a source's irregular model id → the catalog's id (spec 1.2 §3.4) */
|
|
32
|
-
aliases: z.record(z.string(), z.string()),
|
|
33
|
-
/** family id → the effort a source that names none means; a family not listed means `high` */
|
|
34
|
-
defaultEffort: z.record(z.string(), z.string()),
|
|
35
|
-
});
|
|
36
|
-
export type SourcesFile = z.infer<typeof SourcesFileSchema>;
|
|
37
|
-
|
|
38
|
-
/** Effort words as catherd writes them, weakest first; a source's spelling (`xHigh`, `MAX`) is folded onto these. */
|
|
39
|
-
export const EFFORT_ORDER = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"] as const;
|
|
40
|
-
|
|
41
|
-
/** `s` as a catherd effort word (`xHigh` → `xhigh`), or null when it is not one. */
|
|
42
|
-
export function effortWord(s: string): string | null {
|
|
43
|
-
const w = s.trim().toLowerCase();
|
|
44
|
-
return (EFFORT_ORDER as readonly string[]).includes(w) ? w : null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Spec 1.2 §3.4: a source's model id in catherd's form: without a `vendor/` prefix, lower case, and each run
|
|
49
|
-
* of dots and blanks as one `-` (`openai/gpt-5.6-sol` and `GPT 5.6 Sol` → `gpt-5-6-sol`).
|
|
50
|
-
*/
|
|
51
|
-
export function normalizeId(id: string): string {
|
|
52
|
-
return id
|
|
53
|
-
.slice(id.lastIndexOf("/") + 1)
|
|
54
|
-
.trim()
|
|
55
|
-
.toLowerCase()
|
|
56
|
-
.replace(/[\s.]+/g, "-")
|
|
57
|
-
.replace(/-+/g, "-");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** A parser's `rung`: `<source's model id>#<effort>`, or the bare id when the source names no effort. */
|
|
61
|
-
export function splitSourceRung(rung: string): { id: string; effort: string | null } {
|
|
62
|
-
const hash = rung.lastIndexOf("#");
|
|
63
|
-
return hash < 0 ? { id: rung, effort: null } : { id: rung.slice(0, hash), effort: rung.slice(hash + 1) };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface IdMapper {
|
|
67
|
-
/** the id pairs are keyed by: normalized, then through the alias table */
|
|
68
|
-
key(id: string): string;
|
|
69
|
-
/** the catalog family a source id names, directly, by a backend's own id or through an alias; never guessed */
|
|
70
|
-
family(id: string): Family | null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Spec 1.2 §3.4: matches a source's ids to the catalog's families. */
|
|
74
|
-
export function idMapper(families: Family[], aliases: Record<string, string>): IdMapper {
|
|
75
|
-
const alias = new Map(Object.entries(aliases).map(([from, to]) => [normalizeId(from), normalizeId(to)]));
|
|
76
|
-
const byKey = new Map<string, Family>();
|
|
77
|
-
for (const f of families) {
|
|
78
|
-
for (const on of Object.values(f.on)) if (on) byKey.set(normalizeId(on.id), f);
|
|
79
|
-
byKey.set(normalizeId(f.id), f);
|
|
80
|
-
}
|
|
81
|
-
const key = (id: string) => {
|
|
82
|
-
const n = normalizeId(id);
|
|
83
|
-
return alias.get(n) ?? n;
|
|
84
|
-
};
|
|
85
|
-
return { key, family: (id) => byKey.get(key(id)) ?? null };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Spec 1.2 §3.4: the effort a source that names none means for `family`. */
|
|
89
|
-
export const defaultEffortOf = (sources: SourcesFile, family: Family): string =>
|
|
90
|
-
sources.defaultEffort[family.id] ?? "high";
|
|
91
|
-
|
|
92
|
-
const order = (e: string) => (EFFORT_ORDER as readonly string[]).indexOf(e);
|
|
93
|
-
|
|
94
|
-
/** Every effort the family offers on any backend, weakest first (spec 1.2 §4.3 `adjacent`). */
|
|
95
|
-
export function familyEfforts(f: Family): string[] {
|
|
96
|
-
const all = new Set<string>();
|
|
97
|
-
for (const on of Object.values(f.on)) for (const e of on?.efforts ?? []) all.add(e);
|
|
98
|
-
return [...all].filter((e) => order(e) >= 0).sort((a, b) => order(a) - order(b));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** Spec 1.2 §4.2: one source's fit onto a dimension's anchor, used or not, and why not. */
|
|
102
|
-
export const FitRowSchema = z.object({
|
|
103
|
-
dim: z.enum(DIMS),
|
|
104
|
-
source: z.string(),
|
|
105
|
-
field: z.string(),
|
|
106
|
-
n: z.number().int(),
|
|
107
|
-
a: z.number().nullable(),
|
|
108
|
-
b: z.number().nullable(),
|
|
109
|
-
r2: z.number().nullable(),
|
|
110
|
-
used: z.boolean(),
|
|
111
|
-
why: z.string().optional(),
|
|
112
|
-
});
|
|
113
|
-
export type FitRow = z.infer<typeof FitRowSchema>;
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* `<data>/sources/derived.json`: what a sync made of the cached answers. `scores` are the synced values the
|
|
117
|
-
* catalog layers over the shipped ones (`measured`, `calibrated`, `adjacent`), `facts` each family's facts,
|
|
118
|
-
* `unmatched` each source's ids no family matched (never guessed), `warnings` the cross-checks that failed.
|
|
119
|
-
*/
|
|
120
|
-
export const DerivedSchema = z.looseObject({
|
|
121
|
-
schema: z.literal(1),
|
|
122
|
-
builtAt: z.iso.datetime(),
|
|
123
|
-
scores: z.array(ScoreSchema),
|
|
124
|
-
facts: z.record(z.string(), FamilyFactsSchema),
|
|
125
|
-
fits: z.array(FitRowSchema),
|
|
126
|
-
unmatched: z.record(z.string(), z.array(z.string())),
|
|
127
|
-
warnings: z.array(z.string()),
|
|
128
|
-
/** canonical rung → the Artificial Analysis stand-in features (spec 1.2 §6.3); a file before plan 14 has none */
|
|
129
|
-
features: z.record(z.string(), z.record(z.string(), z.number())).default({}),
|
|
130
|
-
});
|
|
131
|
-
export type Derived = z.infer<typeof DerivedSchema>;
|
|
132
|
-
|
|
133
|
-
/** The effort of `have` nearest `target` (the weaker one on a tie); null when `have` holds no effort word. */
|
|
134
|
-
export function nearestEffort(target: string, have: string[]): string | null {
|
|
135
|
-
const t = order(target);
|
|
136
|
-
let best: string | null = null;
|
|
137
|
-
for (const e of have) {
|
|
138
|
-
const i = order(e);
|
|
139
|
-
if (i < 0 || t < 0) continue;
|
|
140
|
-
const b = best === null ? Number.POSITIVE_INFINITY : Math.abs(order(best) - t);
|
|
141
|
-
if (Math.abs(i - t) < b || (Math.abs(i - t) === b && i < order(best as string))) best = e;
|
|
142
|
-
}
|
|
143
|
-
return best;
|
|
144
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { ID_PATTERN } from "../../domain/ids.ts";
|
|
4
|
-
import { ROLES } from "../../domain/roles.ts";
|
|
5
|
-
import { cancel, dispatch } from "../../services/dispatch-service.ts";
|
|
6
|
-
import { peek } from "../../services/peek.ts";
|
|
7
|
-
import type { Deps } from "../../services/ports.ts";
|
|
8
|
-
import { handle } from "./result.ts";
|
|
9
|
-
|
|
10
|
-
export function registerDispatchTools(server: McpServer, deps: Deps): void {
|
|
11
|
-
server.registerTool(
|
|
12
|
-
"dispatch",
|
|
13
|
-
{
|
|
14
|
-
description:
|
|
15
|
-
"Start one role on a process backend (codex, claude-code, opencode) and return at launch, in about a second, with { dispatched: { name, role, rung, dispatchId, admittedAt }, hints }; the role runs on. When it finishes, catherd sends this session a <cross-session-message from-name=\"catherd\"> naming the run, the role and its status, and result(run, name) reads the record. Dispatch every independent role one after another, then end your turn. The brief is the text itself; dispatch appends the role's reply contract (reply length and the STATUS line), so do not write it. With lane, the lane file's Owns: paths guard against overlapping lanes, a lane not yet routed is routed first (a rung off its routed ladder starts at the routed rung, with a hint), and rung may be left out: the lane runs at its current rung (its route, or the rung its last climb gave). Without lane, rung is required (route(run, role) gives it), and the choice is recorded in routes.jsonl. thread resumes that role's thread for its own fix round: the thread its catherd message names, or \"latest\" for the name's last thread; a thread the name never ran on in this run is refused (E_ADMIT_THREAD). Refused with a structured error (E_ADMIT_*, E_RUN_BUDGET, E_BACKEND_*) before anything starts. Call it from the main thread.",
|
|
16
|
-
inputSchema: {
|
|
17
|
-
run: z.string(),
|
|
18
|
-
role: z.enum(ROLES),
|
|
19
|
-
name: z.string().regex(ID_PATTERN),
|
|
20
|
-
brief: z.string().min(1),
|
|
21
|
-
rung: z.string().min(3).optional(),
|
|
22
|
-
thread: z.string().optional(),
|
|
23
|
-
lane: z.string().regex(ID_PATTERN).optional(),
|
|
24
|
-
next: z.string().optional(),
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
(a) => handle(() => dispatch(deps, a)),
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
server.registerTool(
|
|
31
|
-
"peek",
|
|
32
|
-
{
|
|
33
|
-
description:
|
|
34
|
-
"A look at the runs, without waiting: with run, that run (and this session becomes its owner); without, every run this session owns, else the newest. Per run, first the open owner questions, which are the owner's questions: push them to the owner, relay the owner's answer with answer(run, milestone, answer), and go on with the work that does not depend on them. Then each live role with its rung, seconds since it started and its last event (the last command, file edit or message line); every finished record not yet read, as the first line of catherd's message; the latest native Claude run; the run's next step; protocol, the milestone loop's next step and its six-line checklist; and verifier, the verifier's latest gate step. name narrows it to one role. It never marks a record read: result(run, name) does. Call it when the user asks how it is going, when a decision needs the other roles' state, or once after run_start on a resumed run; never in a loop. actionable is false, with its reason, when nothing is yours to do (only roles are live on the step, or the owner's answer comes first): end the turn with no tool call, and the next catherd message wakes you.",
|
|
35
|
-
inputSchema: {
|
|
36
|
-
run: z.string().optional(),
|
|
37
|
-
name: z.string().regex(ID_PATTERN).optional(),
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
(a) => handle(() => peek(deps, a)),
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
server.registerTool(
|
|
44
|
-
"cancel",
|
|
45
|
-
{
|
|
46
|
-
description:
|
|
47
|
-
"Stop a live dispatch (interrupt, then SIGTERM, then SIGKILL) and return its record, marked cancelled and read, and hints.",
|
|
48
|
-
inputSchema: { run: z.string(), name: z.string().regex(ID_PATTERN) },
|
|
49
|
-
},
|
|
50
|
-
(a) => handle(() => cancel(deps, a.run, a.name, { read: true })),
|
|
51
|
-
);
|
|
52
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { ID_PATTERN } from "../../domain/ids.ts";
|
|
4
|
-
import { ROLES } from "../../domain/roles.ts";
|
|
5
|
-
import { CatherdError } from "../../domain/errors.ts";
|
|
6
|
-
import { CLIMB_REASONS } from "../../domain/route.ts";
|
|
7
|
-
import { LANE_FIELDS, type LaneField, laneSet, ownsAdd } from "../../services/lane-edit.ts";
|
|
8
|
-
import { ask, climb, LAND_SKIPS, land, route, routeLanes } from "../../services/lane-service.ts";
|
|
9
|
-
import type { Deps } from "../../services/ports.ts";
|
|
10
|
-
import { preflight } from "../../services/preflight.ts";
|
|
11
|
-
import { handle } from "./result.ts";
|
|
12
|
-
|
|
13
|
-
export function registerLaneTools(server: McpServer, deps: Deps): void {
|
|
14
|
-
server.registerTool(
|
|
15
|
-
"route",
|
|
16
|
-
{
|
|
17
|
-
description:
|
|
18
|
-
"The rung for a lane (from the lane file's Kind/Difficulty lines when it declares both, else Jev, else the profile default), or with lanes (several lane files) for each of them in one call, Jev asked about all at once ({ routes: [...] }, in order), or, without a lane file, a role's default rung, with the ladder above it: only rungs at least as strong as the start. Rungs are backend:model#effort; a claude: rung comes with the agent to run it as. Returns rung, ladder, backend, agent and why: one line naming the decision, where Jev disagreed with the lane's header, when no rung clears the bar (and the closest), and when a tie between quotas decided. Every decision, with its provenance (each threshold, the value used, its source and date, the rung's cost and run evidence), is written to the run's routes.jsonl. A lane whose Kind: or Difficulty: the catalog does not know is refused with E_LANE_INVALID.",
|
|
19
|
-
inputSchema: {
|
|
20
|
-
run: z.string(),
|
|
21
|
-
lane_file: z.string().optional(),
|
|
22
|
-
lanes: z.array(z.string()).min(1).optional(),
|
|
23
|
-
role: z.enum(ROLES).default("worker"),
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
(a) =>
|
|
27
|
-
handle(async () => {
|
|
28
|
-
if (a.lanes && a.lane_file !== undefined)
|
|
29
|
-
throw new CatherdError("E_INPUT_INVALID", "route takes lane_file or lanes, not both", {
|
|
30
|
-
fix: "pass every lane file in lanes",
|
|
31
|
-
});
|
|
32
|
-
return a.lanes
|
|
33
|
-
? { routes: await routeLanes(deps, { run: a.run, laneFiles: a.lanes, role: a.role }) }
|
|
34
|
-
: route(deps, { run: a.run, laneFile: a.lane_file, role: a.role });
|
|
35
|
-
}),
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
const lane = z.string().regex(ID_PATTERN);
|
|
39
|
-
server.registerTool(
|
|
40
|
-
"lane_set",
|
|
41
|
-
{
|
|
42
|
-
description:
|
|
43
|
-
"Set one header line of a lane file (owns, fast_check, kind, difficulty, after, allow), validated as write_run_file validates a lane: an unknown Kind or Difficulty, an Owns path outside the repo, or a malformed After:/Allow: is refused with E_LANE_INVALID, and an Owns that a running lane holds with E_ADMIT_OVERLAP. after names lanes that must finish first; allow lists path or path:line hits the lane's absence check may report. Returns the line and the header as it now reads. Orchestrator only.",
|
|
44
|
-
inputSchema: {
|
|
45
|
-
run: z.string(),
|
|
46
|
-
lane,
|
|
47
|
-
field: z.enum(Object.keys(LANE_FIELDS) as [LaneField, ...LaneField[]]),
|
|
48
|
-
value: z.string(),
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
(a) => handle(() => laneSet(deps, a)),
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
server.registerTool(
|
|
55
|
-
"owns_add",
|
|
56
|
-
{
|
|
57
|
-
description:
|
|
58
|
-
"Grow a lane's Owns mid-lane, with why (kept in the lane file): a running lane that owns one of the paths refuses it with E_ADMIT_OVERLAP; another lane file that lists one comes back as a hint. A dispatch of the lane still running is held to the new list when it finishes, so its edits there are not violations. Orchestrator only.",
|
|
59
|
-
inputSchema: {
|
|
60
|
-
run: z.string(),
|
|
61
|
-
lane,
|
|
62
|
-
paths: z.array(z.string().min(1)).min(1).max(100),
|
|
63
|
-
why: z.string().min(1),
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
(a) => handle(() => ownsAdd(deps, a)),
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
server.registerTool(
|
|
70
|
-
"preflight",
|
|
71
|
-
{
|
|
72
|
-
description:
|
|
73
|
-
"Run each lane's fast check once, on the base tree, behind the heavy-command lock, with a 120 s timeout, in the user's login environment plus the repo's gate environment (catherd knowledge env). By default only the lanes of milestones not landed yet; milestone names one. Each lane is pass, fails-as-expected, skipped (it checks a file the lane creates, or a pnpm filter that matches no package yet), cannot-start (a missing command, a timeout, or an environment error: no Docker, DNS, a denied permission; its note says which) or lock-busy (no heavy slot came free within 60 s: run it again); only cannot-start blocks. warnings names each lane whose fast check runs no linter while the repo has one. When the profile asks for confirmation, the first call returns the commands to show the user; call again with confirmed: true. Refused with E_LANE_INVALID, running nothing, while any lane's Kind: or Difficulty: is not one the catalog knows.",
|
|
74
|
-
inputSchema: {
|
|
75
|
-
run: z.string(),
|
|
76
|
-
confirmed: z.boolean().optional(),
|
|
77
|
-
milestone: z.string().regex(ID_PATTERN).optional(),
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
(a) => handle(() => preflight(deps, { run: a.run, confirmed: a.confirmed, milestone: a.milestone })),
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
server.registerTool(
|
|
84
|
-
"climb",
|
|
85
|
-
{
|
|
86
|
-
description:
|
|
87
|
-
"Move a routed lane one rung up its ladder and record why. Returns the next rung, or top: true, and any hints. Dispatch the lane again at that rung on a fresh thread. Pass env: true when the environment caused it (a missing service, a broken tool, a usage limit), not the rung. Refused with E_CLIMB_DESIGN when the evidence is a design question (Jev's finding answer is design, or a blocked climb's evidence is about lane ownership): send it to the architect instead. Refused with E_CLIMB_ENV when the lane's last reply named the environment on an ENV: line: fix the environment or park the milestone.",
|
|
88
|
-
inputSchema: {
|
|
89
|
-
run: z.string(),
|
|
90
|
-
lane: z.string().regex(ID_PATTERN),
|
|
91
|
-
reason: z.enum(CLIMB_REASONS),
|
|
92
|
-
evidence: z.string().optional(),
|
|
93
|
-
env: z.boolean().optional(),
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
(a) => handle(() => climb(deps, a)),
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
server.registerTool(
|
|
100
|
-
"ask",
|
|
101
|
-
{
|
|
102
|
-
description:
|
|
103
|
-
"Ask Jev one fixed question. finding: state { lane_file, finding } → design | code | unclear. same-defect: state { before, after } → yes | no. Falls back to the default when Jev is unsure or absent. Keep the state short, in English, and free of secrets.",
|
|
104
|
-
inputSchema: {
|
|
105
|
-
run: z.string(),
|
|
106
|
-
question: z.enum(["finding", "same-defect"]),
|
|
107
|
-
state: z.record(z.string(), z.string()),
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
(a) => handle(() => ask(deps, a)),
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
server.registerTool(
|
|
114
|
-
"land",
|
|
115
|
-
{
|
|
116
|
-
description:
|
|
117
|
-
"Record a landed milestone after you commit it: its five-column ledger row (with the minutes it took) and state.md's last check and next step, with any hints. learned, when given, goes to this repo's knowledge.md. Returns digest: the milestone's digest (R/digests/<milestone>.md: A-lines, the commits, lanes with rungs and climbs, the reviewer's BLOCKER and BUG lines, the owner questions still open, the verifier's verdict with carried items, minutes and tokens), and digestPath, its full path, for the milestone push to link. Refused with E_LAND_GATE unless, since the milestone's lanes started, a reviewer named reviewer-<milestone> ended ok (a dispatch, or a Claude subagent recorded with record_agent_run, role reviewer) and a verifier verdict was recorded ok under exactly verifier-<milestone> (record_agent_run, role verifier; a headless verifier's reply opening VERDICT: PASS; verifier-M1-pre does not count). minutes leave out the time the milestone was parked and any machine or workspace pause. A skip over an empty commit range is refused: commit first. skip: docs-only lands a milestone whose commit range changed only docs; skip: no-code one that changed no source file (put the evidence, e.g. a green pipeline, in evidence).",
|
|
118
|
-
inputSchema: {
|
|
119
|
-
run: z.string(),
|
|
120
|
-
milestone: z.string().regex(ID_PATTERN),
|
|
121
|
-
what: z.string().min(1),
|
|
122
|
-
commit: z.string().regex(/^[0-9a-f]{7,40}$/),
|
|
123
|
-
evidence: z.string().min(1),
|
|
124
|
-
next: z.string().min(1),
|
|
125
|
-
learned: z.string().min(1).optional(),
|
|
126
|
-
skip: z.enum(LAND_SKIPS).optional(),
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
(a) => handle(() => land(deps, a)),
|
|
130
|
-
);
|
|
131
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { ID_PATTERN } from "../../domain/ids.ts";
|
|
4
|
-
import { gateCheckOrList, gatePass } from "../../services/gate-service.ts";
|
|
5
|
-
import { answer, park } from "../../services/questions.ts";
|
|
6
|
-
import type { Deps } from "../../services/ports.ts";
|
|
7
|
-
import { handle } from "./result.ts";
|
|
8
|
-
|
|
9
|
-
// Spec 1.1 §7 and §8: the tools the enforced protocol adds.
|
|
10
|
-
export function registerProtocolTools(server: McpServer, deps: Deps): void {
|
|
11
|
-
const gate = {
|
|
12
|
-
run: z.string(),
|
|
13
|
-
item: z.string().min(1),
|
|
14
|
-
command: z.string().min(1),
|
|
15
|
-
paths: z.array(z.string().min(1)).min(1),
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
server.registerTool(
|
|
19
|
-
"gate_check",
|
|
20
|
-
{
|
|
21
|
-
description:
|
|
22
|
-
"The verifier, before running a gate item: { carried: true, passedAt, commit } when this repo already has a pass of the same command on the same content of paths (repo-relative; . for the whole repo: the tracked files plus uncommitted not-ignored changes; the repo's lockfiles always count, so never name node_modules; a git-ignored input such as .env or a build output is hashed only when named as a path, and as absent while it is not built), so it reports the item as carried over from that commit instead of running it; else { carried: false }, and it runs the item. Either way the call is recorded as the verifier's current step, which status and peek show. Pass milestone (the M the verifier checks): the milestone's digest then lists only its own carried items, and the answer adds recorded, the items already checked for that milestone in this run, each with its command and whether it passed. Call it first with only run and milestone to get that list without checking anything: reuse those item names, and re-check the failed ones first.",
|
|
23
|
-
inputSchema: {
|
|
24
|
-
run: z.string(),
|
|
25
|
-
item: gate.item.optional(),
|
|
26
|
-
command: gate.command.optional(),
|
|
27
|
-
paths: gate.paths.optional(),
|
|
28
|
-
milestone: z.string().regex(ID_PATTERN).optional(),
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
(a) => handle(() => gateCheckOrList(deps, a)),
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
server.registerTool(
|
|
35
|
-
"gate_pass",
|
|
36
|
-
{
|
|
37
|
-
description:
|
|
38
|
-
"The verifier, after a gate item passed: records the command, the content hash of paths and the commit, with the evidence (a log path or the decisive line), so a later gate_check on unchanged content carries it over.",
|
|
39
|
-
inputSchema: { ...gate, evidence: z.string().min(1) },
|
|
40
|
-
},
|
|
41
|
-
(a) => handle(() => gatePass(deps, a)),
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
server.registerTool(
|
|
45
|
-
"park",
|
|
46
|
-
{
|
|
47
|
-
description:
|
|
48
|
-
"An owner question only the user can answer: parks the milestone (land refuses it until answered) and puts it in front of state.md's next step, instead of stopping the run. Returns the parked milestones and hints: push the full question to the user with PushNotification, and go on with the milestones and runs that do not depend on it.",
|
|
49
|
-
inputSchema: { run: z.string(), milestone: z.string().regex(ID_PATTERN), question: z.string().min(1) },
|
|
50
|
-
},
|
|
51
|
-
(a) => handle(() => park(deps, a)),
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
server.registerTool(
|
|
55
|
-
"answer",
|
|
56
|
-
{
|
|
57
|
-
description:
|
|
58
|
-
"The owner answered a parked milestone's question in this session: records the answer and unparks the milestone. status, peek and run_start list the questions still open.",
|
|
59
|
-
inputSchema: { run: z.string(), milestone: z.string().regex(ID_PATTERN), answer: z.string().min(1) },
|
|
60
|
-
},
|
|
61
|
-
(a) => handle(() => answer(deps, a)),
|
|
62
|
-
);
|
|
63
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { probePush } from "../../services/doctor-push.ts";
|
|
3
|
-
import type { Deps } from "../../services/ports.ts";
|
|
4
|
-
import { handle } from "./result.ts";
|
|
5
|
-
|
|
6
|
-
export function registerPushTools(server: McpServer, deps: Deps): void {
|
|
7
|
-
server.registerTool(
|
|
8
|
-
"test_push",
|
|
9
|
-
{
|
|
10
|
-
description:
|
|
11
|
-
"Send this session one labeled smoke message through catherd's push (Claude Code's peer inbox, or the native Codex queue for this thread) and return the receipt: { outcome, enqueue, processing, msgId, detail }. A receipt proves the host accepted it, never that the model read it. For a check from inside a session, where a shell cannot name the Codex thread; catherd doctor --test-push --thread <uuid> is the shell's form.",
|
|
12
|
-
inputSchema: {},
|
|
13
|
-
},
|
|
14
|
-
() => handle(() => probePush(deps.host, process.env)),
|
|
15
|
-
);
|
|
16
|
-
}
|