codiedev 0.9.72 → 0.9.76

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/dist/cli.js CHANGED
@@ -30,6 +30,8 @@ const share_1 = require("./commands/share");
30
30
  const send_1 = require("./commands/send");
31
31
  const capture_1 = require("./commands/capture");
32
32
  const pack_1 = require("./commands/pack");
33
+ const traces_1 = require("./commands/traces");
34
+ const evals_1 = require("./commands/evals");
33
35
  // --- starter packs phase 2 ---
34
36
  const hookGate_1 = require("./packs/hookGate");
35
37
  const sync_1 = require("./packs/sync");
@@ -91,6 +93,16 @@ Starter packs:
91
93
  detected tool
92
94
  codiedev pack status Installed packs, up to date or not
93
95
 
96
+ Traces (agents on the gateway):
97
+ codiedev traces agents Agents you maintain, with saved test cases
98
+ codiedev traces cases <agent> [--dataset name] [--model id]
99
+ Pull an agent's test cases (or one dataset)
100
+ into this repo for your coding agent to
101
+ run, optionally on another model
102
+ codiedev traces report <results.json> Send the results back to Traces
103
+ codiedev evals report <run.json> File one variant's eval scores under an experiment
104
+ codiedev evals last <subject> Latest experiment on a subject + its champion
105
+
94
106
  Discovery:
95
107
  codiedev search "<query>" Semantic search across the team's
96
108
  captured sessions and artifacts
@@ -152,6 +164,8 @@ status sync never weakens it.
152
164
  `.trim();
153
165
  const COMMAND_HELP = {
154
166
  pack: pack_1.PACK_HELP,
167
+ traces: traces_1.TRACES_HELP,
168
+ evals: evals_1.EVALS_HELP,
155
169
  connect: `
156
170
  codiedev connect — link your agent CLI to Codiedev
157
171
 
