codiedev 0.9.77 → 0.9.78

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.
@@ -27,6 +27,9 @@ interface CasesResponse {
27
27
  ok: boolean;
28
28
  /** The suite pulled, when --suite was given. */
29
29
  suite: string | null;
30
+ /** The saved dataset version pulled, when --version was given; cases deleted since are counted in `missing`. */
31
+ version?: number | null;
32
+ missing?: number;
30
33
  /** How the suite is judged, when it has settings. */
31
34
  judge: {
32
35
  model: string | null;
@@ -44,6 +47,7 @@ interface CasesResponse {
44
47
  export interface ResultsFile {
45
48
  agent: string;
46
49
  suite: string | null;
50
+ version?: number | null;
47
51
  model: string;
48
52
  judgeModel: string | null;
49
53
  harness: string | null;
@@ -55,10 +55,10 @@ exports.TRACES_HELP = `
55
55
  codiedev traces — test cases saved from Codiedev → Traces
56
56
 
57
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]
58
+ codiedev traces cases <agent> [--dataset name [--version n]] [--case id[,id]] [--model id] [--out dir] [--json]
59
59
  Write an agent's cases (one dataset, or just
60
60
  the cases named) into this repo, default
61
- .codiedev/traces/<agent>[/<dataset>]/, then
61
+ .codiedev/traces/<agent>[/<dataset>[/v<n>]]/, then
62
62
  ask your coding agent to run them.
63
63
  --model runs them on another model, e.g.
64
64
  the replacement for a retiring one
@@ -78,7 +78,7 @@ const FLAG_LABEL = {
78
78
  };
79
79
  const TRIES = 3;
80
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";
81
+ const recordedModel = (res) => res.cases.flatMap((c) => c.steps.map((s) => s.model)).filter(Boolean)[0] ?? "the agent's current model";
82
82
  function slugify(name) {
83
83
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
84
84
  }
@@ -90,7 +90,7 @@ function casesBrief(res, casesFile, opts = {}) {
90
90
  const target = opts.model;
91
91
  const results = opts.resultsFile ? path.basename(opts.resultsFile) : "results.json";
92
92
  const lines = [
93
- `# Test cases for ${res.agent.name}${res.suite ? ` · ${res.suite}` : ""}${target ? ` on ${target}` : ""}`,
93
+ `# Test cases for ${res.agent.name}${res.suite ? ` · ${res.suite}` : ""}${res.version ? ` v${res.version}` : ""}${target ? ` on ${target}` : ""}`,
94
94
  "",
95
95
  res.agent.description ? `${res.agent.description}\n` : "",
96
96
  `Saved from Codiedev → Traces. ${plural(res.cases.length, "case")} in \`${path.basename(casesFile)}\` (one JSON object per line):`,
@@ -101,7 +101,7 @@ function casesBrief(res, casesFile, opts = {}) {
101
101
  if (target) {
102
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
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.");
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.", " - Cases whose `runId` starts with `manual:` or `import:` were written by hand, not recorded: `steps[0].request` is the input and there's no recorded reply, so judge them on `expected`.");
105
105
  if (res.judge?.model || res.judge?.rubric) {
106
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
107
  }
@@ -121,6 +121,7 @@ function resultsTemplate(res, model) {
121
121
  return {
122
122
  agent: res.agent.name,
123
123
  suite: res.suite,
124
+ ...(res.version ? { version: res.version } : {}),
124
125
  model: model ?? recordedModel(res),
125
126
  judgeModel: res.judge?.model ?? null,
126
127
  harness: null,
@@ -140,7 +141,7 @@ function parseCasesArgs(args) {
140
141
  for (let i = 0; i < args.length; i++) {
141
142
  const a = args[i];
142
143
  let matched = false;
143
- for (const [name, key] of [["--out", "out"], ["-o", "out"], ["--dataset", "suite"], ["--suite", "suite"], ["--model", "model"], ["--case", "caseIds"]]) {
144
+ for (const [name, key] of [["--out", "out"], ["-o", "out"], ["--dataset", "suite"], ["--suite", "suite"], ["--model", "model"], ["--case", "caseIds"], ["--version", "version"]]) {
144
145
  const f = flag(args, i, name);
145
146
  if (f.skip >= 0) {
146
147
  parsed[key] = f.value;
@@ -182,20 +183,26 @@ async function runAgents(config) {
182
183
  console.log("\nPull one: codiedev traces cases <agent>");
183
184
  }
184
185
  async function runCases(args, config) {
185
- const { agent, suite, model, caseIds, out, json } = parseCasesArgs(args);
186
+ const { agent, suite, model, caseIds, version, out, json } = parseCasesArgs(args);
186
187
  if (!agent) {
187
- console.error("Usage: codiedev traces cases <agent> [--dataset name] [--model id] [--out dir] [--json]");
188
+ console.error("Usage: codiedev traces cases <agent> [--dataset name [--version n]] [--model id] [--out dir] [--json]");
189
+ process.exit(1);
190
+ }
191
+ if (version !== undefined && (!suite || !/^[1-9]\d*$/.test(version))) {
192
+ console.error("--version takes a whole number and needs --dataset: codiedev traces cases <agent> --dataset <name> --version <n>");
188
193
  process.exit(1);
189
194
  }
190
195
  const start = Date.now();
191
196
  let res;
192
197
  try {
193
- res = await (0, shared_1.apiRequest)("GET", "/api/cli/traces/cases", { config, query: { agent, ...(suite ? { suite } : {}), ...(caseIds ? { case: caseIds } : {}) } });
198
+ res = await (0, shared_1.apiRequest)("GET", "/api/cli/traces/cases", { config, query: { agent, ...(suite ? { suite } : {}), ...(version ? { version } : {}), ...(caseIds ? { case: caseIds } : {}) } });
194
199
  }
195
200
  catch (err) {
196
201
  const e = err;
197
202
  (0, shared_1.recordAgentEvent)(config, { tool: "codiedev_traces_cases", ok: false, latencyMs: Date.now() - start, error: e.message });
198
- if (e.status === 404)
203
+ if (e.status === 404 && version && /No version/.test(e.message))
204
+ console.error(`No version ${version} of "${suite}". See its versions in Codiedev → Traces → Test cases.`);
205
+ else if (e.status === 404)
199
206
  console.error(`No agent called "${agent}". See your agents with: codiedev traces agents`);
200
207
  else if (e.status === 403)
201
208
  console.error(`Only ${agent}'s maintainers can pull its test cases.`);
@@ -214,7 +221,7 @@ async function runCases(args, config) {
214
221
  : `${res.agent.name} has no saved test cases. Open a run in Codiedev → Traces and click "Save as test case".`);
215
222
  return;
216
223
  }
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)] : [])));
224
+ const dir = path.resolve(process.cwd(), out ?? path.join(".codiedev", "traces", slugify(res.agent.name), ...(res.suite ? [slugify(res.suite)] : []), ...(res.version ? [`v${res.version}`] : []), ...(caseIds && res.cases.length === 1 ? [slugify(res.cases[0].name)] : []), ...(model ? [slugify(model)] : [])));
218
225
  fs.mkdirSync(dir, { recursive: true });
219
226
  const casesFile = path.join(dir, "cases.jsonl");
220
227
  fs.writeFileSync(casesFile, res.cases.map((c) => JSON.stringify(c)).join("\n") + "\n", "utf8");
@@ -224,7 +231,9 @@ async function runCases(args, config) {
224
231
  fs.writeFileSync(briefFile, casesBrief(res, casesFile, { model, resultsFile }), "utf8");
225
232
  const fix = res.cases.filter((c) => c.kind === "fix").length;
226
233
  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)`);
234
+ console.log(`✓ ${plural(res.cases.length, "case")} for ${res.agent.name}${res.suite ? ` · ${res.suite}` : ""}${res.version ? ` v${res.version}` : ""}${model ? ` on ${model}` : ""} (${fix} to fix, ${res.cases.length - fix} to keep passing)`);
235
+ if (res.missing)
236
+ console.log(` ${plural(res.missing, "case")} in v${res.version} were deleted since and can't be pulled.`);
228
237
  console.log(` ${rel(casesFile)}`);
229
238
  console.log(` ${rel(briefFile)}`);
230
239
  console.log(` ${rel(resultsFile)} (filled in by your coding agent, then: codiedev traces report ${rel(resultsFile)})`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codiedev",
3
- "version": "0.9.77",
3
+ "version": "0.9.78",
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",