@viraatdas/rudder 2.10.22 → 2.10.24

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.
Files changed (47) hide show
  1. package/README.md +41 -0
  2. package/dist/improve/advisor.d.ts +10 -0
  3. package/dist/improve/advisor.js +115 -0
  4. package/dist/improve/advisor.js.map +1 -0
  5. package/dist/improve/collect.d.ts +23 -0
  6. package/dist/improve/collect.js +228 -0
  7. package/dist/improve/collect.js.map +1 -0
  8. package/dist/improve/gate.d.ts +17 -0
  9. package/dist/improve/gate.js +60 -0
  10. package/dist/improve/gate.js.map +1 -0
  11. package/dist/improve/index.d.ts +9 -0
  12. package/dist/improve/index.js +336 -0
  13. package/dist/improve/index.js.map +1 -0
  14. package/dist/improve/judge.d.ts +22 -0
  15. package/dist/improve/judge.js +101 -0
  16. package/dist/improve/judge.js.map +1 -0
  17. package/dist/improve/mine.d.ts +25 -0
  18. package/dist/improve/mine.js +173 -0
  19. package/dist/improve/mine.js.map +1 -0
  20. package/dist/improve/propose.d.ts +51 -0
  21. package/dist/improve/propose.js +150 -0
  22. package/dist/improve/propose.js.map +1 -0
  23. package/dist/improve/rank.d.ts +16 -0
  24. package/dist/improve/rank.js +49 -0
  25. package/dist/improve/rank.js.map +1 -0
  26. package/dist/improve/schedule.d.ts +1 -0
  27. package/dist/improve/schedule.js +93 -0
  28. package/dist/improve/schedule.js.map +1 -0
  29. package/dist/improve/ship.d.ts +25 -0
  30. package/dist/improve/ship.js +82 -0
  31. package/dist/improve/ship.js.map +1 -0
  32. package/dist/improve/state.d.ts +152 -0
  33. package/dist/improve/state.js +261 -0
  34. package/dist/improve/state.js.map +1 -0
  35. package/dist/jj.js +4 -0
  36. package/dist/jj.js.map +1 -1
  37. package/dist/main.js +11 -0
  38. package/dist/main.js.map +1 -1
  39. package/dist/native/darwin-arm64/rudder-native +0 -0
  40. package/dist/native/darwin-x64/rudder-native +0 -0
  41. package/dist/native/linux-x64/rudder-native +0 -0
  42. package/dist/task-summary.d.ts +6 -0
  43. package/dist/task-summary.js +6 -1
  44. package/dist/task-summary.js.map +1 -1
  45. package/dist/types.d.ts +20 -0
  46. package/dist/types.js.map +1 -1
  47. package/package.json +1 -1
package/README.md CHANGED
@@ -16,6 +16,11 @@ isolated, reviewable, and easy to merge. It opens a native three-pane dashboard,
16
16
  gives every task its own git worktree, and runs real Claude Code or Codex
17
17
  processes in the worker pane.
18
18
 
19
+ > **AI agents and contributors:** this README is for using Rudder. If you are
20
+ > an AI agent (or a human) working on the Rudder codebase itself, read
21
+ > [`AGENTS.md`](./AGENTS.md) first; it is the engineering reference and the
22
+ > source of truth for architecture, conventions, and release rules.
23
+
19
24
  ## Install
20
25
 
