catherd-cli 1.4.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 +188 -169
- package/THIRD_PARTY_NOTICES.md +217 -35
- package/catalog/jev.json +27 -1
- package/catalog/models.json +56 -239
- 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 -356
- package/plugin/skills/catherd-setup/SKILL.md +21 -20
- package/src/adapters/all.ts +0 -4
- package/src/adapters/backend.ts +19 -4
- package/src/adapters/claude-code/index.ts +126 -14
- package/src/adapters/codex/events.ts +10 -0
- package/src/adapters/codex/index.ts +17 -5
- package/src/adapters/cursor/home.ts +1 -1
- package/src/adapters/cursor/index.ts +10 -4
- package/src/adapters/opencode/index.ts +38 -1
- package/src/cli.ts +27 -27
- package/src/domain/agents.ts +3 -1
- 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 +106 -0
- package/src/domain/catalog.ts +69 -313
- package/src/domain/changes.ts +23 -1
- package/src/domain/cost.ts +19 -29
- package/src/domain/errors.ts +6 -2
- package/src/domain/fail-lines.ts +26 -0
- package/src/domain/failover.ts +37 -75
- package/src/domain/findings.ts +47 -0
- package/src/domain/gate-brief.ts +53 -0
- package/src/domain/hints.ts +13 -2
- 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 +118 -3
- package/src/domain/milestone-memory.ts +53 -0
- package/src/domain/notice.ts +32 -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 +175 -138
- package/src/domain/profile.ts +140 -63
- package/src/domain/record.ts +68 -3
- package/src/domain/reply.ts +29 -0
- package/src/domain/role-prompts.ts +182 -56
- package/src/domain/role-scope.ts +76 -0
- package/src/domain/role-tools.ts +34 -0
- package/src/domain/roles.ts +2 -0
- package/src/domain/route.ts +90 -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 +229 -72
- package/src/domain/slopalytics.ts +72 -0
- package/src/domain/state.ts +5 -0
- package/src/domain/util.ts +22 -0
- package/src/domain/workspace.ts +130 -0
- package/src/entry/answer-command.ts +39 -0
- package/src/entry/catalog-command.ts +24 -165
- package/src/entry/check-bin.ts +17 -0
- package/src/entry/cli-kit.ts +18 -2
- package/src/entry/daemon-bin.ts +22 -0
- package/src/entry/deps.ts +2 -0
- package/src/entry/doctor-command.ts +20 -2
- package/src/entry/glyphs.ts +15 -2
- package/src/entry/host-arg.ts +28 -0
- package/src/entry/init-command.ts +20 -69
- package/src/entry/knowledge-command.ts +121 -5
- package/src/entry/lock-command.ts +103 -8
- package/src/entry/mcp/handshake.ts +6 -2
- package/src/entry/mcp/role-bin.ts +20 -0
- package/src/entry/mcp/role-server.ts +134 -0
- package/src/entry/mcp/server.ts +85 -21
- 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/pause-command.ts +67 -0
- package/src/entry/profile-command.ts +33 -18
- package/src/entry/role-cli-command.ts +159 -0
- package/src/entry/runs-command.ts +81 -27
- package/src/entry/scorecard-command.ts +187 -0
- package/src/entry/supervise-bin.ts +1 -1
- package/src/entry/supervise-command.ts +30 -3
- package/src/entry/workspace-command.ts +44 -0
- package/src/infra/boot-lock.ts +25 -0
- package/src/infra/delivery.ts +3 -0
- package/src/infra/dispatch-dir.ts +2 -0
- package/src/infra/env.ts +11 -3
- package/src/infra/git.ts +70 -0
- package/src/infra/heavy-lock.ts +133 -6
- package/src/infra/host-probe.ts +16 -0
- package/src/infra/launch.ts +63 -7
- package/src/infra/lock-activity.ts +88 -0
- package/src/infra/login-env.ts +48 -0
- 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 +37 -2
- package/src/infra/role-mcp.ts +69 -0
- package/src/infra/slopalytics.ts +30 -0
- package/src/infra/store.ts +6 -1
- package/src/infra/supervisor.ts +107 -16
- package/src/services/admission.ts +133 -31
- package/src/services/backends.ts +4 -6
- package/src/services/budget.ts +26 -6
- package/src/services/capture.ts +1 -22
- package/src/services/catalog-service.ts +155 -254
- package/src/services/credentials.ts +15 -26
- package/src/services/daemon.ts +181 -0
- package/src/services/dispatch-service.ts +209 -17
- package/src/services/dispatches.ts +30 -2
- package/src/services/doctor-access.ts +8 -3
- package/src/services/doctor-backends.ts +1 -1
- package/src/services/doctor-checks.ts +47 -0
- package/src/services/doctor-docker.ts +221 -0
- package/src/services/doctor-role-mcp.ts +65 -0
- package/src/services/doctor.ts +72 -15
- package/src/services/end-push.ts +157 -0
- package/src/services/failover-rungs.ts +109 -0
- package/src/services/finalize.ts +24 -2
- package/src/services/gate-env.ts +217 -0
- package/src/services/gate-service.ts +231 -45
- package/src/services/global-install.ts +46 -13
- package/src/services/jev-service.ts +2 -4
- package/src/services/lane-edit.ts +154 -0
- package/src/services/lane-service.ts +291 -55
- package/src/services/milestone-check.ts +433 -0
- package/src/services/milestone-tree.ts +170 -0
- package/src/services/milestones.ts +167 -29
- package/src/services/notifier.ts +124 -4
- package/src/services/notify-service.ts +26 -0
- package/src/services/orchestrator-wait.ts +82 -0
- package/src/services/panel-setup.ts +116 -0
- package/src/services/pause.ts +161 -0
- package/src/services/peek.ts +79 -17
- package/src/services/plan-input.ts +151 -0
- package/src/services/ports.ts +60 -5
- package/src/services/preflight.ts +232 -24
- package/src/services/profile-service.ts +31 -3
- package/src/services/profile-store.ts +18 -6
- package/src/services/protocol.ts +123 -25
- package/src/services/provenance.ts +34 -89
- package/src/services/questions.ts +24 -0
- package/src/services/reconcile.ts +33 -1
- package/src/services/reentry.ts +7 -6
- package/src/services/role-access.ts +27 -0
- package/src/services/routing-service.ts +161 -42
- 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-pin.ts +125 -0
- package/src/services/run-read.ts +221 -0
- package/src/services/run-report.ts +176 -0
- package/src/services/run-service.ts +204 -23
- package/src/services/run-store.ts +159 -18
- package/src/services/scorecard-metrics.ts +86 -0
- package/src/services/scorecard.ts +382 -0
- package/src/services/scratch.ts +60 -0
- package/src/services/sessions.ts +3 -1
- package/src/services/state.ts +21 -0
- package/src/services/summary.ts +76 -12
- package/src/services/tree-path.ts +27 -0
- package/src/services/verifier-brief.ts +23 -0
- package/src/services/verifier-step.ts +48 -0
- package/src/services/workspace-admission.ts +259 -0
- package/src/services/workspace-service.ts +259 -0
- package/src/services/workspace-store.ts +95 -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 -36
- 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 -320
- package/src/adapters/antigravity/models.ts +0 -37
- package/src/adapters/grok/events.ts +0 -112
- package/src/adapters/grok/home.ts +0 -132
- package/src/adapters/grok/index.ts +0 -339
- package/src/adapters/grok/models.ts +0 -48
- package/src/domain/calibration.ts +0 -125
- package/src/domain/sources.ts +0 -144
- package/src/entry/mcp/dispatch-tools.ts +0 -52
- package/src/entry/mcp/lane-tools.ts +0 -84
- package/src/entry/mcp/protocol-tools.ts +0 -57
- package/src/entry/mcp/run-tools.ts +0 -148
- package/src/entry/mcp/setup-tools.ts +0 -108
- package/src/entry/tui/commands.ts +0 -563
- package/src/entry/tui/effects.ts +0 -381
- 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 -373
- 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 -573
- package/src/entry/tui/views/save-dialog.tsx +0 -340
- package/src/entry/tui/views/status.tsx +0 -209
- 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 -87
- package/src/infra/sources/artificial-analysis.ts +0 -112
- package/src/infra/sources/cache.ts +0 -124
- 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 -255
- package/src/services/source-derive.ts +0 -374
- package/src/services/source-sync.ts +0 -305
- package/src/services/standins.ts +0 -204
- package/src/services/treat-likes.ts +0 -97
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assertNativeHost } from "./backends.ts";
|
|
2
|
+
import { percentilesOf } from "../domain/bars.ts";
|
|
2
3
|
import { BUDGET_CHEAP_AT } from "../domain/budget.ts";
|
|
3
4
|
import { errorMessage } from "../domain/errors.ts";
|
|
4
5
|
import {
|
|
@@ -13,27 +14,38 @@ import {
|
|
|
13
14
|
import { type Difficulty, type Kind, parseLaneHeader } from "../domain/lane.ts";
|
|
14
15
|
import type { Role } from "../domain/roles.ts";
|
|
15
16
|
import type { RouteJev } from "../domain/route.ts";
|
|
17
|
+
import type { Catalog } from "../domain/catalog.ts";
|
|
16
18
|
import {
|
|
17
19
|
candidates,
|
|
18
20
|
defaultDifficulty,
|
|
19
21
|
defaultLadder,
|
|
20
22
|
type Pick,
|
|
23
|
+
quotaUsage,
|
|
21
24
|
type RoutingProfile,
|
|
25
|
+
rolesWithoutRung,
|
|
22
26
|
select,
|
|
23
27
|
} from "../domain/select.ts";
|
|
24
28
|
import { log } from "../infra/log.ts";
|
|
25
|
-
import { catalogQuery, freshenDiscovery, loadCatalog } from "./catalog-service.ts";
|
|
29
|
+
import { catalogQuery, freshenDiscovery, loadCatalog, startIndex } from "./catalog-service.ts";
|
|
26
30
|
import { type Asked, askJev, type JevOpts, jevQuestions, logJev } from "./jev-service.ts";
|
|
27
31
|
import type { ProfileView, RouteAnswer, RouteRequest, RoutingPort, Verdict } from "./ports.ts";
|
|
28
32
|
import { provenanceOf } from "./provenance.ts";
|
|
29
|
-
import { runEvidence } from "./run-evidence.ts";
|
|
33
|
+
import { type EvidenceTable, runEvidence } from "./run-evidence.ts";
|
|
30
34
|
|
|
31
|
-
function routingProfile(
|
|
35
|
+
function routingProfile(
|
|
36
|
+
v: ProfileView,
|
|
37
|
+
role: Role,
|
|
38
|
+
spentFraction: number,
|
|
39
|
+
usage: Record<string, number> = {},
|
|
40
|
+
): RoutingProfile {
|
|
32
41
|
const rc = v.roles[role];
|
|
33
42
|
return {
|
|
43
|
+
usage,
|
|
34
44
|
// spec §4.6: from 80 % of the budget on, start at the cheapest rung that clears the bar
|
|
35
45
|
objective: spentFraction >= BUDGET_CHEAP_AT ? "cost" : v.objective,
|
|
36
46
|
billing: v.billing,
|
|
47
|
+
maxEffort: v.maxEffort ?? {},
|
|
48
|
+
bars: { ...v.bars, ...rc?.bars },
|
|
37
49
|
role: {
|
|
38
50
|
enabled: rc?.enabled ?? false,
|
|
39
51
|
rungs: rc?.rungs ?? [],
|
|
@@ -50,14 +62,60 @@ const answer = (
|
|
|
50
62
|
asked: Asked | null,
|
|
51
63
|
jev: RouteJev | null,
|
|
52
64
|
): RouteAnswer => ({
|
|
53
|
-
|
|
65
|
+
rung: pick.rung,
|
|
66
|
+
ladder: pick.ladder,
|
|
54
67
|
source,
|
|
55
68
|
kind,
|
|
56
69
|
difficulty,
|
|
57
70
|
questionSet: asked?.answers ? asked.meta.questionSet : null,
|
|
58
71
|
jev,
|
|
72
|
+
jevSaid: null,
|
|
73
|
+
why: "",
|
|
74
|
+
...(pick.noClear ? { noClear: pick.noClear } : {}),
|
|
75
|
+
...(pick.tie ? { tie: pick.tie } : {}),
|
|
59
76
|
});
|
|
60
77
|
|
|
78
|
+
/** Spec 1.5 plan 24: what Jev said when it differs from the kind and difficulty the route used, else null. */
|
|
79
|
+
function jevSaid(
|
|
80
|
+
judged: ReturnType<typeof judgeRoute> | null,
|
|
81
|
+
kind: Kind | null,
|
|
82
|
+
difficulty: Difficulty | null,
|
|
83
|
+
): string | null {
|
|
84
|
+
if (!judged) return null;
|
|
85
|
+
const k = judged.kind;
|
|
86
|
+
const d = judged.track ? judged.difficulty : null;
|
|
87
|
+
if ((k === null || k === kind) && (d === null || d === difficulty)) return null;
|
|
88
|
+
return `Jev said ${k ?? "no sure kind"}/${d ?? "no sure difficulty"}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Spec 1.5 plan 24: the route's one-line why, for the coordinator; the provenance goes to routes.jsonl. */
|
|
92
|
+
function whyOf(a: RouteAnswer, hasLane: boolean): string {
|
|
93
|
+
const what = `${a.kind}/${a.difficulty}`;
|
|
94
|
+
const head =
|
|
95
|
+
a.source === "lane"
|
|
96
|
+
? `the lane's Kind/Difficulty, ${what}`
|
|
97
|
+
: a.source === "jev"
|
|
98
|
+
? `Jev: ${what}`
|
|
99
|
+
: a.source === "jev-kind"
|
|
100
|
+
? `Jev's kind with ${what}`
|
|
101
|
+
: hasLane
|
|
102
|
+
? "the role's default rung: neither Jev nor the lane file gave a kind and difficulty"
|
|
103
|
+
: "the role's default rung";
|
|
104
|
+
const start =
|
|
105
|
+
a.noClear ?? (a.source === "default" ? null : "the first rung in objective order that clears it");
|
|
106
|
+
return [head, a.jevSaid, start, a.tie].filter((x): x is string => Boolean(x)).join("; ");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Spec 1.2 §8 evidence is display only: an unreadable run on this machine never fails a route. */
|
|
110
|
+
function evidenceOrNone(c: Catalog): EvidenceTable | null {
|
|
111
|
+
try {
|
|
112
|
+
return runEvidence(c);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
log("debug", "route", { evidence: errorMessage(e) });
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
61
119
|
/** How long `route` waits on the daily discovery refresh before routing on the cached listing. */
|
|
62
120
|
const DISCOVERY_BUDGET_MS = 5_000;
|
|
63
121
|
|
|
@@ -79,53 +137,78 @@ async function freshenWithin(rungs: string[], repo: string, ms: number): Promise
|
|
|
79
137
|
clearTimeout(timer);
|
|
80
138
|
}
|
|
81
139
|
|
|
140
|
+
/** What every route of one call shares: the role's routing profile and the catalog. */
|
|
141
|
+
interface Prepared {
|
|
142
|
+
p: RoutingProfile;
|
|
143
|
+
c: Catalog;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function prepare(req: RouteRequest, o: RoutingOpts): Promise<Prepared> {
|
|
147
|
+
const p = routingProfile(req.profile, req.role, req.spentFraction, req.usage);
|
|
148
|
+
await freshenWithin(p.role.rungs, req.repo, o.discoveryBudgetMs ?? DISCOVERY_BUDGET_MS);
|
|
149
|
+
return { p, c: loadCatalog({ repo: req.repo, runDir: req.runDir }) };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Jev's answer about a lane; null when the route reads no lane, empty when it asks nothing (Jev off, one usable rung). */
|
|
153
|
+
interface Judged {
|
|
154
|
+
asked: Asked | null;
|
|
155
|
+
judged: ReturnType<typeof judgeRoute> | null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function judge(req: RouteRequest, { p, c }: Prepared, o: RoutingOpts): Promise<Judged | null> {
|
|
159
|
+
if (req.laneText === null) return null;
|
|
160
|
+
// one usable rung: nothing for Jev to choose, but the lane's header still names its kind and difficulty
|
|
161
|
+
if (req.profile.jev.use === "off" || candidates(c, p, req.role).length <= 1)
|
|
162
|
+
return { asked: null, judged: null };
|
|
163
|
+
const asked = await askJev(req.runDir, "route-v2", laneState(req.laneText), o);
|
|
164
|
+
const judged = asked.answers ? judgeRoute(jevQuestions().sets["route-v2"].rule, asked.answers) : null;
|
|
165
|
+
return { asked, judged };
|
|
166
|
+
}
|
|
167
|
+
|
|
82
168
|
/**
|
|
83
|
-
* Spec §5.4: kind and difficulty from
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* usable rung never asks
|
|
169
|
+
* Spec §5.4: kind and difficulty from the lane file's `Kind:`/`Difficulty:` when it declares both (spec 1.5
|
|
170
|
+
* plan 24: a declared header wins, and `jevSaid` names where Jev disagreed), else from Jev (§5.5's rule), else
|
|
171
|
+
* the role's default rung. A kind Jev is sure of survives a difficulty in the dead band: the difficulty then
|
|
172
|
+
* comes from the lane, else the role's default difficulty (`jev-kind`). A role with one usable rung never asks
|
|
173
|
+
* Jev.
|
|
87
174
|
*/
|
|
88
|
-
|
|
89
|
-
const p = routingProfile(req.profile, req.role, req.spentFraction);
|
|
90
|
-
await freshenWithin(p.role.rungs, req.repo, o.discoveryBudgetMs ?? DISCOVERY_BUDGET_MS);
|
|
91
|
-
const c = loadCatalog({ repo: req.repo });
|
|
175
|
+
function decide(req: RouteRequest, { p, c }: Prepared, j: Judged | null): RouteAnswer {
|
|
92
176
|
const fallback = () => defaultLadder(c, p, req.role);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
177
|
+
const finish = (out: RouteAnswer): RouteAnswer => {
|
|
178
|
+
out.why = whyOf(out, req.laneText !== null);
|
|
179
|
+
out.provenance = provenanceOf(
|
|
180
|
+
c,
|
|
181
|
+
out.rung,
|
|
182
|
+
out.kind,
|
|
183
|
+
out.difficulty,
|
|
184
|
+
p.billing,
|
|
185
|
+
evidenceOrNone(c),
|
|
186
|
+
percentilesOf(p),
|
|
187
|
+
);
|
|
188
|
+
return out;
|
|
189
|
+
};
|
|
190
|
+
if (req.laneText === null || j === null)
|
|
191
|
+
return finish(answer(fallback(), "default", null, null, null, null));
|
|
97
192
|
const lane = parseLaneHeader(req.laneText);
|
|
98
|
-
|
|
99
|
-
let judged: ReturnType<typeof judgeRoute> | null = null;
|
|
100
|
-
if (req.profile.jev.use !== "off") {
|
|
101
|
-
asked = await askJev(req.runDir, "route-v2", laneState(req.laneText), o);
|
|
102
|
-
if (asked.answers) judged = judgeRoute(jevQuestions().sets["route-v2"].rule, asked.answers);
|
|
103
|
-
}
|
|
193
|
+
const { asked, judged } = j;
|
|
104
194
|
const jev: RouteJev | null = judged
|
|
105
195
|
? { pKind: judged.pKind, pA: judged.pA, pB: judged.pB, nouls: judged.nouls }
|
|
106
196
|
: null;
|
|
107
197
|
let out: RouteAnswer;
|
|
108
|
-
if (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
difficulty,
|
|
118
|
-
asked,
|
|
119
|
-
jev,
|
|
120
|
-
);
|
|
198
|
+
if (lane.kind && lane.difficulty) {
|
|
199
|
+
out = answer(select(c, p, req.role, lane.difficulty), "lane", lane.kind, lane.difficulty, asked, jev);
|
|
200
|
+
} else if (judged?.track && judged.difficulty) {
|
|
201
|
+
const kind = lane.kind ?? judged.kind ?? "repo_code";
|
|
202
|
+
out = answer(select(c, p, req.role, judged.difficulty), "jev", kind, judged.difficulty, asked, jev);
|
|
203
|
+
} else if (judged?.kind || (judged && lane.kind)) {
|
|
204
|
+
const kind = (lane.kind ?? judged.kind) as Kind;
|
|
205
|
+
const difficulty = lane.difficulty ?? defaultDifficulty(c, p, req.role);
|
|
206
|
+
out = answer(select(c, p, req.role, difficulty), "jev-kind", kind, difficulty, asked, jev);
|
|
121
207
|
} else {
|
|
122
|
-
|
|
123
|
-
out =
|
|
124
|
-
kind && lane.difficulty
|
|
125
|
-
? answer(select(c, p, req.role, kind, lane.difficulty), "lane", kind, lane.difficulty, asked, jev)
|
|
126
|
-
: answer(fallback(), "default", null, null, asked, jev);
|
|
208
|
+
out = answer(fallback(), "default", null, null, asked, jev);
|
|
127
209
|
}
|
|
128
|
-
out.
|
|
210
|
+
out.jevSaid = out.source === "default" ? null : jevSaid(judged, out.kind, out.difficulty);
|
|
211
|
+
finish(out);
|
|
129
212
|
if (asked) {
|
|
130
213
|
logJev(req.runDir, {
|
|
131
214
|
...asked.meta,
|
|
@@ -143,9 +226,34 @@ async function route(req: RouteRequest, o: RoutingOpts): Promise<RouteAnswer> {
|
|
|
143
226
|
return out;
|
|
144
227
|
}
|
|
145
228
|
|
|
229
|
+
async function route(req: RouteRequest, o: RoutingOpts): Promise<RouteAnswer> {
|
|
230
|
+
const prep = await prepare(req, o);
|
|
231
|
+
return decide(req, prep, await judge(req, prep, o));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Spec 1.5 plan 24, batch `route`: the lanes of one role (one run, one profile) share one discovery refresh and
|
|
236
|
+
* one catalog, and Jev is asked about all of them at once (Ruling 6: one request per lane, all in flight
|
|
237
|
+
* together, so each keeps its own cache key and jev.jsonl row). Then each lane is decided in order, every
|
|
238
|
+
* routed start counting as a use of its quota, so lanes that tie spread over the quotas.
|
|
239
|
+
*/
|
|
240
|
+
async function routeMany(reqs: RouteRequest[], o: RoutingOpts): Promise<RouteAnswer[]> {
|
|
241
|
+
const first = reqs[0];
|
|
242
|
+
if (!first) return [];
|
|
243
|
+
const prep = await prepare(first, o);
|
|
244
|
+
const judged = await Promise.all(reqs.map((r) => judge(r, prep, o)));
|
|
245
|
+
const usage: Record<string, number> = { ...first.usage };
|
|
246
|
+
return reqs.map((req, i) => {
|
|
247
|
+
const out = decide(req, { ...prep, p: { ...prep.p, usage: { ...usage } } }, judged[i] ?? null);
|
|
248
|
+
const q = quotaUsage([out.rung]);
|
|
249
|
+
for (const [k, n] of Object.entries(q)) usage[k] = (usage[k] ?? 0) + n;
|
|
250
|
+
return out;
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
146
254
|
async function verdict<T extends string>(
|
|
147
255
|
runDir: string,
|
|
148
|
-
set: Extract<SetName, "finding" | "same-defect">,
|
|
256
|
+
set: Extract<SetName, "finding" | "same-defect" | "stop">,
|
|
149
257
|
state: Record<string, unknown>,
|
|
150
258
|
options: readonly T[],
|
|
151
259
|
use: "auto" | "off",
|
|
@@ -177,6 +285,11 @@ export function routingService(o: RoutingOpts = {}): RoutingPort {
|
|
|
177
285
|
assertNativeHost(result.rung, req.host ?? "unknown");
|
|
178
286
|
return result;
|
|
179
287
|
},
|
|
288
|
+
routeMany: async (reqs) => {
|
|
289
|
+
const results = await routeMany(reqs, o);
|
|
290
|
+
results.forEach((r, i) => assertNativeHost(r.rung, reqs[i]?.host ?? "unknown"));
|
|
291
|
+
return results;
|
|
292
|
+
},
|
|
180
293
|
finding: (runDir, laneText, finding, use) =>
|
|
181
294
|
verdict(
|
|
182
295
|
runDir,
|
|
@@ -195,6 +308,12 @@ export function routingService(o: RoutingOpts = {}): RoutingPort {
|
|
|
195
308
|
use,
|
|
196
309
|
o,
|
|
197
310
|
),
|
|
311
|
+
stopCause: (runDir, status, reply, use) =>
|
|
312
|
+
verdict(runDir, "stop", { status, reply: scrubFree(reply.slice(-4000)) }, VERDICT_OPTIONS.stop, use, o),
|
|
313
|
+
checkStart: async (view, repo) => {
|
|
314
|
+
const { catalog, index } = await startIndex(repo);
|
|
315
|
+
return { unusable: rolesWithoutRung(catalog, view), index };
|
|
316
|
+
},
|
|
198
317
|
catalog: (filter, billing) => catalogQuery(filter, billing),
|
|
199
318
|
};
|
|
200
319
|
}
|