@moda-ai/cli 1.55.24 → 1.55.25

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.
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  buildDoctorReport
3
- } from "./cli-vg88mg20.js";
3
+ } from "./cli-mebf5vtr.js";
4
4
  import"./cli-0wr3483h.js";
5
- import"./cli-y2ty9vtv.js";
5
+ import"./cli-m805msp6.js";
6
6
  import"./cli-mm48br00.js";
7
7
  import {
8
8
  HARNESS_REPORT_APPROVAL_PATH,
@@ -70,6 +70,9 @@ function renderFixesBlock() {
70
70
  "- Propose the change in the code, prompt, tool definition, or skill that",
71
71
  " causes the issue, and cite the Moda problem or trace ids it is based on",
72
72
  " in the PR description.",
73
+ "- Test a prompt change on the Problem's failing traces before it ships (a paid",
74
+ " replay; prompt changes only today): `moda prompts ab --problem=<problem_id>",
75
+ " --baseline=<prompt_key> --candidate=<edited prompt file>`.",
73
76
  "- Prompts live in `.moda/prompts.yml` (version 2: `prompt_paths` globs plus",
74
77
  " `sources:` mappings for prompts defined in code); after `moda prompts sync`,",
75
78
  " pass `prompt: lock.prompts['<key>']` to createThinkTelemetry/createAI7Telemetry.",
@@ -17,6 +17,7 @@ import {
17
17
  createCommandContext
18
18
  } from "./cli-qvg26twp.js";
19
19
  import {
20
+ UUID_RE,
20
21
  assertFlags,
21
22
  assertPositionals
22
23
  } from "./cli-dn91fb56.js";
@@ -5561,6 +5562,9 @@ var DEFAULT_SEEDS_PER_CASE = 3;
5561
5562
  var DEFAULT_AUTO_GENERATE_CASES = 5;
5562
5563
  var MAX_AUTO_GENERATE_CASES = 100;
5563
5564
  var MAX_REPLAY_TRACES = 100;
5565
+ var DEFAULT_PROBLEM_MAX_CASES = 10;
5566
+ var PROBLEM_PAGE_LIMIT = 50;
5567
+ var PROBLEM_MAX_PAGES = 4;
5564
5568
  var PLAYOUT_CONFIRM_THRESHOLD = 200;
5565
5569
  var REPLAY_MODEL_ALLOWLIST = [
5566
5570
  "openai/gpt-5.6-luna",
@@ -5589,7 +5593,7 @@ async function runPromptAb(flags, profileOptions, context) {
5589
5593
  const baselineSource = flags.baseline || flags["baseline-file"] || flags["baseline-key"];
5590
5594
  const candidateSource = flags.candidate || flags["candidate-file"] || flags["candidate-key"];
5591
5595
  if (!baselineSource || !candidateSource) {
5592
- throw new Error("Usage: moda prompts ab --baseline=<file|key> --candidate=<file|key> [--auto-generate [--cases=1-100]|--set-id=ID|--traces=id1,id2[@msg_index] (max 100)] [--seeds=1-10] [--model=<slug>] [--yes] (legacy alias: --conversations=)");
5596
+ throw new Error("Usage: moda prompts ab --baseline=<file|key> --candidate=<file|key> [--auto-generate [--cases=1-100]|--set-id=ID|--traces=id1,id2[@msg_index] (max 100)|--problem=<problem_id> [--max-cases=1-100]] [--seeds=1-10] [--model=<slug>] [--yes] (legacy alias: --conversations=)");
5593
5597
  }
5594
5598
  assertArmSourceShape(baselineSource, "baseline");
5595
5599
  assertArmSourceShape(candidateSource, "candidate");
@@ -5599,6 +5603,10 @@ async function runPromptAb(flags, profileOptions, context) {
5599
5603
  const pollIntervalMs = parsePositiveInt(flags["poll-interval"], DEFAULT_POLL_INTERVAL_MS, 120000, "--poll-interval");
5600
5604
  const assistantModel = assertAllowedModel(flags.model ?? flags["assistant-model"], flags, "--model");
5601
5605
  const plan = await planReplaySet(flags, tenantId, profileOptions);
5606
+ const problemCases = plan.kind === "traces" ? plan.problem : undefined;
5607
+ if (problemCases && context.outputMode === "human") {
5608
+ process.stderr.write(describeProblemCases(problemCases));
5609
+ }
5602
5610
  const plannedPlayouts = plan.cases === null ? null : plan.cases * seedsPerCase * 2;
5603
5611
  assertPlayoutBudget(plannedPlayouts, { seedsPerCase, cases: plan.cases }, context.yes, rerunWithYes("prompts ab", [], flags));
5604
5612
  if (context.outputMode === "human") {
@@ -5644,6 +5652,7 @@ async function runPromptAb(flags, profileOptions, context) {
5644
5652
  if (noWait) {
5645
5653
  context.output.writeData({
5646
5654
  replaySetId,
5655
+ ...problemCases ? { problemCases } : {},
5647
5656
  ...caseSources ? { caseSources } : {},
5648
5657
  ...warnings.length ? { warnings } : {},
5649
5658
  runId: enqueue.runId,
@@ -5673,6 +5682,7 @@ async function runPromptAb(flags, profileOptions, context) {
5673
5682
  }
5674
5683
  const result = {
5675
5684
  replaySetId,
5685
+ ...problemCases ? { problemCases } : {},
5676
5686
  ...caseSources ? { caseSources } : {},
5677
5687
  ...warnings.length ? { warnings } : {},
5678
5688
  plannedPlayouts,
@@ -5801,6 +5811,9 @@ function resolvePromptArmSpec(source, label, opts = {}) {
5801
5811
  };
5802
5812
  }
5803
5813
  async function planReplaySet(flags, tenantId, profileOptions) {
5814
+ const problemPlan = await planProblemCases(flags);
5815
+ if (problemPlan)
5816
+ return problemPlan;
5804
5817
  const existingSetId = (flags["set-id"] || flags["replay-set-id"] || "").trim();
5805
5818
  if (existingSetId) {
5806
5819
  const snapshot = await fetchReplaySetCases(tenantId, existingSetId, profileOptions);
@@ -5821,6 +5834,151 @@ async function planReplaySet(flags, tenantId, profileOptions) {
5821
5834
  cases: parseCappedInt(flags.cases ?? flags["case-count"], DEFAULT_AUTO_GENERATE_CASES, MAX_AUTO_GENERATE_CASES, "--cases")
5822
5835
  };
5823
5836
  }
5837
+ var PROBLEM_CASE_CONFLICTS = [
5838
+ ["traces", "--traces"],
5839
+ ["conversations", "--conversations"],
5840
+ ["conversation-ids", "--conversation-ids"],
5841
+ ["set-id", "--set-id"],
5842
+ ["replay-set-id", "--replay-set-id"],
5843
+ ["cases", "--cases"],
5844
+ ["case-count", "--case-count"]
5845
+ ];
5846
+ async function planProblemCases(flags) {
5847
+ const raw = flags.problem;
5848
+ if (raw === undefined) {
5849
+ if (flags["max-cases"] !== undefined) {
5850
+ throw new CliInputError("--max-cases applies only with --problem, so nothing was sent.", "Use --cases=N with --auto-generate, or pass --problem=<problem_id>.");
5851
+ }
5852
+ return null;
5853
+ }
5854
+ const problemId = raw.trim();
5855
+ const conflicts = PROBLEM_CASE_CONFLICTS.filter(([key]) => flags[key] !== undefined).map(([, name]) => name);
5856
+ if (flags["auto-generate"] === "true")
5857
+ conflicts.push("--auto-generate");
5858
+ if (conflicts.length) {
5859
+ throw new CliInputError(`--problem cannot be combined with ${conflicts.join(", ")}: each picks the replay cases, so nothing was sent.`, "Pass one of --problem=<problem_id> [--max-cases=N], --traces=<ids>, --set-id=<id>, or --auto-generate [--cases=N].");
5860
+ }
5861
+ if (!UUID_RE.test(problemId)) {
5862
+ throw new CliInputError(`--problem needs a canonical problem UUID; got "${problemId}", so nothing was sent.`, "Run `moda problems` to find the problem_id.");
5863
+ }
5864
+ const maxCases = parsePositiveInt(flags["max-cases"], DEFAULT_PROBLEM_MAX_CASES, MAX_REPLAY_TRACES, "--max-cases");
5865
+ const selection = await fetchProblemCases(problemId, maxCases);
5866
+ if (!selection.traces.length) {
5867
+ throw new CliInputError(`Problem ${problemId} has no affected traces to replay, so nothing was sent.`, `Check the id with \`moda problems get ${problemId}\`, or pass --traces=<ids>.`);
5868
+ }
5869
+ return {
5870
+ kind: "traces",
5871
+ traces: selection.traces.map((c) => c.msg_index === null ? c.conversation_id : `${c.conversation_id}@${c.msg_index}`),
5872
+ cases: selection.traces.length,
5873
+ problem: selection
5874
+ };
5875
+ }
5876
+ async function readProblemPages(problemId, view, enough, drain = false) {
5877
+ const rows = [];
5878
+ let enoughAt = null;
5879
+ let cursor = null;
5880
+ let resolvedId = null;
5881
+ let hasMore = false;
5882
+ for (let page = 0;page < PROBLEM_MAX_PAGES; page++) {
5883
+ const query = new URLSearchParams({ limit: String(PROBLEM_PAGE_LIMIT) });
5884
+ if (cursor)
5885
+ query.set("cursor", cursor);
5886
+ const data = await callDataAPI(`/problems/${encodeURIComponent(problemId)}/${view}?${query.toString()}`);
5887
+ if (typeof data?.problem_id === "string" && data.problem_id)
5888
+ resolvedId = data.problem_id;
5889
+ const pageRows = data?.[view];
5890
+ if (Array.isArray(pageRows))
5891
+ rows.push(...pageRows);
5892
+ const next = data?.pagination?.next_cursor;
5893
+ hasMore = data?.pagination?.has_more === true && typeof next === "string" && next !== "";
5894
+ if (enoughAt === null && enough(rows))
5895
+ enoughAt = rows.length;
5896
+ if (!hasMore || enoughAt !== null && !drain)
5897
+ break;
5898
+ cursor = next;
5899
+ }
5900
+ return { rows, enoughAt, resolvedId, hasMore };
5901
+ }
5902
+ async function fetchProblemCases(problemId, maxCases) {
5903
+ const anchored = (evidenceRows, traceRows2) => selectProblemCases(evidenceRows, traceRows2, MAX_REPLAY_TRACES).traces.filter((c) => c.msg_index !== null).length;
5904
+ const evidence = await readProblemPages(problemId, "evidence", (rows) => anchored(rows, []) >= maxCases, true);
5905
+ const choiceRows = evidence.enoughAt === null ? evidence.rows : evidence.rows.slice(0, evidence.enoughAt);
5906
+ let traceRows = [];
5907
+ let tracesHaveMore = false;
5908
+ let resolvedId = evidence.resolvedId;
5909
+ if (anchored(choiceRows, []) < maxCases) {
5910
+ const traces2 = await readProblemPages(problemId, "conversations", (rows) => anchored(evidence.rows, rows) >= maxCases);
5911
+ traceRows = traces2.rows;
5912
+ tracesHaveMore = traces2.hasMore;
5913
+ resolvedId ??= traces2.resolvedId;
5914
+ }
5915
+ const selected = selectProblemCases(choiceRows, traceRows, maxCases);
5916
+ const all = selectProblemCases(evidence.rows, traceRows, Number.MAX_SAFE_INTEGER);
5917
+ const earliestByTrace = new Map(all.traces.map((c) => [c.conversation_id, c]));
5918
+ const traces = selected.traces.map((c) => {
5919
+ const first = earliestByTrace.get(c.conversation_id);
5920
+ return first && first.msg_index !== null && (c.msg_index === null || first.msg_index < c.msg_index) ? first : c;
5921
+ });
5922
+ const found = all.found;
5923
+ const resolved = resolvedId ?? problemId;
5924
+ return {
5925
+ problemId: resolved,
5926
+ ...resolved !== problemId ? { requestedProblemId: problemId } : {},
5927
+ maxCases,
5928
+ traces,
5929
+ found,
5930
+ moreAvailable: found > traces.length || evidence.hasMore || tracesHaveMore
5931
+ };
5932
+ }
5933
+ function anchorIndex(value) {
5934
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
5935
+ }
5936
+ function selectProblemCases(evidenceRows, traceRows, maxCases) {
5937
+ const byTrace = new Map;
5938
+ const consider = (conversationId, msgIndex, source, family) => {
5939
+ if (typeof conversationId !== "string" || !conversationId.trim())
5940
+ return;
5941
+ const id = conversationId.trim();
5942
+ const existing = byTrace.get(id);
5943
+ const signalFamily = typeof family === "string" && family ? family : undefined;
5944
+ if (existing && (msgIndex === null || existing.msg_index !== null && existing.msg_index <= msgIndex))
5945
+ return;
5946
+ byTrace.set(id, {
5947
+ conversation_id: id,
5948
+ msg_index: msgIndex,
5949
+ source,
5950
+ ...signalFamily ? { signal_family: signalFamily } : {}
5951
+ });
5952
+ };
5953
+ for (const row of evidenceRows) {
5954
+ const r = row ?? {};
5955
+ consider(r.conversation_id, anchorIndex(r.anchor_msg_index), "evidence", r.signal_family);
5956
+ }
5957
+ for (const row of traceRows) {
5958
+ const r = row ?? {};
5959
+ const anchor = r.latest_anchor ?? {};
5960
+ consider(r.conversation_id, anchorIndex(anchor.anchor_msg_index), "trace", undefined);
5961
+ }
5962
+ const all = [...byTrace.values()];
5963
+ const ordered = [...all.filter((c) => c.msg_index !== null), ...all.filter((c) => c.msg_index === null)];
5964
+ return { traces: ordered.slice(0, maxCases), found: all.length };
5965
+ }
5966
+ function describeProblemCases(selection) {
5967
+ const requested = selection.requestedProblemId ? ` (requested ${selection.requestedProblemId})` : "";
5968
+ const lines = [
5969
+ `Replaying ${selection.traces.length} trace(s) from Problem ${selection.problemId}${requested}, each from its evidence anchor:`
5970
+ ];
5971
+ for (const c of selection.traces) {
5972
+ const where = c.msg_index === null ? " (no anchor: replays from the first message)" : ` @ msg ${c.msg_index}`;
5973
+ lines.push(` ${c.conversation_id}${where}${c.signal_family ? ` [${c.signal_family}]` : ""}`);
5974
+ }
5975
+ if (selection.moreAvailable) {
5976
+ lines.push(`Capped at --max-cases=${selection.maxCases}; the Problem has more affected traces.`);
5977
+ }
5978
+ return `${lines.join(`
5979
+ `)}
5980
+ `;
5981
+ }
5824
5982
  async function fetchReplaySetCases(tenantId, setId, profileOptions) {
5825
5983
  try {
5826
5984
  const set = await callControlAPI(`/tenants/${encodeURIComponent(tenantId)}/replay-sets/${encodeURIComponent(setId)}`, { method: "GET" }, profileOptions);
@@ -5876,7 +6034,8 @@ async function ensureReplaySet(plan, flags, tenantId, profileOptions) {
5876
6034
  if (plan.kind === "existing")
5877
6035
  return { setId: plan.setId, warnings: [] };
5878
6036
  if (plan.kind === "traces") {
5879
- return createSetFromTraces(plan.traces, flags, tenantId, profileOptions);
6037
+ const defaultName = plan.problem ? `Prompt A/B problem ${plan.problem.problemId.slice(0, 8)} (${plan.traces.length} trace(s))` : undefined;
6038
+ return createSetFromTraces(plan.traces, flags, tenantId, profileOptions, defaultName);
5880
6039
  }
5881
6040
  const caseCount = plan.cases;
5882
6041
  const lookbackDays = parsePositiveInt(flags["lookback-days"], 30, 365, "--lookback-days");
@@ -5896,7 +6055,7 @@ function parseTraceRef(ref) {
5896
6055
  return { conversationId: ref.trim() };
5897
6056
  return { conversationId: match[1], startMsgIndex: Number(match[2]) };
5898
6057
  }
5899
- async function createSetFromTraces(conversationIds, flags, tenantId, profileOptions) {
6058
+ async function createSetFromTraces(conversationIds, flags, tenantId, profileOptions, defaultName) {
5900
6059
  const refs = [...new Set(conversationIds)].map(parseTraceRef);
5901
6060
  const traces = [...new Set(refs.map((ref) => ref.conversationId))];
5902
6061
  const startByConversation = new Map;
@@ -5904,12 +6063,16 @@ async function createSetFromTraces(conversationIds, flags, tenantId, profileOpti
5904
6063
  if (ref.startMsgIndex !== undefined)
5905
6064
  startByConversation.set(ref.conversationId, ref.startMsgIndex);
5906
6065
  }
5907
- const name = (flags.name ?? flags["set-name"] ?? `Prompt A/B ${traces.length} trace(s)`).trim();
6066
+ const name = (flags.name ?? flags["set-name"] ?? defaultName ?? `Prompt A/B ${traces.length} trace(s)`).trim();
5908
6067
  const base = `/tenants/${encodeURIComponent(tenantId)}/replay-sets`;
6068
+ const traceAnchors = traces.filter((id) => startByConversation.has(id)).map((id) => ({ conversationId: id, msgIndex: startByConversation.get(id) }));
5909
6069
  let set = null;
5910
6070
  let unavailable = null;
5911
6071
  try {
5912
- set = await callControlAPI(`${base}/auto-generate`, { method: "POST", body: JSON.stringify({ name, conversationIds: traces }) }, profileOptions, { timeoutMs: 300000, retries: 0 });
6072
+ set = await callControlAPI(`${base}/auto-generate`, {
6073
+ method: "POST",
6074
+ body: JSON.stringify({ name, conversationIds: traces, ...traceAnchors.length ? { traceAnchors } : {} })
6075
+ }, profileOptions, { timeoutMs: 300000, retries: 0 });
5913
6076
  if (!set?.id)
5914
6077
  throw new Error("Auto-generate did not return a replay set id");
5915
6078
  } catch (error) {
@@ -5933,14 +6096,15 @@ async function createSetFromTraces(conversationIds, flags, tenantId, profileOpti
5933
6096
  const update = {};
5934
6097
  if (fallback.length && index !== undefined && existing.position !== index)
5935
6098
  update.position = index;
5936
- if (startMsgIndex !== undefined)
6099
+ if (startMsgIndex !== undefined && existing.sourceStartMsgIndex !== startMsgIndex) {
5937
6100
  update.sourceStartMsgIndex = startMsgIndex;
6101
+ }
5938
6102
  if (!Object.keys(update).length)
5939
6103
  continue;
5940
6104
  await callControlAPI(`${base}/${encodeURIComponent(set.id)}/cases/${encodeURIComponent(existing.id)}`, { method: "PUT", body: JSON.stringify(update) }, profileOptions);
5941
6105
  }
5942
6106
  for (const conversationId of fallback) {
5943
- const scenario = await loadScenarioFromConversation(conversationId);
6107
+ const scenario = await loadScenarioFromConversation(conversationId, startByConversation.get(conversationId));
5944
6108
  await callControlAPI(`${base}/${encodeURIComponent(set.id)}/cases`, {
5945
6109
  method: "POST",
5946
6110
  body: JSON.stringify({
@@ -5960,19 +6124,40 @@ async function createSetFromTraces(conversationIds, flags, tenantId, profileOpti
5960
6124
  }
5961
6125
  return { setId: set.id, caseSources: { distilled, fallback }, warnings };
5962
6126
  }
5963
- async function loadScenarioFromConversation(conversationId) {
6127
+ var ANCHOR_REQUEST_MAX_READS = 20;
6128
+ var CONTEXT_WINDOW = 10;
6129
+ async function loadScenarioFromConversation(conversationId, startMsgIndex) {
6130
+ const anchored = startMsgIndex !== undefined && startMsgIndex > 0;
6131
+ const userRequest = (messages, atOrBefore) => messages.filter((message) => message.role === "user" && String(message.content ?? "").trim() && (atOrBefore === undefined || typeof message.index === "number" && message.index <= atOrBefore));
6132
+ const readContext = async (msgIndex, window2) => {
6133
+ const data = await callDataAPI(`/conversations/${encodeURIComponent(conversationId)}/context?msg_index=${msgIndex}&window=${window2}`);
6134
+ return data.context?.messages ?? data.messages ?? [];
6135
+ };
5964
6136
  try {
5965
- const data = await callDataAPI(`/conversations/${encodeURIComponent(conversationId)}/context?msg_index=0&window=5`);
5966
- const messages = data.context?.messages ?? data.messages ?? [];
5967
- const firstUser = messages.find((message) => message.role === "user" && String(message.content ?? "").trim());
5968
- const opening = String(firstUser?.content ?? "").trim();
5969
- if (opening) {
5970
- return `The user says: "${opening}"
6137
+ if (anchored) {
6138
+ let center = startMsgIndex;
6139
+ for (let read = 0;read < ANCHOR_REQUEST_MAX_READS; read++) {
6140
+ const lead = userRequest(await readContext(center, CONTEXT_WINDOW), startMsgIndex).pop();
6141
+ const request = String(lead?.content ?? "").trim();
6142
+ if (request) {
6143
+ return `Earlier in the conversation the user and the agent already talked; the replay keeps those turns as history. ` + `Now the user says: "${request}"
6144
+
6145
+ Help them with this request using the available tools.`;
6146
+ }
6147
+ if (center - CONTEXT_WINDOW <= 0)
6148
+ break;
6149
+ center = Math.max(0, center - (2 * CONTEXT_WINDOW + 1));
6150
+ }
6151
+ } else {
6152
+ const request = String(userRequest(await readContext(0, 5))[0]?.content ?? "").trim();
6153
+ if (request) {
6154
+ return `The user says: "${request}"
5971
6155
 
5972
6156
  Replay the conversation faithfully and help them accomplish their goal.`;
6157
+ }
5973
6158
  }
5974
6159
  } catch {}
5975
- return `Replay conversation ${conversationId}: help the user accomplish their original goal using the available tools.`;
6160
+ return anchored ? `Replay conversation ${conversationId} from message ${startMsgIndex}: help the user with their request at that point using the available tools.` : `Replay conversation ${conversationId}: help the user accomplish their original goal using the available tools.`;
5976
6161
  }
5977
6162
  async function fetchLatestRun(tenantId, replaySetId, runId, profileOptions) {
5978
6163
  return callControlAPI(`/tenants/${encodeURIComponent(tenantId)}/replay-sets/${encodeURIComponent(replaySetId)}/runs/latest?runId=${encodeURIComponent(runId)}`, { method: "GET" }, profileOptions, { timeoutMs: 60000, retries: 2 });
@@ -6505,7 +6690,9 @@ var PROMPT_WRITE_SUBCOMMAND_FLAGS = {
6505
6690
  "set-id",
6506
6691
  "set-name",
6507
6692
  "tenant-id",
6508
- "allow-any-model"
6693
+ "allow-any-model",
6694
+ "problem",
6695
+ "max-cases"
6509
6696
  ],
6510
6697
  propose: [
6511
6698
  "gate",
@@ -4,7 +4,7 @@ import {
4
4
  } from "./cli-0wr3483h.js";
5
5
  import {
6
6
  inspectPromptManifest
7
- } from "./cli-y2ty9vtv.js";
7
+ } from "./cli-m805msp6.js";
8
8
  import {
9
9
  readHarnessSyncState,
10
10
  renderDoctorHuman,
package/dist/cli.js CHANGED
@@ -2522,31 +2522,31 @@ function leafManifestFields(leaf, commands) {
2522
2522
  import { fileURLToPath } from "url";
2523
2523
  import { realpathSync } from "fs";
2524
2524
  async function runPromptsCommand(...args) {
2525
- return (await import("./prompts-cvzt994j.js")).runPromptsCommand(...args);
2525
+ return (await import("./prompts-3h5jcjrw.js")).runPromptsCommand(...args);
2526
2526
  }
2527
2527
  async function runSkillsCommand(...args) {
2528
- return (await import("./skills-tadh1jss.js")).runSkillsCommand(...args);
2528
+ return (await import("./skills-vq3njc8a.js")).runSkillsCommand(...args);
2529
2529
  }
2530
2530
  async function runFixCommand(...args) {
2531
- return (await import("./fix-exvxzvtz.js")).runFixCommand(...args);
2531
+ return (await import("./fix-0qdtdz5h.js")).runFixCommand(...args);
2532
2532
  }
2533
2533
  async function runFixesCommand(...args) {
2534
- return (await import("./fix-exvxzvtz.js")).runFixesCommand(...args);
2534
+ return (await import("./fix-0qdtdz5h.js")).runFixesCommand(...args);
2535
2535
  }
2536
2536
  async function runHarnessCommand(...args) {
2537
2537
  return (await import("./harness-vzy7n7ea.js")).runHarnessCommand(...args);
2538
2538
  }
2539
2539
  async function runSyncCommand(...args) {
2540
- return (await import("./sync-g8wtecz1.js")).runSyncCommand(...args);
2540
+ return (await import("./sync-hq9dpqpr.js")).runSyncCommand(...args);
2541
2541
  }
2542
2542
  async function runRegistryCommand(...args) {
2543
2543
  return (await import("./registry-s2vdsfw8.js")).runRegistryCommand(...args);
2544
2544
  }
2545
2545
  async function runDoctorCommand(...args) {
2546
- return (await import("./doctor-3r19ry92.js")).runDoctorCommand(...args);
2546
+ return (await import("./doctor-q21mr5ya.js")).runDoctorCommand(...args);
2547
2547
  }
2548
2548
  async function runStatusCommand(...args) {
2549
- return (await import("./doctor-3r19ry92.js")).runStatusCommand(...args);
2549
+ return (await import("./doctor-q21mr5ya.js")).runStatusCommand(...args);
2550
2550
  }
2551
2551
  async function runAuthCommand(...args) {
2552
2552
  return (await import("./profile-commands-6e2dgv72.js")).runAuthCommand(...args);
@@ -2558,7 +2558,7 @@ async function runProfilesCommand(...args) {
2558
2558
  return (await import("./profile-commands-6e2dgv72.js")).runProfilesCommand(...args);
2559
2559
  }
2560
2560
  async function runAgentCommand(...args) {
2561
- return (await import("./agent-b4tdzy9q.js")).runAgentCommand(...args);
2561
+ return (await import("./agent-t7dxmpdq.js")).runAgentCommand(...args);
2562
2562
  }
2563
2563
  async function runProductionCommand(...args) {
2564
2564
  return (await import("./production-intelligence-c8e0xw3n.js")).runProductionCommand(...args);
@@ -5382,7 +5382,7 @@ var commandRegistry = createCommandRegistry([
5382
5382
  resetApiRequestCountBeforeRun: true,
5383
5383
  telemetry: "result-and-error",
5384
5384
  handler: async (context) => {
5385
- const { runInit } = await import("./index-e1pf1qz2.js");
5385
+ const { runInit } = await import("./index-4b1ybz51.js");
5386
5386
  if (context.outputMode === "agent-stream") {
5387
5387
  context.output.writeEvent({
5388
5388
  event: "started",
@@ -6259,11 +6259,13 @@ var commandRegistry = createCommandRegistry([
6259
6259
  {
6260
6260
  name: "ab",
6261
6261
  description: "Run a judged prompt A/B replay experiment",
6262
- usage: "moda prompts ab --baseline=<path> --candidate=<path> [--traces=<ids>|--set-id=<id>|--auto-generate] [--yes]",
6262
+ usage: "moda prompts ab --baseline=<path> --candidate=<path> [--traces=<ids>|--problem=<problem_id> [--max-cases=<n>]|--set-id=<id>|--auto-generate] [--yes]",
6263
6263
  flags: [
6264
6264
  { name: "--baseline=<path>", description: "Prompt file to treat as the control." },
6265
6265
  { name: "--candidate=<path>", description: "Prompt file to treat as the variant." },
6266
6266
  { name: "--traces=<ids>", description: "Comma-separated trace ids (conversation_id values) to replay, at most 100. Each case uses Moda's distilled scenario and success criteria for the trace; a trace that cannot be distilled falls back to its opening user message with generic criteria, and the run warns. Legacy alias: --conversations=<ids>." },
6267
+ { name: "--problem=<problem_id>", description: "Replay the Problem's failing traces: one case per affected trace, starting at its evidence msg_index (like --traces=<id>@<n>), read from `moda problems get <id> --evidence`, then `--traces` if needed. Prints the traces it chose. Not combinable with --traces, --set-id, --auto-generate, or --cases." },
6268
+ { name: "--max-cases=<n>", description: "With --problem: replay at most this many traces (default 10, max 100)." },
6267
6269
  { name: "--cases=<n>", description: "Cases to auto-generate (default 5, max 100)." },
6268
6270
  { name: "--seeds=<n>", description: "Repeats per case per arm (default 3, max 10)." },
6269
6271
  { name: "--model=<slug>", description: "Assistant model for both arms; must be in the replay allowlist unless --allow-any-model." },
@@ -6271,7 +6273,8 @@ var commandRegistry = createCommandRegistry([
6271
6273
  { name: "--promote", description: "Make this run's replay set the tenant's primary replay set. Off by default, so an ad-hoc A/B never replaces it." }
6272
6274
  ],
6273
6275
  examples: [
6274
- "moda prompts ab --baseline=prompts/agent.prompt.md --candidate=prompts/agent-v2.prompt.md --traces=conv_1,conv_2"
6276
+ "moda prompts ab --baseline=prompts/agent.prompt.md --candidate=prompts/agent-v2.prompt.md --traces=conv_1,conv_2",
6277
+ "moda prompts ab --baseline=prompts/agent.prompt.md --candidate=prompts/agent-v2.prompt.md --problem=<problem_id>"
6275
6278
  ]
6276
6279
  },
6277
6280
  {
@@ -3,9 +3,9 @@ import {
3
3
  promptsDiscoveredCheck,
4
4
  runDoctorCommand,
5
5
  runStatusCommand
6
- } from "./cli-vg88mg20.js";
6
+ } from "./cli-mebf5vtr.js";
7
7
  import"./cli-0wr3483h.js";
8
- import"./cli-y2ty9vtv.js";
8
+ import"./cli-m805msp6.js";
9
9
  import"./cli-mm48br00.js";
10
10
  import"./cli-7xc585hc.js";
11
11
  import"./cli-97fnztpy.js";
@@ -2,7 +2,7 @@ import {
2
2
  DEFAULT_POLL_INTERVAL_MS,
3
3
  DEFAULT_WAIT_TIMEOUT_MS,
4
4
  fetchLatestRun
5
- } from "./cli-y2ty9vtv.js";
5
+ } from "./cli-m805msp6.js";
6
6
  import"./cli-mm48br00.js";
7
7
  import"./cli-7xc585hc.js";
8
8
  import"./cli-97fnztpy.js";
@@ -4,7 +4,7 @@ import {
4
4
  import"./cli-9hc5bg2r.js";
5
5
  import {
6
6
  buildDoctorReport
7
- } from "./cli-vg88mg20.js";
7
+ } from "./cli-mebf5vtr.js";
8
8
  import {
9
9
  ConnectCodeError,
10
10
  connectWithCode,
@@ -15,7 +15,7 @@ import {
15
15
  import {
16
16
  initPrompts,
17
17
  runPromptSync
18
- } from "./cli-y2ty9vtv.js";
18
+ } from "./cli-m805msp6.js";
19
19
  import"./cli-mm48br00.js";
20
20
  import {
21
21
  canonicalText,
@@ -47,7 +47,7 @@ import {
47
47
  import {
48
48
  agentsMdProblem,
49
49
  runSkillSync
50
- } from "./cli-rt26sbz7.js";
50
+ } from "./cli-m0045npe.js";
51
51
  import {
52
52
  createCommandContext
53
53
  } from "./cli-qvg26twp.js";
@@ -11,7 +11,7 @@ import {
11
11
  runPromptSync,
12
12
  runPromptsCommand,
13
13
  writePromptSyncProgress
14
- } from "./cli-y2ty9vtv.js";
14
+ } from "./cli-m805msp6.js";
15
15
  import {
16
16
  promptSourceHashPayload
17
17
  } from "./cli-mm48br00.js";
@@ -3,7 +3,7 @@ import {
3
3
  pullSkills,
4
4
  runSkillSync,
5
5
  runSkillsCommand
6
- } from "./cli-rt26sbz7.js";
6
+ } from "./cli-m0045npe.js";
7
7
  import"./cli-qvg26twp.js";
8
8
  import"./cli-dn91fb56.js";
9
9
  import"./cli-9btx12wp.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runPromptsCommand
3
- } from "./cli-y2ty9vtv.js";
3
+ } from "./cli-m805msp6.js";
4
4
  import"./cli-mm48br00.js";
5
5
  import {
6
6
  runHarnessCommand
@@ -8,7 +8,7 @@ import {
8
8
  import"./cli-97fnztpy.js";
9
9
  import {
10
10
  runSkillsCommand
11
- } from "./cli-rt26sbz7.js";
11
+ } from "./cli-m0045npe.js";
12
12
  import"./cli-qvg26twp.js";
13
13
  import"./cli-dn91fb56.js";
14
14
  import"./cli-9btx12wp.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moda-ai/cli",
3
- "version": "1.55.24",
3
+ "version": "1.55.25",
4
4
  "description": "CLI for Moda: production monitoring and evals for AI agents.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": "moda.skill_index.v1",
3
- "bundled_at": "2026-10-15T13:10:26.478Z",
4
- "cli_version": "1.55.24",
3
+ "bundled_at": "2026-10-15T20:30:38.730Z",
4
+ "cli_version": "1.55.25",
5
5
  "skills": [
6
6
  {
7
7
  "id": "integration-cloudflare-think",
@@ -82,7 +82,10 @@ on search, traces, and clusters. `moda <noun> <verb> --help` shows which.
82
82
  2. `moda problems get <problem_id> --evidence` and open two or three anchors with
83
83
  `moda traces get <id> --msg-index=<n>` before deciding on a cause.
84
84
  3. Make the change in code; cite the problem id and trace ids in the commit or PR.
85
- 4. After it ships, and only if the user agrees:
85
+ 4. Test a prompt change on the Problem's failing traces (a paid replay, so ask
86
+ first; prompt changes only today):
87
+ `moda prompts ab --problem=<problem_id> --baseline=<prompt_key> --candidate=<edited file>`.
88
+ 5. After it ships, and only if the user agrees:
86
89
  `moda problems feedback <problem_id> --action=mark_fixed`.
87
90
  More: `references/production-intelligence.md`.
88
91
 
@@ -90,7 +90,8 @@ Rules that keep the fix grounded:
90
90
  - Cite the Moda problem id and the trace ids you relied on in the PR
91
91
  description, so a reviewer can open the same evidence.
92
92
  - To test a prompt change against real traffic before shipping it, A/B it with
93
- `moda prompts ab --baseline=<path> --candidate=<path> --traces=<ids>` (see
94
- section 7).
93
+ `moda prompts ab --baseline=<path> --candidate=<path> --problem=<problem_id>`
94
+ (replays the Problem's failing traces from their evidence anchors) or
95
+ `--traces=<ids>` (see section 7).
95
96
  - Mark the Problem fixed only after the change ships. If the issue is detected
96
97
  again, the Problem comes back; `moda problems reopen` reopens it by hand.
@@ -72,7 +72,10 @@ read, they fail (exit 1) with the number and the exact command to re-run with `-
72
72
  Only add `--yes` when the user has agreed to that spend. Limits: `--cases`
73
73
  default 5, max 100; `--seeds` default 3, max 10; `--traces` at most 100
74
74
  (`--traces=conv_id@412` replays that trace from msg_index 412, with the
75
- earlier turns as history);
75
+ earlier turns as history, and writes its case from the messages around 412);
76
+ `--problem=<problem_id>` picks a Problem's affected traces, each anchored at
77
+ its earliest evidence msg_index, up to
78
+ `--max-cases` (default 10, max 100), and prints them (`problemCases`);
76
79
  `--model` / `--assistant-model` must be one of `openai/gpt-5.6-luna`,
77
80
  `openai/gpt-4o-mini`, `openai/gpt-4o`, `anthropic/claude-sonnet-4-5`,
78
81
  `anthropic/claude-haiku-4-5`, `google/gemini-2.5-flash`,
@@ -80,7 +83,8 @@ earlier turns as history);
80
83
 
81
84
  `prompts ab --traces=<ids>` builds one case per trace from Moda's distilled
82
85
  scenario and success criteria for that trace. A trace Moda cannot distill
83
- falls back to its opening user message with three generic criteria (which long
86
+ falls back to its opening user message (for `id@N`, the user message leading
87
+ into N) with three generic criteria (which long
84
88
  traces pass easily); the run warns and lists it under `caseSources.fallback`.
85
89
  `prompts ab` never makes its replay set the tenant's primary set unless you
86
90
  pass `--promote`.