@@ -515,6 +529,12 @@ async function main() {
515
529
  case "ls":
516
530
  await (0, library_1.runLibrary)(rest);
517
531
  return;
532
+ case "traces":
533
+ await (0, traces_1.runTraces)(rest);
534
+ return;
535
+ case "evals":
536
+ await (0, evals_1.runEvals)(rest);
537
+ return;
518
538
  case "share":
519
539
  await (0, share_1.runShare)(rest);
520
540
  return;
@@ -69,10 +69,16 @@ export declare function resolveCapturePlugin({ homeDir, existsSync, }: {
69
69
  homeDir: string;
70
70
  existsSync: (candidate: string) => boolean;
71
71
  }): string;
72
- export declare function fetchApprovedCatalog({ baseUrl, key, fetchImpl, }: {
72
+ /**
73
+ * The account's approved models. Every `codie` session starts here, so one
74
+ * dropped connection or slow gateway reply would end the session before it
75
+ * began; anything but a definite rejection is retried once.
76
+ */
77
+ export declare function fetchApprovedCatalog({ baseUrl, key, fetchImpl, retryDelayMs, }: {
73
78
  baseUrl?: string;
74
79
  key: string;
75
80
  fetchImpl?: typeof fetch;
81
+ retryDelayMs?: number;
76
82
  }): Promise<CatalogModel[]>;
77
83
  export declare function parseCodieArgs(argv: string[]): {
78
84
  args: string[];
@@ -349,9 +349,29 @@ function resolveCapturePlugin({ homeDir, existsSync, }) {
349
349
  }
350
350
  return (0, url_1.pathToFileURL)(capturePath).href;
351
351
  }
352
- async function fetchApprovedCatalog({ baseUrl, key, fetchImpl = fetch, }) {
352
+ // A definite answer from the gateway (harness off, token rejected): retrying
353
+ // can't change it.
354
+ class CatalogRejected extends Error {
355
+ }
356
+ /**
357
+ * The account's approved models. Every `codie` session starts here, so one
358
+ * dropped connection or slow gateway reply would end the session before it
359
+ * began; anything but a definite rejection is retried once.
360
+ */
361
+ async function fetchApprovedCatalog({ baseUrl, key, fetchImpl = fetch, retryDelayMs = 1_000, }) {
362
+ try {
363
+ return await fetchCatalogOnce(baseUrl, key, fetchImpl);
364
+ }
365
+ catch (error) {
366
+ if (error instanceof CatalogRejected)
367
+ throw error;
368
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
369
+ return fetchCatalogOnce(baseUrl, key, fetchImpl);
370
+ }
371
+ }
372
+ async function fetchCatalogOnce(baseUrl, key, fetchImpl) {
353
373
  const controller = new AbortController();
354
- const timeout = setTimeout(() => controller.abort(), 5000);
374
+ const timeout = setTimeout(() => controller.abort(), 10_000);
355
375
  try {
356
376
  const response = await fetchImpl(`${cleanBaseUrl(baseUrl)}/models`, {
357
377
  headers: { Authorization: `Bearer ${key}` },
@@ -361,11 +381,11 @@ async function fetchApprovedCatalog({ baseUrl, key, fetchImpl = fetch, }) {
361
381
  // whose gateway is still switched off. Those developers must get an
362
382
  // answer, not an HTTP status.
363
383
  if (response.status === 403) {
364
- throw new Error("The Codie harness is not enabled for your company yet. Ask your Codiedev admin to turn it on — " +
384
+ throw new CatalogRejected("The Codie harness is not enabled for your company yet. Ask your Codiedev admin to turn it on — " +
365
385
  "your session capture keeps working in the meantime.");
366
386
  }
367
387
  if (response.status === 401) {
368
- throw new Error("Your Codiedev token was rejected. Run `codiedev connect` to relink this machine.");
388
+ throw new CatalogRejected("Your Codiedev token was rejected. Run `codiedev connect` to relink this machine.");
369
389
  }
370
390
  if (!response.ok)
371
391
  throw new Error(`model catalog returned ${response.status}`);
@@ -0,0 +1,3 @@
1
+ import type { CodiedevConfig } from "../utils";
2
+ export declare const EVALS_HELP: string;
3
+ export declare function runEvals(args: string[], configOverride?: CodiedevConfig): Promise<void>;
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ // `codiedev evals report <run.json> [--harness name]`
3
+ // `codiedev evals last <subject> [--json]`
4
+ //
5
+ // Evals are experiments (a question about a subject: an agent, or a workload
6
+ // like session-summaries) and the runs that answer them, one per variant. The
7
+ // eval runs wherever it runs (a script, a coding agent); `report` files the
8
+ // scores under the experiment in Codiedev → Traces → Evals, shared with the
9
+ // team. `last` is where to start the next one: the latest experiment on the
10
+ // subject, its runs, and the champion the subject runs today.
11
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
+ desc = { enumerable: true, get: function() { return m[k]; } };
16
+ }
17
+ Object.defineProperty(o, k2, desc);
18
+ }) : (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ o[k2] = m[k];
21
+ }));
22
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
24
+ }) : function(o, v) {
25
+ o["default"] = v;
26
+ });
27
+ var __importStar = (this && this.__importStar) || (function () {
28
+ var ownKeys = function(o) {
29
+ ownKeys = Object.getOwnPropertyNames || function (o) {
30
+ var ar = [];
31
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
+ return ar;
33
+ };
34
+ return ownKeys(o);
35
+ };
36
+ return function (mod) {
37
+ if (mod && mod.__esModule) return mod;
38
+ var result = {};
39
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
+ __setModuleDefault(result, mod);
41
+ return result;
42
+ };
43
+ })();
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.EVALS_HELP = void 0;
46
+ exports.runEvals = runEvals;
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
49
+ const shared_1 = require("./shared");
50
+ exports.EVALS_HELP = `
51
+ codiedev evals — experiments and their runs, shared with the team
52
+
53
+ codiedev evals report <run.json> [--harness name]
54
+ File one variant's scores under an
55
+ experiment (created on first use)
56
+ codiedev evals last <subject> [--json] The latest experiment on a subject,
57
+ its runs, and the current champion
58
+
59
+ run.json:
60
+ {
61
+ "subject": "session-summaries", // agent name or workload
62
+ "question": "Is Terra medium as good as GLM high?",
63
+ "experimentId": "…", // optional, instead of subject + question
64
+ "dataset": "30 Signal & Code sessions", // optional
65
+ "run": {
66
+ "label": "terra-med-v2", "model": "openai/gpt-6.1-terra",
67
+ "provider": "openai", "reasoningEffort": "medium", "prompt": "summary_extract_v2",
68
+ "gitSha": "…", "isControl": false, "judgeModel": "anthropic/claude-sonnet-5.5",
69
+ "casesTotal": 30, "casesScored": 30, "errors": 0,
70
+ "score": 3.85, "metrics": { "fidelity": 3.6 },
71
+ "costPerCaseUsd": 0.026, "latencyMsAvg": 21000,
72
+ "wins": 20, "losses": 8, "ties": 2, "note": "…",
73
+ "cases": [{ "key": "case-1", "score": 4, "note": "…" }]
74
+ }
75
+ }
76
+
77
+ Scores and notes only: never put prompts, transcripts or outputs in it.
78
+ `.trim();
79
+ function flag(args, i, name) {
80
+ const a = args[i];
81
+ if (a === name && i + 1 < args.length)
82
+ return { value: args[i + 1], skip: 1 };
83
+ if (a.startsWith(`${name}=`))
84
+ return { value: a.slice(name.length + 1), skip: 0 };
85
+ return { skip: -1 };
86
+ }
87
+ function variant(run) {
88
+ return [run.model, run.reasoningEffort, run.prompt].filter(Boolean).join(" · ");
89
+ }
90
+ function fmtScore(n) {
91
+ return n === null ? "–" : n.toFixed(2);
92
+ }
93
+ function fmtCost(n) {
94
+ return n === null ? "–" : `$${n.toFixed(4)}`;
95
+ }
96
+ async function runReport(args, config) {
97
+ let file;
98
+ let harness;
99
+ for (let i = 0; i < args.length; i++) {
100
+ const f = flag(args, i, "--harness");
101
+ if (f.skip >= 0) {
102
+ harness = f.value;
103
+ i += f.skip;
104
+ continue;
105
+ }
106
+ if (!args[i].startsWith("-") && !file)
107
+ file = args[i];
108
+ }
109
+ if (!file) {
110
+ console.error("Usage: codiedev evals report <run.json> [--harness name]");
111
+ process.exit(1);
112
+ }
113
+ let body;
114
+ try {
115
+ body = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), file), "utf8"));
116
+ }
117
+ catch (err) {
118
+ console.error(`Couldn't read ${file}: ${err.message}`);
119
+ process.exit(1);
120
+ }
121
+ if (!body.subject || !body.question || !body.run?.label || !body.run?.model || typeof body.run?.casesTotal !== "number") {
122
+ console.error(`${file} needs subject, question, and run.label, run.model, run.casesTotal. See: codiedev evals help`);
123
+ process.exit(1);
124
+ }
125
+ const start = Date.now();
126
+ try {
127
+ const res = await (0, shared_1.apiRequest)("POST", "/api/cli/evals/runs", {
128
+ config,
129
+ body: { ...body, run: { ...body.run, harness: harness ?? body.run.harness ?? undefined } },
130
+ });
131
+ (0, shared_1.recordAgentEvent)(config, { tool: "codiedev_evals_report", ok: true, latencyMs: Date.now() - start });
132
+ console.log(`✓ Reported ${body.run.label} on ${body.subject}${res.created ? " (new experiment)" : ""}: "${body.question}"`);
133
+ console.log(` experiment ${res.experimentId}. See it in Codiedev → Traces → Evals.`);
134
+ }
135
+ catch (err) {
136
+ const e = err;
137
+ (0, shared_1.recordAgentEvent)(config, { tool: "codiedev_evals_report", ok: false, latencyMs: Date.now() - start, error: e.message });
138
+ console.error(`Couldn't report: ${e.message}`);
139
+ process.exit(1);
140
+ }
141
+ }
142
+ async function runLast(args, config) {
143
+ const json = args.includes("--json");
144
+ const subject = args.find((a) => !a.startsWith("-"));
145
+ if (!subject) {
146
+ console.error("Usage: codiedev evals last <subject> [--json]");
147
+ process.exit(1);
148
+ }
149
+ let res;
150
+ try {
151
+ res = await (0, shared_1.apiRequest)("GET", `/api/cli/evals/last?subject=${encodeURIComponent(subject)}`, { config });
152
+ }
153
+ catch (err) {
154
+ const e = err;
155
+ console.error(e.status === 404 ? `No evals on ${subject} yet.` : `Couldn't load evals: ${e.message}`);
156
+ process.exit(1);
157
+ }
158
+ if (json) {
159
+ console.log(JSON.stringify(res, null, 2));
160
+ return;
161
+ }
162
+ const { experiment, runs, champion } = res;
163
+ if (champion) {
164
+ console.log(`Champion: ${champion.run.label} (${variant(champion.run)}), score ${fmtScore(champion.run.score)}, ${fmtCost(champion.run.costPerCaseUsd)}/case`);
165
+ if (champion.decisionNote)
166
+ console.log(` Why: ${champion.decisionNote}`);
167
+ }
168
+ else {
169
+ console.log("Champion: none picked yet");
170
+ }
171
+ console.log("");
172
+ console.log(`Latest experiment (${res.experiments} on ${experiment.subject}): ${experiment.question}`);
173
+ if (experiment.dataset)
174
+ console.log(` Dataset: ${experiment.dataset}`);
175
+ console.log(` id ${experiment.id}, by ${experiment.createdBy}, updated ${new Date(experiment.updatedAt).toISOString().slice(0, 16).replace("T", " ")}`);
176
+ console.log("");
177
+ for (const run of [...runs].sort((a, b) => a.ranAt - b.ranAt)) {
178
+ const h2h = run.wins !== null ? ` · ${run.wins}W/${run.losses ?? 0}L/${run.ties ?? 0}T` : "";
179
+ console.log(` ${run.isControl ? "[control] " : ""}${run.label.padEnd(24)} ${fmtScore(run.score).padStart(5)} ${fmtCost(run.costPerCaseUsd).padStart(8)}/case${h2h} ${variant(run)}`);
180
+ }
181
+ console.log("");
182
+ console.log(`Next run on this experiment: put "experimentId": "${experiment.id}" in run.json, then codiedev evals report run.json`);
183
+ }
184
+ async function runEvals(args, configOverride) {
185
+ const [sub, ...rest] = args;
186
+ if (sub === "report")
187
+ return runReport(rest, configOverride ?? (0, shared_1.requireConfig)());
188
+ if (sub === "last")
189
+ return runLast(rest, configOverride ?? (0, shared_1.requireConfig)());
190
+ console.log(exports.EVALS_HELP);
191
+ if (sub && sub !== "help")
192
+ process.exit(1);
193
+ }
@@ -0,0 +1,68 @@
1
+ import type { CodiedevConfig } from "../utils";
2
+ export declare const TRACES_HELP: string;
3
+ interface CaseStep {
4
+ model: string;
5
+ finishReason?: string;
6
+ statusCode: number;
7
+ request?: string;
8
+ reply?: string;
9
+ toolCalls?: Array<{
10
+ name: string;
11
+ arguments: string;
12
+ }>;
13
+ }
14
+ interface CaseRow {
15
+ id: string;
16
+ name: string;
17
+ suite: string | null;
18
+ runId: string;
19
+ kind: "fix" | "keep";
20
+ flags: string[];
21
+ note: string | null;
22
+ expected: string | null;
23
+ createdAt: string;
24
+ steps: CaseStep[];
25
+ }
26
+ interface CasesResponse {
27
+ ok: boolean;
28
+ /** The suite pulled, when --suite was given. */
29
+ suite: string | null;
30
+ /** How the suite is judged, when it has settings. */
31
+ judge: {
32
+ model: string | null;
33
+ rubric: string | null;
34
+ } | null;
35
+ agent: {
36
+ id: string;
37
+ name: string;
38
+ description: string | null;
39
+ promptDays: number | null;
40
+ };
41
+ cases: CaseRow[];
42
+ }
43
+ /** What `report` sends, and what `cases` writes for the coding agent to fill in. */
44
+ export interface ResultsFile {
45
+ agent: string;
46
+ suite: string | null;
47
+ model: string;
48
+ judgeModel: string | null;
49
+ harness: string | null;
50
+ results: Array<{
51
+ caseId: string;
52
+ name: string;
53
+ passes: number | null;
54
+ tries: number;
55
+ note: string | null;
56
+ output: string | null;
57
+ }>;
58
+ }
59
+ export declare function slugify(name: string): string;
60
+ /** The brief a coding agent follows to run the cases and report back. */
61
+ export declare function casesBrief(res: CasesResponse, casesFile: string, opts?: {
62
+ model?: string;
63
+ resultsFile?: string;
64
+ }): string;
65
+ /** The results.json a run starts from: every case, passes to be filled in. */
66
+ export declare function resultsTemplate(res: CasesResponse, model: string | undefined): ResultsFile;
67
+ export declare function runTraces(args: string[], configOverride?: CodiedevConfig): Promise<void>;
68
+ export {};
@@ -0,0 +1,292 @@
1
+ "use strict";
2
+ // `codiedev traces agents`
3
+ // `codiedev traces cases <agent> [--dataset name] [--model id] [--out dir] [--json]`
4
+ // `codiedev traces report <results.json> [--harness name]`
5
+ //
6
+ // Test cases are runs an engineer saved from Codiedev → Traces. They run in
7
+ // the engineer's coding agent, not in the portal: `cases` writes them into the
8
+ // repo with a brief the agent follows to replay each one against the agent's
9
+ // own code, optionally on another model (a regression check before a model
10
+ // swap), and a results.json for `report` to send back so Traces shows pass
11
+ // rates by model.
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.TRACES_HELP = void 0;
47
+ exports.slugify = slugify;
48
+ exports.casesBrief = casesBrief;
49
+ exports.resultsTemplate = resultsTemplate;
50
+ exports.runTraces = runTraces;
51
+ const fs = __importStar(require("fs"));
52
+ const path = __importStar(require("path"));
53
+ const shared_1 = require("./shared");
54
+ exports.TRACES_HELP = `
55
+ codiedev traces — test cases saved from Codiedev → Traces
56
+
57
+ codiedev traces agents Agents you maintain, with saved case counts
58
+ codiedev traces cases <agent> [--dataset name] [--case id[,id]] [--model id] [--out dir] [--json]
59
+ Write an agent's cases (one dataset, or just
60
+ the cases named) into this repo, default
61
+ .codiedev/traces/<agent>[/<dataset>]/, then
62
+ ask your coding agent to run them.
63
+ --model runs them on another model, e.g.
64
+ the replacement for a retiring one
65
+ codiedev traces report <results.json> [--harness name]
66
+ Send the filled-in results back, so
67
+ Traces shows the pass rate for that model
68
+
69
+ <agent> is the agent's name (its API key name) or id. Only an agent's
70
+ maintainers can pull its cases or report on them.
71
+ `.trim();
72
+ const FLAG_LABEL = {
73
+ budget_block: "refused for budget",
74
+ errors: "model call failed",
75
+ tool_loop: "repeated the same tool call",
76
+ truncated: "reply cut off at max_tokens",
77
+ context_overflow: "prompt over 70% of the context window",
78
+ };
79
+ const TRIES = 3;
80
+ const plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
81
+ const recordedModel = (res) => res.cases.flatMap((c) => c.steps.map((s) => s.model))[0] ?? "the recorded model";
82
+ function slugify(name) {
83
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
84
+ }
85
+ /** The brief a coding agent follows to run the cases and report back. */
86
+ function casesBrief(res, casesFile, opts = {}) {
87
+ const fix = res.cases.filter((c) => c.kind === "fix");
88
+ const keep = res.cases.filter((c) => c.kind === "keep");
89
+ const withBodies = res.cases.filter((c) => c.steps.some((s) => s.request)).length;
90
+ const target = opts.model;
91
+ const results = opts.resultsFile ? path.basename(opts.resultsFile) : "results.json";
92
+ const lines = [
93
+ `# Test cases for ${res.agent.name}${res.suite ? ` · ${res.suite}` : ""}${target ? ` on ${target}` : ""}`,
94
+ "",
95
+ res.agent.description ? `${res.agent.description}\n` : "",
96
+ `Saved from Codiedev → Traces. ${plural(res.cases.length, "case")} in \`${path.basename(casesFile)}\` (one JSON object per line):`,
97
+ `- **${fix.length} to fix**: production runs that failed. The fix should make them pass.`,
98
+ `- **${keep.length} to keep passing**: good runs. They should still pass after any change.`,
99
+ "",
100
+ ];
101
+ if (target) {
102
+ lines.push("## This is a model check", "", `Run every case on **${target}** instead of ${recordedModel(res)} (the model the cases were recorded on). Change only the model the agent asks the gateway for; keep its prompts and tools as they are, so the result says how ${target} does on the same work.`, "");
103
+ }
104
+ lines.push("## How to run them", "", "1. Find where this agent builds its model requests and calls the Codiedev gateway.", `2. For each case, replay \`steps[0].request\` (the first prompt the agent sent) through the agent's own code path, so tools and prompts are the current version, not the recorded one${target ? `, with the model set to ${target}` : ""}.`, "3. Judge each try against the case:", " - A case's `expected`, when present, is what a passing answer looks like (a reference reply or criteria). It wins over everything below.", " - `fix` cases: the run must not reproduce its `flags` (listed below), and must finish (no failed call, no `finish_reason: \"length\"`, no repeated identical tool call).", " - `keep` cases: the final reply must still do what the recorded final reply did. Compare meaning, not wording.", " - A case's `note`, when present, is the engineer's extra context.");
105
+ if (res.judge?.model || res.judge?.rubric) {
106
+ lines.push(` - Where passing needs judgement, judge with **${res.judge.model ?? "a strong model"}** through the gateway${res.judge.rubric ? `, using this rubric: "${res.judge.rubric}"` : ""}. Use the same judge for every model so results compare.`);
107
+ }
108
+ lines.push(`4. Run each case ${TRIES} times: agents are stochastic, so report a pass rate per case, not a single result.`, `5. Fill in \`${results}\`: for each case set \`passes\` (out of \`tries\`), \`output\` (the agent's final reply on one try, so it can be compared side by side with other models) and a one-line \`note\` on any failure. Set \`harness\` to the coding agent you are (claude-code, codex, cursor, copilot, …).`, `6. Send it back: \`codiedev traces report ${opts.resultsFile ? path.relative(process.cwd(), opts.resultsFile) || results : results}\`. Then show a table: case, kind, passes out of ${TRIES}, and the failure note.`, "", "Send real model calls through the gateway with the agent's usual key and `X-Codiedev-Run-Id: case-<id>-<try>`, so the replays show up in Traces too.", "");
109
+ if (withBodies < res.cases.length) {
110
+ lines.push(`Note: ${plural(res.cases.length - withBodies, "case")} ${res.cases.length - withBodies === 1 ? "has" : "have"} no recorded prompt (the agent wasn't keeping prompts then). For those, rebuild the input from the case's \`expected\` or \`note\`, or skip them and say so.`, "");
111
+ }
112
+ lines.push("## Cases", "");
113
+ for (const c of res.cases) {
114
+ const flags = c.flags.length ? c.flags.map((f) => FLAG_LABEL[f] ?? f).join(", ") : "no flags";
115
+ lines.push(`- \`${c.id}\` · ${c.name} · ${c.kind} · ${flags} · ${plural(c.steps.length, "model call")}${c.expected ? ` · expected: "${c.expected.replace(/\s+/g, " ").slice(0, 200)}"` : ""}${c.note ? ` · note: "${c.note}"` : ""}`);
116
+ }
117
+ return lines.filter((line, i, all) => !(line === "" && all[i - 1] === "")).join("\n") + "\n";
118
+ }
119
+ /** The results.json a run starts from: every case, passes to be filled in. */
120
+ function resultsTemplate(res, model) {
121
+ return {
122
+ agent: res.agent.name,
123
+ suite: res.suite,
124
+ model: model ?? recordedModel(res),
125
+ judgeModel: res.judge?.model ?? null,
126
+ harness: null,
127
+ results: res.cases.map((c) => ({ caseId: c.id, name: c.name, passes: null, tries: TRIES, note: null, output: null })),
128
+ };
129
+ }
130
+ function flag(args, i, name) {
131
+ const a = args[i];
132
+ if (a === name && i + 1 < args.length)
133
+ return { value: args[i + 1], skip: 1 };
134
+ if (a.startsWith(`${name}=`))
135
+ return { value: a.slice(name.length + 1), skip: 0 };
136
+ return { skip: -1 };
137
+ }
138
+ function parseCasesArgs(args) {
139
+ const parsed = { json: false };
140
+ for (let i = 0; i < args.length; i++) {
141
+ const a = args[i];
142
+ let matched = false;
143
+ for (const [name, key] of [["--out", "out"], ["-o", "out"], ["--dataset", "suite"], ["--suite", "suite"], ["--model", "model"], ["--case", "caseIds"]]) {
144
+ const f = flag(args, i, name);
145
+ if (f.skip >= 0) {
146
+ parsed[key] = f.value;
147
+ i += f.skip;
148
+ matched = true;
149
+ break;
150
+ }
151
+ }
152
+ if (matched)
153
+ continue;
154
+ if (a === "--json")
155
+ parsed.json = true;
156
+ else if (!a.startsWith("-") && !parsed.agent)
157
+ parsed.agent = a;
158
+ }
159
+ return parsed;
160
+ }
161
+ async function runAgents(config) {
162
+ const start = Date.now();
163
+ let res;
164
+ try {
165
+ res = await (0, shared_1.apiRequest)("GET", "/api/cli/traces/agents", { config });
166
+ }
167
+ catch (err) {
168
+ const e = err;
169
+ (0, shared_1.recordAgentEvent)(config, { tool: "codiedev_traces_agents", ok: false, latencyMs: Date.now() - start, error: e.message });
170
+ console.error(`Couldn't list agents: ${e.message}`);
171
+ process.exit(1);
172
+ }
173
+ (0, shared_1.recordAgentEvent)(config, { tool: "codiedev_traces_agents", ok: true, latencyMs: Date.now() - start });
174
+ if (!res.agents.length) {
175
+ console.log("You don't maintain any agents yet. Connect one in Codiedev → Traces → How it works.");
176
+ return;
177
+ }
178
+ for (const agent of res.agents) {
179
+ const capture = agent.promptDays ? `prompts kept ${agent.promptDays} days` : "metadata only";
180
+ console.log(`${agent.name.padEnd(28)} ${plural(agent.cases, "case").padStart(9)} ${capture}`);
181
+ }
182
+ console.log("\nPull one: codiedev traces cases <agent>");
183
+ }
184
+ async function runCases(args, config) {
185
+ const { agent, suite, model, caseIds, out, json } = parseCasesArgs(args);
186
+ if (!agent) {
187
+ console.error("Usage: codiedev traces cases <agent> [--dataset name] [--model id] [--out dir] [--json]");
188
+ process.exit(1);
189
+ }
190
+ const start = Date.now();
191
+ let res;
192
+ try {
193
+ res = await (0, shared_1.apiRequest)("GET", "/api/cli/traces/cases", { config, query: { agent, ...(suite ? { suite } : {}), ...(caseIds ? { case: caseIds } : {}) } });
194
+ }
195
+ catch (err) {
196
+ const e = err;
197
+ (0, shared_1.recordAgentEvent)(config, { tool: "codiedev_traces_cases", ok: false, latencyMs: Date.now() - start, error: e.message });
198
+ if (e.status === 404)
199
+ console.error(`No agent called "${agent}". See your agents with: codiedev traces agents`);
200
+ else if (e.status === 403)
201
+ console.error(`Only ${agent}'s maintainers can pull its test cases.`);
202
+ else
203
+ console.error(`Couldn't pull cases: ${e.message}`);
204
+ process.exit(1);
205
+ }
206
+ (0, shared_1.recordAgentEvent)(config, { tool: "codiedev_traces_cases", ok: true, latencyMs: Date.now() - start });
207
+ if (json) {
208
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
209
+ return;
210
+ }
211
+ if (!res.cases.length) {
212
+ console.log(suite
213
+ ? `${res.agent.name} has no test cases in "${suite}". See your datasets in Codiedev → Traces → Test cases.`
214
+ : `${res.agent.name} has no saved test cases. Open a run in Codiedev → Traces and click "Save as test case".`);
215
+ return;
216
+ }
217
+ const dir = path.resolve(process.cwd(), out ?? path.join(".codiedev", "traces", slugify(res.agent.name), ...(res.suite ? [slugify(res.suite)] : []), ...(caseIds && res.cases.length === 1 ? [slugify(res.cases[0].name)] : []), ...(model ? [slugify(model)] : [])));
218
+ fs.mkdirSync(dir, { recursive: true });
219
+ const casesFile = path.join(dir, "cases.jsonl");
220
+ fs.writeFileSync(casesFile, res.cases.map((c) => JSON.stringify(c)).join("\n") + "\n", "utf8");
221
+ const resultsFile = path.join(dir, "results.json");
222
+ fs.writeFileSync(resultsFile, JSON.stringify(resultsTemplate(res, model), null, 2) + "\n", "utf8");
223
+ const briefFile = path.join(dir, "RUN_CASES.md");
224
+ fs.writeFileSync(briefFile, casesBrief(res, casesFile, { model, resultsFile }), "utf8");
225
+ const fix = res.cases.filter((c) => c.kind === "fix").length;
226
+ const rel = (file) => path.relative(process.cwd(), file) || file;
227
+ console.log(`✓ ${plural(res.cases.length, "case")} for ${res.agent.name}${res.suite ? ` · ${res.suite}` : ""}${model ? ` on ${model}` : ""} (${fix} to fix, ${res.cases.length - fix} to keep passing)`);
228
+ console.log(` ${rel(casesFile)}`);
229
+ console.log(` ${rel(briefFile)}`);
230
+ console.log(` ${rel(resultsFile)} (filled in by your coding agent, then: codiedev traces report ${rel(resultsFile)})`);
231
+ console.log(`\nNext: ask your coding agent to "run the test cases in ${rel(briefFile)}"`);
232
+ }
233
+ async function runReport(args, config) {
234
+ let file;
235
+ let harness;
236
+ for (let i = 0; i < args.length; i++) {
237
+ const f = flag(args, i, "--harness");
238
+ if (f.skip >= 0) {
239
+ harness = f.value;
240
+ i += f.skip;
241
+ continue;
242
+ }
243
+ if (!args[i].startsWith("-") && !file)
244
+ file = args[i];
245
+ }
246
+ if (!file) {
247
+ console.error("Usage: codiedev traces report <results.json> [--harness name]");
248
+ process.exit(1);
249
+ }
250
+ let body;
251
+ try {
252
+ body = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), file), "utf8"));
253
+ }
254
+ catch (err) {
255
+ console.error(`Couldn't read ${file}: ${err.message}`);
256
+ process.exit(1);
257
+ }
258
+ const missing = body.results.filter((r) => typeof r.passes !== "number");
259
+ if (missing.length) {
260
+ console.error(`${plural(missing.length, "case")} in ${file} ${missing.length === 1 ? "has" : "have"} no "passes" yet: ${missing.slice(0, 5).map((r) => r.name).join(", ")}. Run them first.`);
261
+ process.exit(1);
262
+ }
263
+ const start = Date.now();
264
+ try {
265
+ const res = await (0, shared_1.apiRequest)("POST", "/api/cli/traces/results", {
266
+ config,
267
+ body: { ...body, harness: harness ?? body.harness ?? undefined },
268
+ });
269
+ (0, shared_1.recordAgentEvent)(config, { tool: "codiedev_traces_report", ok: true, latencyMs: Date.now() - start });
270
+ const passed = body.results.filter((r) => r.passes === r.tries).length;
271
+ console.log(`✓ Reported ${body.agent}${body.suite ? ` · ${body.suite}` : ""} on ${body.model}: ${Math.round(res.passRate * 100)}% of tries passed, ${passed}/${body.results.length} cases passed every try.`);
272
+ console.log(" See it next to other models in Codiedev → Traces → Test cases.");
273
+ }
274
+ catch (err) {
275
+ const e = err;
276
+ (0, shared_1.recordAgentEvent)(config, { tool: "codiedev_traces_report", ok: false, latencyMs: Date.now() - start, error: e.message });
277
+ console.error(e.status === 403 ? `Only ${body.agent}'s maintainers can report its results.` : `Couldn't report: ${e.message}`);
278
+ process.exit(1);
279
+ }
280
+ }
281
+ async function runTraces(args, configOverride) {
282
+ const [sub, ...rest] = args;
283
+ if (sub === "agents")
284
+ return runAgents(configOverride ?? (0, shared_1.requireConfig)());
285
+ if (sub === "cases")
286
+ return runCases(rest, configOverride ?? (0, shared_1.requireConfig)());
287
+ if (sub === "report")
288
+ return runReport(rest, configOverride ?? (0, shared_1.requireConfig)());
289
+ console.log(exports.TRACES_HELP);
290
+ if (sub && sub !== "help")
291
+ process.exit(1);
292
+ }
@@ -16,6 +16,7 @@ export interface PiRuntimeDeps {
16
16
  rename?: (from: string, to: string) => void;
17
17
  remove?: (target: string) => void;
18
18
  log?: (message: string) => void;
19
+ sleep?: (ms: number) => void;
19
20
  }