21
26
  ```bash
@@ -351,6 +356,42 @@ rudder cleanup
351
356
  `rudder mouse-test parsed`. Set `RUDDER_WHEEL_SCROLL_ROWS=<n>` to change the
352
357
  scroll step, or `RUDDER_MOUSE_DEBUG=1` to inspect routing.
353
358
 
359
+ ## Continual improvement loop
360
+
361
+ Rudder improves itself from its own telemetry. Every run already leaves a
362
+ record on disk (`.rudder/runs/`, event logs, verifier results, steer history,
363
+ token usage). The improvement loop, `rudder improve`, is a scheduled local
364
+ batch job (launchd on macOS, not a resident daemon) that:
365
+
366
+ 1. **Collects** recent session telemetry across your registered projects,
367
+ redacting secrets at the source.
368
+ 2. **Mines** it into ranked friction findings (failed runs, user redirects,
369
+ merge conflicts, verifier misses) using the advisor pattern: a Sonnet
370
+ executor consults a Fable 5 advisor mid-generation, so most tokens bill at
371
+ the executor rate.
372
+ 3. **Proposes** fixes with headless agents in isolated worktrees of the
373
+ rudder repo, each briefed with a rich context pack (finding, evidence,
374
+ surface map, prior failed attempts, repo conventions).
375
+ 4. **Judges** each candidate with the repo's full test gates plus a
376
+ three-lens adversarial LLM panel that fails closed.
377
+ 5. **Ships** survivors automatically: rebase onto `origin/main`,
378
+ `npm version patch`, push main + tag, and the normal tag-driven CI
379
+ publishes the new version to npm. Later cycles verify the targeted metric
380
+ actually improved and flag regressions for revert.
381
+
382
+ ```bash
383
+ rudder improve run --dry-run # see what it would do, propose nothing
384
+ rudder improve schedule install # nightly cycle at 03:30 via launchd
385
+ rudder improve status # shipped versions, metrics trend, ledger
386
+ ```
387
+
388
+ Everything stays on your machine, spend is hard-capped per cycle
389
+ (`improve.budgetUsd`, default $5), autonomy is configurable
390
+ (`improve.autonomy`: `observe` | `propose` | `ship`), and `RUDDER_IMPROVE=0`
391
+ disables it. The full harness spec is in
392
+ [`docs/continual-improvement.md`](./docs/continual-improvement.md);
393
+ implementation details live in [`AGENTS.md`](./AGENTS.md) section 15.
394
+
354
395
  ## Building from source
355
396
 
356
397
  ```bash
