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
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parseFindings } from "../domain/findings.ts";
|
|
4
|
+
import { JUDGE_CONTRACT, type JudgeInput, JUDGE_VERBS } from "../domain/judge.ts";
|
|
5
|
+
import { parseLaneHeader } from "../domain/lane.ts";
|
|
6
|
+
import { planInputNames } from "../domain/plan-doc.ts";
|
|
7
|
+
import { parseReplyDesign } from "../domain/record.ts";
|
|
8
|
+
import { RUN_UNIT } from "../domain/run-events.ts";
|
|
9
|
+
import type { Action } from "../domain/run-machine.ts";
|
|
10
|
+
import { unitMilestone } from "../domain/branch.ts";
|
|
11
|
+
import { integrateName, lanesOf, type RunState } from "../domain/run-state.ts";
|
|
12
|
+
import { VERSION } from "../infra/version.ts";
|
|
13
|
+
import { landedCommits } from "./milestones.ts";
|
|
14
|
+
import { readChecks } from "./milestone-check.ts";
|
|
15
|
+
import { laneTasks, milestoneTasks, PLAN_INPUT_DIR, taskBlock } from "./plan-input.ts";
|
|
16
|
+
import { readRecords, type Run, runPaths } from "./run-store.ts";
|
|
17
|
+
|
|
18
|
+
type DispatchAction = Extract<Action, { type: "dispatch" }>;
|
|
19
|
+
|
|
20
|
+
const ALINES = (run: Run): string =>
|
|
21
|
+
run.meta.aLines.length ? run.meta.aLines.map((a) => `- ${a}`).join("\n") : "- (none written)";
|
|
22
|
+
|
|
23
|
+
const read = (file: string): string => {
|
|
24
|
+
try {
|
|
25
|
+
return readFileSync(file, "utf8");
|
|
26
|
+
} catch {
|
|
27
|
+
return "";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** The reply of the latest ended dispatch named `name`, by its path in the run folder; "" when there is none. */
|
|
32
|
+
function lastReply(run: Run, s: RunState, name: string): string {
|
|
33
|
+
const d = Object.values(s.dispatches)
|
|
34
|
+
.filter((x) => x.name === name && x.ended !== null)
|
|
35
|
+
.at(-1);
|
|
36
|
+
return d ? read(join(run.dir, "roles", name, d.dispatchId, "reply.md")) : "";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const READ_RULES = "If the repo has a CLAUDE.md or AGENTS.md, read it first and follow it.";
|
|
40
|
+
|
|
41
|
+
const DOSSIER = [
|
|
42
|
+
"what past runs of this repo learned (read_knowledge), so you map only what changed since then;",
|
|
43
|
+
"the files and folders involved, one line each on what they hold;",
|
|
44
|
+
"the existing patterns the work should copy, by path;",
|
|
45
|
+
"the build, test and run commands, with how long the full suite takes when the docs, the CI config or a log say so (never run the suite to find out: write unknown);",
|
|
46
|
+
"the lint and type-check commands, and how to scope each to one package;",
|
|
47
|
+
"the symbols the change will call or alter, with their signatures;",
|
|
48
|
+
"the repo's rules (CLAUDE.md, AGENTS.md, conventions) that bind this work;",
|
|
49
|
+
"risks: shared files, generated code, slow or flaky tests.",
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
/** The lane file header the architect writes: what `route`, `preflight` and `dispatch` read. */
|
|
53
|
+
const LANE_HEADER = [
|
|
54
|
+
"Every lane file starts with these lines:",
|
|
55
|
+
" # Mx.Ly — <title>",
|
|
56
|
+
" Owns: <repo-relative paths, a trailing / for a folder, never a glob>",
|
|
57
|
+
" Fast check: <the lane's targeted tests plus the linter of every package it touches, and the type check when the repo has one, scoped to those packages>",
|
|
58
|
+
" Kind: repo_code|terminal|ui|prose|research",
|
|
59
|
+
" Difficulty: copy|build|logic|hard",
|
|
60
|
+
" Tasks: <plan task numbers, comma-separated; <plan file>#<n> when the run has several plan files>",
|
|
61
|
+
"Three optional lines follow: `After: Mx.Ly, …` when the lane compiles against another lane's changes; `Allow: path[:line], …` under a fast check that greps for absence, naming the hits that are allowed exceptions; and `Reference: <repo path>` when the lane builds something with a close sibling in the repository.",
|
|
62
|
+
"Two lanes of one milestone never own the same file.",
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
function architectBrief(run: Run, s: RunState, a: DispatchAction): string {
|
|
66
|
+
// a milestone's call-back carries review findings; the run's own notes are what the owner or the plan check asked to change
|
|
67
|
+
const delta = a.notes.length > 0 && a.unit !== RUN_UNIT;
|
|
68
|
+
const dossier = join(run.dir, "dossier.md");
|
|
69
|
+
if (delta)
|
|
70
|
+
return [
|
|
71
|
+
`You are the architect of catherd run ${run.id} (${run.meta.title}), called back on ${a.unit}: its review found things the plan does not cover.`,
|
|
72
|
+
"",
|
|
73
|
+
"Findings the lanes' owners cannot fix, verbatim:",
|
|
74
|
+
...a.notes.map((n) => `- ${n}`),
|
|
75
|
+
"",
|
|
76
|
+
`Read ${join(run.dir, "plan.md")} and the lane files, then change only what these findings need: edit a lane file, or add a lane to ${a.unit} with a fresh id (the next M<n>.L<k>). Lanes that are done keep their ids. Write with write_run_file. ${READ_RULES}`,
|
|
77
|
+
"",
|
|
78
|
+
...LANE_HEADER,
|
|
79
|
+
].join("\n");
|
|
80
|
+
return [
|
|
81
|
+
`You are the architect of catherd run ${run.id} (${run.meta.title}), run folder ${run.dir}.`,
|
|
82
|
+
"",
|
|
83
|
+
"The A-lines this run must meet:",
|
|
84
|
+
ALINES(run),
|
|
85
|
+
"",
|
|
86
|
+
s.plan.length
|
|
87
|
+
? `The owner's approved plan: ${planInputNames(s.plan)
|
|
88
|
+
.map((n) => `${PLAN_INPUT_DIR}/${n}`)
|
|
89
|
+
.join(
|
|
90
|
+
", ",
|
|
91
|
+
)} (read each with read_run_file). Lane the tasks the acceptance lines name, or every task when they name none.`
|
|
92
|
+
: `No plan was given: write plan.md to the depth your role text sets. The run waits for the owner's approval of it before any lane runs. Read the dossier first: ${existsSync(dossier) ? dossier : "dossier.md in the run folder"}.`,
|
|
93
|
+
...(a.notes.length ? ["", "What to change:", ...a.notes.map((n) => `- ${n}`)] : []),
|
|
94
|
+
"",
|
|
95
|
+
"Write plan.md and every lanes/Mx.Ly.md yourself with write_run_file. In plan.md, one heading per milestone, `## M1 — <title>`, then its full check (the whole suite), and an `After: M0, …` line only when the milestone needs another milestone landed first (none otherwise, so a parked milestone does not hold the rest).",
|
|
96
|
+
"A full check slower than about five minutes is a problem to solve: make speeding it up an early lane.",
|
|
97
|
+
...(s.base !== null
|
|
98
|
+
? [
|
|
99
|
+
"",
|
|
100
|
+
`In this run each milestone is its own branch and merge request from origin/${s.base}, and milestones run side by side: a milestone that builds on another's code names it on its first lane's After: line (After: M1), and only those wait. When a fresh checkout needs a command before anything runs (installing dependencies, generating code), put Setup: <command> on the line under plan.md's # title.`,
|
|
101
|
+
]
|
|
102
|
+
: []),
|
|
103
|
+
"",
|
|
104
|
+
...LANE_HEADER,
|
|
105
|
+
"",
|
|
106
|
+
READ_RULES,
|
|
107
|
+
].join("\n");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** A fix round's worker sees this many lines of the failing milestone check's log. */
|
|
111
|
+
export const CHECK_LOG_TAIL_LINES = 40;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The end of the milestone's latest check in this run, when it failed (a later passing one removes it): what a
|
|
115
|
+
* fix round starts from when the finding names only the check.
|
|
116
|
+
*/
|
|
117
|
+
function failingCheck(run: Run, milestone: string): string[] {
|
|
118
|
+
const last = readChecks(run.meta.repo)
|
|
119
|
+
.filter((r) => r.run === run.id && r.milestone === milestone && !r.baseline)
|
|
120
|
+
.at(-1);
|
|
121
|
+
if (!last || last.exit === 0) return [];
|
|
122
|
+
let tail: string;
|
|
123
|
+
try {
|
|
124
|
+
tail = readFileSync(last.log, "utf8")
|
|
125
|
+
.split("\n")
|
|
126
|
+
.filter((l) => l.trim())
|
|
127
|
+
.slice(-CHECK_LOG_TAIL_LINES)
|
|
128
|
+
.join("\n");
|
|
129
|
+
} catch {
|
|
130
|
+
tail = `(log unreadable: ${last.log})`;
|
|
131
|
+
}
|
|
132
|
+
return [
|
|
133
|
+
"",
|
|
134
|
+
`The milestone check \`${last.command}\` exited ${last.exit} on ${last.head.slice(0, 12)}; the end of its log:`,
|
|
135
|
+
"```",
|
|
136
|
+
tail,
|
|
137
|
+
"```",
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** A lane's plan tasks, verbatim; nothing for a lane that names none. */
|
|
142
|
+
function planTasks(run: Run, lane: string): string[] {
|
|
143
|
+
const tasks = laneTasks(run, lane);
|
|
144
|
+
return tasks.length
|
|
145
|
+
? [
|
|
146
|
+
"",
|
|
147
|
+
"Your plan tasks. Implement them as written; where one conflicts with the code, reply DESIGN: as your role text says.",
|
|
148
|
+
...tasks.map(taskBlock),
|
|
149
|
+
]
|
|
150
|
+
: [];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The fast check of each lane of a milestone: a fix worker reruns those whose files it changes. */
|
|
154
|
+
function fastChecks(run: Run, s: RunState, milestone: string): string[] {
|
|
155
|
+
const rows = lanesOf(s, milestone).flatMap((l) => {
|
|
156
|
+
const check = parseLaneHeader(read(join(runPaths(run.dir).lanes, `${l.id}.md`))).fastCheck;
|
|
157
|
+
return check ? [`- ${l.id} (${l.owns.join(", ")}): \`${check}\``] : [];
|
|
158
|
+
});
|
|
159
|
+
return rows.length
|
|
160
|
+
? ["", `The fast checks of ${milestone}'s lanes; run the ones whose files you change:`, ...rows]
|
|
161
|
+
: [];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** A research answer past this many characters is cut, and the brief says how many. */
|
|
165
|
+
export const RESEARCH_ANSWER_CHARS = 4000;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Plan 34: one block per `After:` lane of `lane` that is a research lane with an answer: the reply of its latest
|
|
169
|
+
* ok record that was not blocked or refused, its closing STATUS line cut. A reply that cannot be read adds nothing.
|
|
170
|
+
*/
|
|
171
|
+
export function researchAnswers(run: Run, s: RunState, lane: string): string[] {
|
|
172
|
+
const out: string[] = [];
|
|
173
|
+
for (const id of s.lanes[lane]?.after ?? []) {
|
|
174
|
+
if (s.lanes[id]?.kind !== "research") continue;
|
|
175
|
+
const latest = readRecords(run)
|
|
176
|
+
.records.filter(
|
|
177
|
+
(r) =>
|
|
178
|
+
r.lane === id && r.status === "ok" && r.replyStatus !== "blocked" && r.replyStatus !== "refused",
|
|
179
|
+
)
|
|
180
|
+
.sort((a, b) => a.endedAt.localeCompare(b.endedAt))
|
|
181
|
+
.at(-1);
|
|
182
|
+
if (!latest?.replyPath) continue;
|
|
183
|
+
const answer = read(join(run.dir, latest.replyPath))
|
|
184
|
+
.replace(/\s*STATUS:[^\n]*\s*$/i, "")
|
|
185
|
+
.trim();
|
|
186
|
+
if (!answer) continue;
|
|
187
|
+
const cut = answer.length - RESEARCH_ANSWER_CHARS;
|
|
188
|
+
out.push(
|
|
189
|
+
`${id} (research) answered:\n${
|
|
190
|
+
cut > 0 ? `${answer.slice(0, RESEARCH_ANSWER_CHARS)}\n[… ${cut} more characters cut]` : answer
|
|
191
|
+
}`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function workerBrief(run: Run, s: RunState, a: DispatchAction): string {
|
|
198
|
+
const lane = s.lanes[a.unit];
|
|
199
|
+
const fix = a.brief === "fix";
|
|
200
|
+
return [
|
|
201
|
+
`You are ${a.name}, a ${a.role} of catherd run ${run.id} (${run.meta.title}). Your lane is ${a.unit}${lane ? `, in milestone ${lane.milestone}` : ""}; its file is below.`,
|
|
202
|
+
"",
|
|
203
|
+
"The A-lines this run must meet:",
|
|
204
|
+
ALINES(run),
|
|
205
|
+
"",
|
|
206
|
+
`Own only the files on your lane's Owns: line; change nothing else. ${READ_RULES}`,
|
|
207
|
+
...planTasks(run, a.unit),
|
|
208
|
+
...(fix
|
|
209
|
+
? [
|
|
210
|
+
"",
|
|
211
|
+
"This is a fix round. Fix exactly these, each as it was reported, and nothing beside them:",
|
|
212
|
+
...a.notes.map((n) => `- ${n}`),
|
|
213
|
+
...(a.notes.length && lane ? failingCheck(run, lane.milestone) : []),
|
|
214
|
+
...(lane ? fastChecks(run, s, lane.milestone) : []),
|
|
215
|
+
]
|
|
216
|
+
: []),
|
|
217
|
+
"",
|
|
218
|
+
`Run your fast check until it passes. Run the full suite only if this brief says so, and wrap any full build or full test suite in: bunx catherd-cli@${VERSION} lock -- <command>. Other lanes share this machine.`,
|
|
219
|
+
...researchAnswers(run, s, a.unit).flatMap((block) => ["", block]),
|
|
220
|
+
].join("\n");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** The worker that finishes a merge of the base into the run's branch: git left these files in conflict. */
|
|
224
|
+
function integrateBrief(run: Run, s: RunState, a: DispatchAction): string {
|
|
225
|
+
return [
|
|
226
|
+
`You are ${a.name} of catherd run ${run.id} (${run.meta.title}). The base branch ${s.base ?? ""} moved while milestone ${a.unit} was being verified, and catherd merged it into the run's branch. The merge stopped on conflicts in the repository you are in:`,
|
|
227
|
+
...a.notes.map((n) => `- ${n}`),
|
|
228
|
+
"",
|
|
229
|
+
"Resolve every conflict, keeping both sides' intent: the base's change and this run's. Edit the conflicted files only, remove every conflict marker, and do not commit or stage anything (no git add, no git commit): catherd does.",
|
|
230
|
+
"",
|
|
231
|
+
"The A-lines this run must meet:",
|
|
232
|
+
ALINES(run),
|
|
233
|
+
"",
|
|
234
|
+
READ_RULES,
|
|
235
|
+
].join("\n");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function milestoneBase(run: Run): string {
|
|
239
|
+
return landedCommits(run).at(-1) ?? run.meta.startHead ?? "HEAD~1";
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** The milestone's plan tasks, and every file a lane changed outside its Owns, declared on an EXTRA: line or not. */
|
|
243
|
+
function reviewPlan(run: Run, s: RunState, milestone: string): string[] {
|
|
244
|
+
const tasks = milestoneTasks(run, milestone);
|
|
245
|
+
const { records } = readRecords(run);
|
|
246
|
+
const outside = lanesOf(s, milestone).flatMap((l) => {
|
|
247
|
+
const r = records.filter((x) => x.lane === l.id).at(-1);
|
|
248
|
+
if (!r) return [];
|
|
249
|
+
const declared = r.extras ?? [];
|
|
250
|
+
return [
|
|
251
|
+
...declared.map((e) => `- ${e.path} — declared by ${r.name}${e.why ? `: ${e.why}` : ""}`),
|
|
252
|
+
...r.violations
|
|
253
|
+
.filter((v) => !declared.some((e) => e.path === v))
|
|
254
|
+
.map((v) => `- ${v} — not declared by ${r.name}`),
|
|
255
|
+
];
|
|
256
|
+
});
|
|
257
|
+
return [
|
|
258
|
+
...(tasks.length ? ["", "The plan tasks this milestone implements:", ...tasks.map(taskBlock)] : []),
|
|
259
|
+
...(outside.length ? ["", "Files changed outside their lane:", ...outside] : []),
|
|
260
|
+
];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** The findings a fix round rejected since the milestone's latest review ended, for the re-check to judge. */
|
|
264
|
+
function rejections(run: Run, s: RunState, milestone: string): string[] {
|
|
265
|
+
const reviewed = Object.values(s.dispatches)
|
|
266
|
+
.filter((d) => (d.name === `reviewer-${milestone}` || d.name === `reviewer-${milestone}-2`) && d.ended)
|
|
267
|
+
.map((d) => (d.ended as { at: string }).at)
|
|
268
|
+
.sort()
|
|
269
|
+
.at(-1);
|
|
270
|
+
const lanes = new Set(lanesOf(s, milestone).map((l) => l.id));
|
|
271
|
+
const rows = readRecords(run).records.flatMap((r) =>
|
|
272
|
+
r.lane && lanes.has(r.lane) && (reviewed === undefined || r.endedAt > reviewed)
|
|
273
|
+
? (r.rejected ?? []).map((x) => `- ${r.name}: REJECTED: ${x.finding}${x.why ? ` — ${x.why}` : ""}`)
|
|
274
|
+
: [],
|
|
275
|
+
);
|
|
276
|
+
return rows.length
|
|
277
|
+
? [
|
|
278
|
+
"",
|
|
279
|
+
"A fix round rejected these findings instead of changing the code. Judge each reason: a finding whose reason holds is closed; one whose reason does not hold stands, with your answer to the reason.",
|
|
280
|
+
...rows,
|
|
281
|
+
]
|
|
282
|
+
: [];
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function reviewBrief(run: Run, s: RunState, a: DispatchAction): string {
|
|
286
|
+
const m = s.milestones.find((x) => x.id === a.unit);
|
|
287
|
+
const base = milestoneBase(run);
|
|
288
|
+
const ui = a.role === "ui-reviewer";
|
|
289
|
+
const head = `You review milestone ${a.unit}${m ? ` (${m.title})` : ""} of catherd run ${run.id}${ui ? ", by its screens" : ""}. You did not write it. Change nothing in the project.`;
|
|
290
|
+
const range = s.base
|
|
291
|
+
? `The milestone is its branch's diff from origin/${s.base}: read \`git diff origin/${s.base}...HEAD\` and \`git diff --name-only origin/${s.base}...HEAD\` (three dots: other milestones land on the base meanwhile); read new files in full.`
|
|
292
|
+
: `The milestone is the commit range ${base}..HEAD: read \`git diff ${base}..HEAD\` and \`git diff --name-only ${base}..HEAD\`; read new files in full.`;
|
|
293
|
+
if (a.brief === "recheck") {
|
|
294
|
+
const second = a.name.endsWith("-2");
|
|
295
|
+
const first = lastReply(run, s, `reviewer-${a.unit}`);
|
|
296
|
+
const open = parseFindings(first).filter((f) => f.label !== "NIT");
|
|
297
|
+
return second
|
|
298
|
+
? [
|
|
299
|
+
head,
|
|
300
|
+
range,
|
|
301
|
+
"",
|
|
302
|
+
`reviewer-${a.unit} replied STATUS: partial: it read only part of the diff. Its reply, which names the files it did not read:`,
|
|
303
|
+
first.trim().split("\n").slice(-40).join("\n"),
|
|
304
|
+
"",
|
|
305
|
+
"Review those files only, and report every finding as BLOCKER|BUG|NIT file:line — problem — fix. With no finding, the reply is CLEAN.",
|
|
306
|
+
].join("\n")
|
|
307
|
+
: [
|
|
308
|
+
head,
|
|
309
|
+
range,
|
|
310
|
+
"",
|
|
311
|
+
`This is a re-check: the lines you reported last time were fixed in ${m?.commit?.slice(0, 12) ?? "HEAD"}. Re-check only these BLOCKER and BUG lines, and report each that is still open as it was, plus any new BLOCKER or BUG the fixes introduced:`,
|
|
312
|
+
...open.map((f) => `- ${f.text}`),
|
|
313
|
+
...reviewPlan(run, s, a.unit),
|
|
314
|
+
...rejections(run, s, a.unit),
|
|
315
|
+
"",
|
|
316
|
+
"With none open and none new, the reply is CLEAN.",
|
|
317
|
+
].join("\n");
|
|
318
|
+
}
|
|
319
|
+
if (ui)
|
|
320
|
+
return [
|
|
321
|
+
head,
|
|
322
|
+
range,
|
|
323
|
+
"",
|
|
324
|
+
"Start the app yourself (the repo's docs say how). Map the changed files to the screens that render them and review those screens only, at viewport size, not full page, in each theme: agent-browser screenshot <path> to the run folder's shots/ directory, and open each PNG yourself before you judge it. If you cannot start the app, reply STATUS: blocked and say why.",
|
|
325
|
+
"",
|
|
326
|
+
"Report every finding as: BLOCKER|BUG|NIT <screen> — problem — fix — <screenshot path>, always with the path. With no finding, the reply is CLEAN.",
|
|
327
|
+
"",
|
|
328
|
+
"The A-lines this run must meet:",
|
|
329
|
+
ALINES(run),
|
|
330
|
+
].join("\n");
|
|
331
|
+
return [
|
|
332
|
+
head,
|
|
333
|
+
range,
|
|
334
|
+
"",
|
|
335
|
+
"The A-lines this run must meet:",
|
|
336
|
+
ALINES(run),
|
|
337
|
+
...reviewPlan(run, s, a.unit),
|
|
338
|
+
"",
|
|
339
|
+
"Report every finding as: BLOCKER|BUG|NIT file:line — problem — fix. Cover:",
|
|
340
|
+
"- unmet A-lines and edge cases;",
|
|
341
|
+
"- code or abstractions nobody needs;",
|
|
342
|
+
"- comments that restate code;",
|
|
343
|
+
"- tests that assert nothing.",
|
|
344
|
+
"",
|
|
345
|
+
"With no finding, the reply is CLEAN.",
|
|
346
|
+
].join("\n");
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function verifyBrief(run: Run, s: RunState, a: DispatchAction): string {
|
|
350
|
+
const m = s.milestones.find((x) => x.id === a.unit);
|
|
351
|
+
const previous = lastReply(run, s, a.name)
|
|
352
|
+
.split("\n")
|
|
353
|
+
.map((l) => l.trim())
|
|
354
|
+
.filter(Boolean)
|
|
355
|
+
.slice(1, 13);
|
|
356
|
+
return [
|
|
357
|
+
`You verify milestone ${a.unit}${m ? ` (${m.title})` : ""} of catherd run ${run.id}: you own the independent gate. The commit under test is ${m?.commit ?? "HEAD"}; the tree is frozen.`,
|
|
358
|
+
"",
|
|
359
|
+
"The A-lines this run must meet:",
|
|
360
|
+
ALINES(run),
|
|
361
|
+
"",
|
|
362
|
+
`Run the milestone's full check, named under "## ${a.unit}" in ${join(run.dir, "plan.md")}, and each lane's fast check where the full check does not cover it. ${READ_RULES}`,
|
|
363
|
+
"",
|
|
364
|
+
...(a.notes.length ? ["Before you start:", ...a.notes.map((n) => `- ${n}`), ""] : []),
|
|
365
|
+
...(previous.length
|
|
366
|
+
? [
|
|
367
|
+
"A verifier failed this milestone before; it said:",
|
|
368
|
+
...previous.map((l) => `- ${l}`),
|
|
369
|
+
"Run those items first.",
|
|
370
|
+
"",
|
|
371
|
+
]
|
|
372
|
+
: []),
|
|
373
|
+
"The first line of your reply is the verdict: VERDICT: PASS, VERDICT: FAIL, or VERDICT: BLOCKED: environment — <the probe that proves it>. On FAIL, list each failing item on its own line below, with the file and the command that failed.",
|
|
374
|
+
].join("\n");
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function researchBrief(run: Run): string {
|
|
378
|
+
return [
|
|
379
|
+
`You are the researcher of catherd run ${run.id} (${run.meta.title}). Map the code the A-lines touch into a dossier.`,
|
|
380
|
+
"",
|
|
381
|
+
"The A-lines:",
|
|
382
|
+
ALINES(run),
|
|
383
|
+
"",
|
|
384
|
+
`A dossier lists, with file:line everywhere:`,
|
|
385
|
+
...DOSSIER.map((d) => `- ${d}`),
|
|
386
|
+
"",
|
|
387
|
+
`It may run to 200 lines; the 15-line cap does not apply to it. Write it with write_run_file to dossier.md, and reply with that path. ${READ_RULES}`,
|
|
388
|
+
].join("\n");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** Plan 35 D8: the tree, branch and diff of a base-run milestone that has its worktree; nothing otherwise. */
|
|
392
|
+
function branchLine(s: RunState, a: DispatchAction): string[] {
|
|
393
|
+
const m = s.base === null ? undefined : s.milestones.find((x) => x.id === unitMilestone(a.unit));
|
|
394
|
+
if (!m?.tree || s.base === null) return [];
|
|
395
|
+
return [
|
|
396
|
+
`You work in ${m.tree}, on branch ${m.branch ?? ""}, which starts from origin/${s.base}. The milestone's diff is \`git diff origin/${s.base}...HEAD\`: three dots, because other milestones land on the base meanwhile and a two-dot diff shows their work as deleted here.`,
|
|
397
|
+
];
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** The brief of one dispatch: what its role is to do, from the run's own facts. Admission adds the lane file and the reply contract. */
|
|
401
|
+
export function briefFor(run: Run, s: RunState, a: DispatchAction): string {
|
|
402
|
+
const text = [briefBody(run, s, a), ...branchLine(s, a)].join("\n\n");
|
|
403
|
+
// a lane's answers ride in its own notes; a milestone's go to every role that works on it; the run's, to planning
|
|
404
|
+
const answered = a.lane
|
|
405
|
+
? []
|
|
406
|
+
: a.unit === RUN_UNIT
|
|
407
|
+
? s.notes
|
|
408
|
+
: (s.milestones.find((m) => m.id === a.unit)?.notes ?? []);
|
|
409
|
+
return answered.length ? `${text}\n\n${answered.join("\n")}` : text;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function briefBody(run: Run, s: RunState, a: DispatchAction): string {
|
|
413
|
+
switch (a.brief) {
|
|
414
|
+
case "dossier":
|
|
415
|
+
return researchBrief(run);
|
|
416
|
+
case "architect":
|
|
417
|
+
return architectBrief(run, s, a);
|
|
418
|
+
case "lane":
|
|
419
|
+
case "fix":
|
|
420
|
+
return a.name === integrateName(a.unit) ? integrateBrief(run, s, a) : workerBrief(run, s, a);
|
|
421
|
+
case "review":
|
|
422
|
+
case "recheck":
|
|
423
|
+
return reviewBrief(run, s, a);
|
|
424
|
+
case "verify":
|
|
425
|
+
return verifyBrief(run, s, a);
|
|
426
|
+
case "judge":
|
|
427
|
+
return `You are ${a.name} of catherd run ${run.id}. ${JUDGE_CONTRACT}`;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const REPLY_TAIL = 3000;
|
|
432
|
+
|
|
433
|
+
/** The rules review's brief (plan 34): a finished run's judge reads its files and proposes changes to the standing rules. */
|
|
434
|
+
export const RULES_REVIEW_BRIEF =
|
|
435
|
+
"Rules review: this run is over. Read its events.jsonl, digests/ and lanes/ with read_run_file, and the repo's standing rules with read_knowledge (one rule per line; a [role, …] tag after the source scopes a line to those roles). Propose at most five changes, each with the event it comes from: a one-line rule that would have prevented a review round, a failed check, a climb or a question (tagged [role, …] when it is not for every role), or the removal of a line that is stale, covers one past incident only, or repeats another. Propose no change when none would have helped. Write nothing.";
|
|
436
|
+
|
|
437
|
+
/** The judge's brief: its kind and unit, the context, the numbered findings and the lanes, nothing else. */
|
|
438
|
+
export function judgeBrief(run: Run, input: JudgeInput): string {
|
|
439
|
+
if (input.kind === "rules")
|
|
440
|
+
return [
|
|
441
|
+
RULES_REVIEW_BRIEF,
|
|
442
|
+
"",
|
|
443
|
+
input.context,
|
|
444
|
+
"",
|
|
445
|
+
`Kind: ${input.kind}. Unit: ${input.unit}. Your verbs: ${JUDGE_VERBS[input.kind].join(", ")}.`,
|
|
446
|
+
].join("\n");
|
|
447
|
+
const reply = /^reply: (\S+)$/m.exec(input.context)?.[1];
|
|
448
|
+
const tail = reply ? read(join(run.dir, reply)).trimEnd().slice(-REPLY_TAIL) : "";
|
|
449
|
+
const verbs = JUDGE_VERBS[input.kind].join(", ");
|
|
450
|
+
// a worker's DESIGN: line (in the reply, or quoted in the context) is a conflict between the plan and the code
|
|
451
|
+
const design =
|
|
452
|
+
input.kind === "question" &&
|
|
453
|
+
(/^\s*DESIGN:/m.test(input.context) ||
|
|
454
|
+
parseReplyDesign(reply ? read(join(run.dir, reply)) : "").length > 0);
|
|
455
|
+
return [
|
|
456
|
+
`You are the judge of catherd run ${run.id} (${run.meta.title}). One decision, from only what this brief gives you and the run's files (${run.dir}). You never edit a file.`,
|
|
457
|
+
"",
|
|
458
|
+
`Kind: ${input.kind}. Unit: ${input.unit}. Your verbs: ${verbs}.`,
|
|
459
|
+
"",
|
|
460
|
+
"The A-lines this run must meet:",
|
|
461
|
+
ALINES(run),
|
|
462
|
+
"",
|
|
463
|
+
input.context,
|
|
464
|
+
...(design
|
|
465
|
+
? [
|
|
466
|
+
"",
|
|
467
|
+
"The worker's DESIGN: line says the approved plan conflicts with the code. Answer only when the plan itself settles it (another task, its contracts, its decisions); a change to what the plan decided is the owner's: ask_owner.",
|
|
468
|
+
...laneTasks(run, input.unit).map(taskBlock),
|
|
469
|
+
]
|
|
470
|
+
: []),
|
|
471
|
+
...(tail ? ["", "The reply's last lines:", tail] : []),
|
|
472
|
+
...(input.findings.length ? ["", "Findings:", ...input.findings.map((f, i) => `${i + 1}. ${f}`)] : []),
|
|
473
|
+
"",
|
|
474
|
+
"Lanes of the milestone and what each owns:",
|
|
475
|
+
...input.lanes.map((l) => `- ${l.id}: ${l.owns.join(", ") || "(nothing)"}`),
|
|
476
|
+
].join("\n");
|
|
477
|
+
}
|