20
21
  /**
21
22
  * The pi CLI entry to run, installing the pinned version first if it is
package/dist/piRuntime.js CHANGED
@@ -70,6 +70,10 @@ function installedPiEntry(installDir, readFile = (f) => fs.readFileSync(f, "utf8
70
70
  return null;
71
71
  }
72
72
  }
73
+ // One retry rides out a dropped connection or registry blip; a second
74
+ // failure is a real problem (offline, blocked registry) and is reported.
75
+ const INSTALL_ATTEMPTS = 2;
76
+ const INSTALL_RETRY_DELAY_MS = 5_000;
73
77
  /**
74
78
  * The pi CLI entry to run, installing the pinned version first if it is
75
79
  * missing or a different version. Staged then renamed, so an interrupted
@@ -85,10 +89,10 @@ function ensurePiRuntime(deps) {
85
89
  const remove = deps.remove ?? ((target) => fs.rmSync(target, { recursive: true, force: true }));
86
90
  const rename = deps.rename ?? fs.renameSync;
87
91
  const log = deps.log ?? ((message) => console.error(message));
88
- const staging = (deps.makeStagingDir ?? (() => {
92
+ const makeStagingDir = deps.makeStagingDir ?? (() => {
89
93
  fs.mkdirSync(deps.stateDir, { recursive: true });
90
94
  return fs.mkdtempSync(path.join(deps.stateDir, ".runtime-pi-"));
91
- }))();
95
+ });
92
96
  const run = deps.run ?? ((args) => {
93
97
  const result = (0, child_process_1.spawnSync)(process.platform === "win32" ? "npm.cmd" : "npm", args, {
94
98
  encoding: "utf8", stdio: ["ignore", "ignore", "pipe"], shell: process.platform === "win32",
@@ -97,26 +101,35 @@ function ensurePiRuntime(deps) {
97
101
  throw result.error;
98
102
  return { status: result.status, stderr: result.stderr ?? "" };
99
103
  });
104
+ const sleep = deps.sleep ?? ((ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms));
100
105
  log(`Installing pi (${exports.PI_RUNTIME_SPEC}). This runs once, and only for \`codie --pi\`.`);
101
- try {
102
- const result = run((0, codieRuntimeInstall_1.buildRuntimeInstallArgs)(staging, exports.PI_RUNTIME_SPEC));
103
- if (result.status !== 0)
104
- throw new Error(result.stderr?.trim() || `npm exited with ${result.status}`);
105
- // Trust the artifact, not the exit code.
106
- const staged = installedPiEntry(staging, readFile);
107
- if (!staged || !exists(staged)) {
108
- throw new Error(`the install did not produce pi. Check that ${exports.PI_RUNTIME_SPEC} is available from your npm registry.`);
106
+ for (let attempt = 1;; attempt++) {
107
+ const staging = makeStagingDir();
108
+ try {
109
+ const result = run((0, codieRuntimeInstall_1.buildRuntimeInstallArgs)(staging, exports.PI_RUNTIME_SPEC));
110
+ if (result.status !== 0)
111
+ throw new Error(result.stderr?.trim() || `npm exited with ${result.status}`);
112
+ // Trust the artifact, not the exit code.
113
+ const staged = installedPiEntry(staging, readFile);
114
+ if (!staged || !exists(staged)) {
115
+ throw new Error(`the install did not produce pi. Check that ${exports.PI_RUNTIME_SPEC} is available from your npm registry.`);
116
+ }
117
+ remove(installDir);
118
+ rename(staging, installDir);
119
+ break;
109
120
  }
110
- remove(installDir);
111
- rename(staging, installDir);
112
- }
113
- catch (error) {
114
- throw new Error(`Couldn't install pi: ${error instanceof Error ? error.message : String(error)}`);
115
- }
116
- finally {
117
- // No-op after a successful promote; otherwise it keeps a ~200MB staging
118
- // dir from being orphaned.
119
- remove(staging);
121
+ catch (error) {
122
+ const message = error instanceof Error ? error.message : String(error);
123
+ if (attempt >= INSTALL_ATTEMPTS)
124
+ throw new Error(`Couldn't install pi: ${message}`);
125
+ log(`pi install failed (${message.split("\n")[0]}); retrying.`);
126
+ }
127
+ finally {
128
+ // No-op after a successful promote; otherwise it keeps a ~200MB staging
129
+ // dir from being orphaned.
130
+ remove(staging);
131
+ }
132
+ sleep(INSTALL_RETRY_DELAY_MS);
120
133
  }
121
134
  const entry = installedPiEntry(installDir, readFile);
122
135
  if (!entry)
package/dist/utils.js CHANGED
@@ -1681,6 +1681,8 @@ thought, use the \`codiedev\` CLI via Bash:**
1681
1681
  | "find something about X" / "has anyone solved Y?" | \`codiedev search "<query>" [--limit N]\` |
1682
1682
  | "why did we…?" / "what does the team know about X?" | \`codiedev ask "<question>"\` (synthesized answer + citations) |
1683
1683
  | "show my library" / "what artifacts exist?" | \`codiedev library [--scope mine\\|shared\\|all] [--kind doc\\|skill]\` |
1684
+ | "run my agent's test cases" / "check the dataset on model Y" | \`codiedev traces cases <agent> [--dataset <name>] [--model <id>]\` (writes cases + RUN_CASES.md + results.json; follow it, then \`codiedev traces report <results.json>\`) |
1685
+ | "what was the last eval on X?" / "file these eval results" | \`codiedev evals last <subject>\` before starting a related eval; \`codiedev evals report <run.json>\` after each variant runs (scores only — \`codiedev evals help\` for the shape) |
1684
1686
 
1685
1687
  **Just push the file — we'll figure out where it goes.** Every artifact
1686
1688
  is classified into one of two kinds at upload time:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codiedev",
3
- "version": "0.9.72",
3
+ "version": "0.9.76",
4
4
  "description": "Connect Claude Code, Codex, Cursor, OpenCode, or VS Code Copilot to Codiedev for team session capture and artifact collaboration — and run the governed Codie harness",
5
5
  "bin": {
6
6
  "codiedev": "dist/cli.js",