@@ -0,0 +1,10 @@
1
+ import type { SpendMeter } from "./state.js";
2
+ export declare function callAdvisedTextModel(params: {
3
+ executorModel: string;
4
+ advisorModel: string;
5
+ system: string;
6
+ user: string;
7
+ maxTokens?: number;
8
+ timeoutMs?: number;
9
+ meter?: SpendMeter;
10
+ }): Promise<string>;
@@ -0,0 +1,115 @@
1
+ import { callTextModel, resolveAnthropicApiKey } from "../task-summary.js";
2
+ /**
3
+ * Advisor-pattern text call (Anthropic advisor tool, beta
4
+ * advisor-tool-2026-03-01): a cheaper executor model generates the output and
5
+ * consults a higher-intelligence advisor mid-generation for strategic
6
+ * guidance. Most tokens bill at the executor rate; the loop gets near
7
+ * advisor-grade judgment on its mining and judging calls at a fraction of the
8
+ * cost of running the big model end to end.
9
+ *
10
+ * Degradation path: the deterministic test hook and the no-API-key case (CLI
11
+ * auth) fall back to callTextModel, and any advisor-request failure falls
12
+ * back to a plain executor-only call, so the loop never hard-depends on the
13
+ * beta.
14
+ */
15
+ const ADVISOR_BETA = "advisor-tool-2026-03-01";
16
+ const ADVISOR_MAX_TOKENS = 2048;
17
+ const MAX_PAUSE_RESUMES = 3;
18
+ const ADVISOR_SYSTEM_NUDGE = `
19
+
20
+ You have access to an \`advisor\` tool backed by a stronger reviewer model. It takes NO parameters; your full context is forwarded automatically. Consult it once before committing to your answer, weigh its guidance seriously, then produce the final output yourself in the requested format.`;
21
+ const ADVISOR_USER_SUFFIX = "\n\n(Advisor: please keep your guidance under 200 words; a focused verdict on the hard part beats a comprehensive plan.)";
22
+ export async function callAdvisedTextModel(params) {
23
+ const plainFallback = () => callTextModel({
24
+ model: params.executorModel,
25
+ system: params.system,
26
+ user: params.user,
27
+ maxTokens: params.maxTokens,
28
+ timeoutMs: params.timeoutMs,
29
+ });
30
+ // Deterministic test hook and disabled-advisor path go straight through.
31
+ if (process.env.RUDDER_FAKE_MODEL_OUTPUT || !params.advisorModel) {
32
+ return await plainFallback();
33
+ }
34
+ const apiKey = await resolveAnthropicApiKey();
35
+ if (!apiKey) {
36
+ // CLI-auth users have no raw key to hit the API with; use the CLI path.
37
+ return await plainFallback();
38
+ }
39
+ const tools = [
40
+ {
41
+ type: "advisor_20260301",
42
+ name: "advisor",
43
+ model: params.advisorModel,
44
+ max_tokens: ADVISOR_MAX_TOKENS,
45
+ },
46
+ ];
47
+ const messages = [
48
+ { role: "user", content: params.user + ADVISOR_USER_SUFFIX },
49
+ ];
50
+ const timeoutMs = params.timeoutMs ?? 240000;
51
+ const collected = [];
52
+ for (let attempt = 0; attempt <= MAX_PAUSE_RESUMES; attempt += 1) {
53
+ let data;
54
+ try {
55
+ const controller = new AbortController();
56
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
57
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
58
+ method: "POST",
59
+ headers: {
60
+ "content-type": "application/json",
61
+ "x-api-key": apiKey,
62
+ "anthropic-version": "2023-06-01",
63
+ "anthropic-beta": ADVISOR_BETA,
64
+ },
65
+ body: JSON.stringify({
66
+ model: params.executorModel,
67
+ max_tokens: params.maxTokens ?? 2048,
68
+ system: params.system + ADVISOR_SYSTEM_NUDGE,
69
+ tools,
70
+ messages,
71
+ }),
72
+ signal: controller.signal,
73
+ });
74
+ clearTimeout(timer);
75
+ if (!response.ok) {
76
+ // Invalid pair / beta unavailable / overloaded: degrade to plain.
77
+ return await plainFallback();
78
+ }
79
+ data = (await response.json());
80
+ }
81
+ catch {
82
+ return await plainFallback();
83
+ }
84
+ meterIterations(params, data);
85
+ for (const block of data.content ?? []) {
86
+ if (block.type === "text" && typeof block.text === "string") {
87
+ collected.push(block.text);
88
+ }
89
+ }
90
+ if (data.stop_reason === "pause_turn" && attempt < MAX_PAUSE_RESUMES) {
91
+ // Resume the paused turn: round-trip the assistant content verbatim.
92
+ messages.push({ role: "assistant", content: data.content ?? [] });
93
+ continue;
94
+ }
95
+ break;
96
+ }
97
+ const text = collected.join("").trim();
98
+ return text || (await plainFallback());
99
+ }
100
+ function meterIterations(params, data) {
101
+ if (!params.meter)
102
+ return;
103
+ const iterations = data.usage?.iterations;
104
+ if (iterations && iterations.length > 0) {
105
+ for (const iteration of iterations) {
106
+ const model = iteration.type === "advisor_message"
107
+ ? (iteration.model ?? params.advisorModel)
108
+ : params.executorModel;
109
+ params.meter.addModelTokens(model, iteration.input_tokens ?? 0, iteration.output_tokens ?? 0);
110
+ }
111
+ return;
112
+ }
113
+ params.meter.addModelTokens(params.executorModel, data.usage?.input_tokens ?? 0, data.usage?.output_tokens ?? 0);
114
+ }
115
+ //# sourceMappingURL=advisor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"advisor.js","sourceRoot":"","sources":["../../src/improve/advisor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { RunRecord } from "../types.js";
2
+ import type { ImproveSettings, MetricsSnapshot, SessionRecord, Watermark } from "./state.js";
3
+ /**
4
+ * Walk registered projects and build redacted session records for every run
5
+ * newer than the watermark. Pure telemetry read; never writes into a project.
6
+ */
7
+ export declare function collectSessions(settings: ImproveSettings, watermark: Watermark): Promise<{
8
+ sessions: SessionRecord[];
9
+ nextWatermark: Watermark;
10
+ }>;
11
+ /**
12
+ * Whether a run experienced an integration conflict at ANY point, not just one
13
+ * still unresolved at collect time. `status`/`merge.status` are LIVE state:
14
+ * once a resolver or re-merge succeeds they are rewritten to merged, which is
15
+ * why counting them alone reported mergeConflictRate 0 while resolver-labeled
16
+ * sessions were everywhere. Reads, in order of reliability: the durable
17
+ * `hadMergeConflict` marker, the live conflict state, `resolverFor` (the
18
+ * daemon's resolver runs), and finally the native resolver task labels, which
19
+ * are the only trace on records written before `hadMergeConflict` existed
20
+ * (formats set in native/src/main.rs start_conflict_resolution_agent).
21
+ */
22
+ export declare function runHadMergeConflict(run: Pick<RunRecord, "status" | "merge" | "sync" | "hadMergeConflict" | "resolverFor" | "task" | "taskSummary">): boolean;
23
+ export declare function computeMetrics(cycleId: string, sessions: SessionRecord[]): MetricsSnapshot;
@@ -0,0 +1,228 @@
1
+ import path from "node:path";
2
+ import { promises as fs } from "node:fs";
3
+ import { loadProjects, runRecordPath, runsDir, verifierPath } from "../state.js";
4
+ import { readJson } from "../util.js";
5
+ import { redactTaskSummarySecrets } from "../task-summary.js";
6
+ const MAX_SESSIONS_PER_PROJECT = 200;
7
+ const MAX_ERROR_EXCERPTS = 3;
8
+ const ERROR_EXCERPT_CHARS = 300;
9
+ const EVENTS_TAIL_BYTES = 64 * 1024;
10
+ /**
11
+ * Walk registered projects and build redacted session records for every run
12
+ * newer than the watermark. Pure telemetry read; never writes into a project.
13
+ */
14
+ export async function collectSessions(settings, watermark) {
15
+ const projects = await loadProjects();
16
+ const sessions = [];
17
+ const nextWatermark = { version: 1, projects: { ...watermark.projects } };
18
+ for (const project of projects) {
19
+ if (!project.repoRoot)
20
+ continue;
21
+ if (settings.excludeProjects.some((pattern) => matchesGlobish(project.repoRoot, pattern))) {
22
+ continue;
23
+ }
24
+ let exists = false;
25
+ try {
26
+ exists = (await fs.stat(project.repoRoot)).isDirectory();
27
+ }
28
+ catch {
29
+ exists = false;
30
+ }
31
+ if (!exists)
32
+ continue;
33
+ const since = watermark.projects[project.repoRoot] ?? "";
34
+ const runs = await readRunsRaw(project.repoRoot);
35
+ const fresh = runs
36
+ .filter((run) => (run.updatedAt ?? "") > since)
37
+ // Skip runs still in flight; their story is not over yet.
38
+ .filter((run) => !["created", "running", "steering", "verifying"].includes(run.status))
39
+ .sort((a, b) => (a.updatedAt < b.updatedAt ? -1 : 1))
40
+ .slice(-MAX_SESSIONS_PER_PROJECT);
41
+ for (const run of fresh) {
42
+ sessions.push(await sessionFromRun(project.name ?? project.slug ?? project.repoRoot, run));
43
+ }
44
+ const newest = fresh.at(-1)?.updatedAt;
45
+ if (newest && newest > (nextWatermark.projects[project.repoRoot] ?? "")) {
46
+ nextWatermark.projects[project.repoRoot] = newest;
47
+ }
48
+ }
49
+ return { sessions, nextWatermark };
50
+ }
51
+ /**
52
+ * Read run records WITHOUT the state.ts load path: loadRunRecord fires the
53
+ * background LLM task summarizer per record, which both costs model calls and
54
+ * rewrites run.json (bumping updatedAt past the watermark). Telemetry
55
+ * collection must be a pure read.
56
+ */
57
+ async function readRunsRaw(repoRoot) {
58
+ let entries = [];
59
+ try {
60
+ const dirents = await fs.readdir(runsDir(repoRoot), { withFileTypes: true });
61
+ entries = dirents.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
62
+ }
63
+ catch {
64
+ return [];
65
+ }
66
+ const runs = await Promise.all(entries.map((id) => readJson(runRecordPath(repoRoot, id))));
67
+ return runs.filter((run) => Boolean(run?.id && run?.status && run?.updatedAt));
68
+ }
69
+ /**
70
+ * Whether a run experienced an integration conflict at ANY point, not just one
71
+ * still unresolved at collect time. `status`/`merge.status` are LIVE state:
72
+ * once a resolver or re-merge succeeds they are rewritten to merged, which is
73
+ * why counting them alone reported mergeConflictRate 0 while resolver-labeled
74
+ * sessions were everywhere. Reads, in order of reliability: the durable
75
+ * `hadMergeConflict` marker, the live conflict state, `resolverFor` (the
76
+ * daemon's resolver runs), and finally the native resolver task labels, which
77
+ * are the only trace on records written before `hadMergeConflict` existed
78
+ * (formats set in native/src/main.rs start_conflict_resolution_agent).
79
+ */
80
+ export function runHadMergeConflict(run) {
81
+ if (run.hadMergeConflict)
82
+ return true;
83
+ if (run.status === "merge-conflict")
84
+ return true;
85
+ if (run.merge?.status === "conflict" || run.sync?.status === "conflict")
86
+ return true;
87
+ if (run.resolverFor)
88
+ return true;
89
+ if (/^(?:merge|rebase) conflicts → /.test(run.taskSummary ?? ""))
90
+ return true;
91
+ return /^Resolve (?:merge|rebase) conflicts(?::|$)/.test(run.task ?? "");
92
+ }
93
+ async function sessionFromRun(project, run) {
94
+ const steerCount = (run.turns ?? []).filter((turn) => turn.source === "steerer" || turn.source === "regoal").length;
95
+ const startedAt = run.process?.startedAt ?? run.createdAt;
96
+ const endedAt = run.process?.endedAt ?? run.updatedAt;
97
+ let durationMs;
98
+ const startMs = Date.parse(startedAt ?? "");
99
+ const endMs = Date.parse(endedAt ?? "");
100
+ if (Number.isFinite(startMs) && Number.isFinite(endMs) && endMs >= startMs) {
101
+ durationMs = endMs - startMs;
102
+ }
103
+ let verifierSatisfied;
104
+ let verifierMissing;
105
+ const verification = run.verification ??
106
+ (await readJson(verifierPath(run.repoRoot, run.id))) ??
107
+ undefined;
108
+ if (verification && typeof verification.satisfied === "boolean") {
109
+ verifierSatisfied = verification.satisfied;
110
+ verifierMissing = (verification.missing ?? []).slice(0, 5).map(redactAndTrim);
111
+ }
112
+ const hadMergeConflict = runHadMergeConflict(run);
113
+ const interesting = run.status === "failed" ||
114
+ run.status === "cancelled" ||
115
+ hadMergeConflict ||
116
+ verifierSatisfied === false;
117
+ return {
118
+ project,
119
+ repoRoot: run.repoRoot,
120
+ runId: run.id,
121
+ task: redactAndTrim(run.taskSummary ?? run.task ?? "", 160),
122
+ backend: run.backend,
123
+ model: run.model,
124
+ effort: run.effort,
125
+ status: run.status,
126
+ createdAt: run.createdAt,
127
+ updatedAt: run.updatedAt,
128
+ durationMs,
129
+ steerCount,
130
+ autoSteerCount: run.autoSteer?.count ?? 0,
131
+ tokensIn: run.tokens?.input ?? 0,
132
+ tokensOut: run.tokens?.output ?? 0,
133
+ verifierSatisfied,
134
+ verifierMissing,
135
+ mergeStatus: run.merge?.status,
136
+ hadMergeConflict,
137
+ errorExcerpts: interesting ? await readErrorExcerpts(run) : [],
138
+ };
139
+ }
140
+ /**
141
+ * Read the tail of a run's events.ndjson and pull out error-shaped events.
142
+ * Redaction happens here, before anything reaches a model.
143
+ */
144
+ async function readErrorExcerpts(run) {
145
+ const eventsFile = path.join(run.repoRoot, ".rudder", "runs", run.id, "events.ndjson");
146
+ let raw = "";
147
+ try {
148
+ const handle = await fs.open(eventsFile, "r");
149
+ try {
150
+ const stat = await handle.stat();
151
+ const start = Math.max(0, stat.size - EVENTS_TAIL_BYTES);
152
+ const buffer = Buffer.alloc(stat.size - start);
153
+ await handle.read(buffer, 0, buffer.length, start);
154
+ raw = buffer.toString("utf8");
155
+ }
156
+ finally {
157
+ await handle.close();
158
+ }
159
+ }
160
+ catch {
161
+ return [];
162
+ }
163
+ const excerpts = [];
164
+ for (const line of raw.split("\n").reverse()) {
165
+ if (excerpts.length >= MAX_ERROR_EXCERPTS)
166
+ break;
167
+ const trimmed = line.trim();
168
+ if (!trimmed)
169
+ continue;
170
+ let event = null;
171
+ try {
172
+ event = JSON.parse(trimmed);
173
+ }
174
+ catch {
175
+ continue;
176
+ }
177
+ const type = event?.type ?? "";
178
+ if (!/error|fail/i.test(type))
179
+ continue;
180
+ const text = event?.text ?? event?.message ?? "";
181
+ if (!text.trim())
182
+ continue;
183
+ excerpts.push(`[${type}] ${redactAndTrim(text, ERROR_EXCERPT_CHARS)}`);
184
+ }
185
+ return excerpts;
186
+ }
187
+ export function computeMetrics(cycleId, sessions) {
188
+ const n = sessions.length;
189
+ const rate = (count) => (n === 0 ? 0 : round4(count / n));
190
+ const failed = sessions.filter((s) => s.status === "failed").length;
191
+ const cancelled = sessions.filter((s) => s.status === "cancelled").length;
192
+ const conflicts = sessions.filter((s) => s.hadMergeConflict || s.status === "merge-conflict" || s.mergeStatus === "conflict").length;
193
+ const verifierMisses = sessions.filter((s) => s.verifierSatisfied === false).length;
194
+ const steered = sessions.filter((s) => s.steerCount > 0).length;
195
+ const durations = sessions
196
+ .map((s) => s.durationMs)
197
+ .filter((d) => typeof d === "number")
198
+ .sort((a, b) => a - b);
199
+ return {
200
+ ts: new Date().toISOString(),
201
+ cycleId,
202
+ runsSeen: n,
203
+ failedRate: rate(failed),
204
+ cancelledRate: rate(cancelled),
205
+ steerRate: rate(steered),
206
+ mergeConflictRate: rate(conflicts),
207
+ verifierMissRate: rate(verifierMisses),
208
+ medianDurationMs: durations.length ? durations[Math.floor(durations.length / 2)] : 0,
209
+ totalTokensIn: sessions.reduce((sum, s) => sum + s.tokensIn, 0),
210
+ totalTokensOut: sessions.reduce((sum, s) => sum + s.tokensOut, 0),
211
+ };
212
+ }
213
+ function redactAndTrim(value, maxChars = 240) {
214
+ const redacted = redactTaskSummarySecrets(value).replace(/\s+/g, " ").trim();
215
+ return redacted.length > maxChars ? `${redacted.slice(0, maxChars - 1)}…` : redacted;
216
+ }
217
+ function round4(value) {
218
+ return Math.round(value * 10000) / 10000;
219
+ }
220
+ /** Minimal glob support: `*` wildcards only, matched against the repo path. */
221
+ function matchesGlobish(value, pattern) {
222
+ const regex = new RegExp(`^${pattern.split("*").map(escapeRegExp).join(".*")}$`);
223
+ return regex.test(value);
224
+ }
225
+ function escapeRegExp(value) {
226
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
227
+ }
228
+ //# sourceMappingURL=collect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect.js","sourceRoot":"","sources":["../../src/improve/collect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { Proposal } from "./propose.js";
2
+ export type GateResult = {
3
+ passed: boolean;
4
+ steps: Array<{
5
+ name: string;
6
+ code: number;
7
+ timedOut: boolean;
8
+ failureTail?: string;
9
+ }>;
10
+ };
11
+ /**
12
+ * The repo's own deterministic gates, run inside the proposal worktree before
13
+ * any judge tokens are spent. Order matches AGENTS.md section 11: typecheck,
14
+ * native tests when native/ changed, then the node test suite (which builds).
15
+ */
16
+ export declare function runGates(proposal: Proposal): Promise<GateResult>;
17
+ export declare function gateSummary(gate: GateResult): string;
@@ -0,0 +1,60 @@
1
+ import path from "node:path";
2
+ import { execStep, logsDir, tail } from "./state.js";
3
+ const MINUTE = 60 * 1000;
4
+ /**
5
+ * The repo's own deterministic gates, run inside the proposal worktree before
6
+ * any judge tokens are spent. Order matches AGENTS.md section 11: typecheck,
7
+ * native tests when native/ changed, then the node test suite (which builds).
8
+ */
9
+ export async function runGates(proposal) {
10
+ const logFile = path.join(logsDir(), `${proposal.finding.id}-gates.log`);
11
+ const nativeChanged = proposal.changedFiles.some((file) => file.startsWith("native/"));
12
+ const steps = [
13
+ {
14
+ name: "npm ci",
15
+ command: "npm",
16
+ args: ["ci", "--no-audit", "--no-fund"],
17
+ timeoutMs: 15 * MINUTE,
18
+ },
19
+ { name: "npm run check", command: "npm", args: ["run", "check"], timeoutMs: 10 * MINUTE },
20
+ ...(nativeChanged
21
+ ? [
22
+ {
23
+ name: "cargo test",
24
+ command: "cargo",
25
+ args: ["test", "--manifest-path", "native/Cargo.toml"],
26
+ timeoutMs: 40 * MINUTE,
27
+ },
28
+ ]
29
+ : []),
30
+ { name: "npm test", command: "npm", args: ["test"], timeoutMs: 40 * MINUTE },
31
+ ];
32
+ const result = { passed: true, steps: [] };
33
+ for (const step of steps) {
34
+ const outcome = await execStep({
35
+ command: step.command,
36
+ args: step.args,
37
+ cwd: proposal.worktree,
38
+ timeoutMs: step.timeoutMs,
39
+ logFile,
40
+ });
41
+ const entry = {
42
+ name: step.name,
43
+ code: outcome.code,
44
+ timedOut: outcome.timedOut,
45
+ ...(outcome.code !== 0 ? { failureTail: tail(outcome.output, 4000) } : {}),
46
+ };
47
+ result.steps.push(entry);
48
+ if (outcome.code !== 0) {
49
+ result.passed = false;
50
+ break;
51
+ }
52
+ }
53
+ return result;
54
+ }
55
+ export function gateSummary(gate) {
56
+ return gate.steps
57
+ .map((step) => `${step.name}: ${step.code === 0 ? "ok" : step.timedOut ? "TIMEOUT" : `exit ${step.code}`}`)
58
+ .join(", ");
59
+ }
60
+ //# sourceMappingURL=gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../../src/improve/gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ type ImproveArgs = {
2
+ args: string[];
3
+ flags: {
4
+ dryRun?: boolean;
5
+ json?: boolean;
6
+ };
7
+ };
8
+ export declare function runImprove(parsed: ImproveArgs): Promise<void>;
9
+ export {};