@pome-sh/cli 0.23.4 → 0.23.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -99,6 +99,16 @@ Three rules CI must honor:
99
99
  excluded from the verdict fraction (`3 of 4 passed · 1 incomplete`) so neither
100
100
  is counted as a pass nor charged to the agent as a loss — but a group holding
101
101
  one cannot exit `0`.
102
+ - **`pome fix-prompt` uses the same codes, and its `1` is only ever
103
+ INCOMPLETE.** Building a prompt for a failed run set exits `0` (the prompt is
104
+ on stdout, and stdout being non-empty is the signal that there was something
105
+ to fix); an all-green root exits `0` with nothing on stdout; a bad argument or
106
+ a root with no readable run sets exits `5`. `1` is reserved for the one case
107
+ where the newest non-passing set was never fully graded: no prompt is built,
108
+ because a run whose checks never ran is not evidence of an agent defect. This
109
+ matches `pome run`, where `1` also covers INCOMPLETE — the two commands do not
110
+ disagree about what an ungraded run exits, and `verdict.json`'s `state` stays
111
+ the field to read when a script needs the reason rather than the code.
102
112
 
103
113
  ## Development
104
114
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "pome-sh",
3
- "version": "0.23.4",
4
- "git_sha": "33793129a877db31c9ef5c777f5dd45c2f6d1a8c",
5
- "build_time": "2026-08-10T14:48:52.274Z"
3
+ "version": "0.23.5",
4
+ "git_sha": "4f55e6deab92cf0ff34dd769c73763fc9b480375",
5
+ "build_time": "2026-08-10T15:36:42.602Z"
6
6
  }
@@ -117,13 +117,15 @@ function groupRunSets(trials) {
117
117
  (a, b) => a.verdict.finalized_at.localeCompare(b.verdict.finalized_at)
118
118
  );
119
119
  const last = bucket[bucket.length - 1];
120
+ const hasFailed = bucket.some((t) => t.verdict.state === "fail");
121
+ const allPassed = bucket.every((t) => t.verdict.state === "pass");
120
122
  sets.push({
121
123
  groupId: bucket[0].verdict.group_id,
122
124
  taskName: bucket[0].verdict.task_name,
123
125
  taskPath: bucket[0].verdict.task_path,
124
126
  trials: bucket,
125
127
  latestFinalizedAt: last.verdict.finalized_at,
126
- anyFailed: bucket.some((t) => !t.verdict.passed)
128
+ outcome: hasFailed ? "fail" : allPassed ? "pass" : "incomplete"
127
129
  });
128
130
  }
129
131
  sets.sort((a, b) => a.latestFinalizedAt.localeCompare(b.latestFinalizedAt));
@@ -131,14 +133,26 @@ function groupRunSets(trials) {
131
133
  }
132
134
  function latestFailedRunSet(sets) {
133
135
  for (let i = sets.length - 1; i >= 0; i -= 1) {
134
- if (sets[i].anyFailed) return sets[i];
136
+ if (sets[i].outcome === "fail") return sets[i];
137
+ }
138
+ return null;
139
+ }
140
+ function latestIncompleteRunSet(sets) {
141
+ for (let i = sets.length - 1; i >= 0; i -= 1) {
142
+ if (sets[i].outcome === "incomplete") return sets[i];
135
143
  }
136
144
  return null;
137
145
  }
138
146
  async function discoverRunSet(target) {
139
147
  const anchorResult = await readVerdictArtifactDetailed(target);
140
148
  if (anchorResult.status === "stale-version") {
141
- return { kind: "trial-dir", set: null, totalSets: 0, staleVersionCount: 1 };
149
+ return {
150
+ kind: "trial-dir",
151
+ set: null,
152
+ incompleteSet: null,
153
+ totalSets: 0,
154
+ staleVersionCount: 1
155
+ };
142
156
  }
143
157
  if (anchorResult.status === "ok") {
144
158
  const anchor = anchorResult.trial;
@@ -151,18 +165,27 @@ async function discoverRunSet(target) {
151
165
  return {
152
166
  kind: "trial-dir",
153
167
  set: own,
168
+ incompleteSet: null,
154
169
  totalSets: Math.max(sets2.length, 1),
155
170
  staleVersionCount: staleVersionDirs2.length
156
171
  };
157
172
  }
158
173
  if (!existsSync(target)) {
159
- return { kind: "root", set: null, totalSets: 0, staleVersionCount: 0 };
174
+ return {
175
+ kind: "root",
176
+ set: null,
177
+ incompleteSet: null,
178
+ totalSets: 0,
179
+ staleVersionCount: 0
180
+ };
160
181
  }
161
182
  const { trials, staleVersionDirs } = await scanVerdictArtifactsDetailed(target);
162
183
  const sets = groupRunSets(trials);
184
+ const failedSet = latestFailedRunSet(sets);
163
185
  return {
164
186
  kind: "root",
165
- set: latestFailedRunSet(sets),
187
+ set: failedSet,
188
+ incompleteSet: failedSet ? null : latestIncompleteRunSet(sets),
166
189
  totalSets: sets.length,
167
190
  staleVersionCount: staleVersionDirs.length
168
191
  };
@@ -1,5 +1,5 @@
1
1
  import { newGroupId, criterionPhrase } from './chunk-RGZBC7NF.js';
2
- import { runTaskHosted, resolveRunAgentIdentity } from './chunk-LRNQ3IDA.js';
2
+ import { runTaskHosted, resolveRunAgentIdentity } from './chunk-RSQY6UIL.js';
3
3
  import './chunk-XOWIA7NR.js';
4
4
  import { createHostedClient, parseTaskFile, outcomeOf } from './chunk-GFTFMA3T.js';
5
5
  import './chunk-NW7HGA2K.js';
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { DEFAULT_CONTROL_PLANE_URL, DEFAULT_DASHBOARD_URL, clearLocalCredentials, friendlyHostedError, runSessionCreate, runSessionList, runSessionStop, resolveCredentials, runTaskHosted, discoverRunSet, VERDICT_ARTIFACT_VERSION, loadTrialEvents, persistCredentialsAfterLogin, DEFAULT_DOCS_SITE_ORIGIN, resolveSeams, resolveCachedAgentId, readLinkCache, postAgentResolver, writeLinkCache, ensurePomeGitignored } from '../../chunk-LRNQ3IDA.js';
2
+ import { DEFAULT_CONTROL_PLANE_URL, DEFAULT_DASHBOARD_URL, clearLocalCredentials, friendlyHostedError, runSessionCreate, runSessionList, runSessionStop, resolveCredentials, runTaskHosted, discoverRunSet, VERDICT_ARTIFACT_VERSION, loadTrialEvents, persistCredentialsAfterLogin, DEFAULT_DOCS_SITE_ORIGIN, resolveSeams, resolveCachedAgentId, readLinkCache, postAgentResolver, writeLinkCache, ensurePomeGitignored } from '../../chunk-RSQY6UIL.js';
3
3
  import { readManifest, writeManifest, MANIFEST_JSON, readRequiredManifest, normalizeManifestTwins } from '../../chunk-XOWIA7NR.js';
4
4
  import { assetPath, runTask, resolvePackageRoot, DEMO_TASK_NAME, demoTaskPath } from '../../chunk-YUKMLGYF.js';
5
5
  import '../../chunk-XDU6TD4O.js';
6
6
  import '../../chunk-CBFKZZBR.js';
7
- import { parseTaskFile, scoreStatus, runScoreLine, readLatestRun, readMetaSummary, readConfigTwins, scoreCountsSummary, markerFor, outcomeOf, criterionMarkerLabel, twinSkipSuffix, readCodeCriteria, createHostedClient, toTwinHttpEvent, redactJsonl, scoreFromFinalizeResponse, parseGitHubSeedState, uploadRunBlobs, isPreSatisfied } from '../../chunk-GFTFMA3T.js';
7
+ import { parseTaskFile, scoreStatus, runScoreLine, readLatestRun, readMetaSummary, outcomeOf, readConfigTwins, scoreCountsSummary, markerFor, criterionMarkerLabel, twinSkipSuffix, readCodeCriteria, createHostedClient, toTwinHttpEvent, redactJsonl, scoreFromFinalizeResponse, parseGitHubSeedState, uploadRunBlobs, isPreSatisfied } from '../../chunk-GFTFMA3T.js';
8
8
  import '../../chunk-NW7HGA2K.js';
9
9
  import { MOUNTED_TWINS, deriveAgentSlug, exitCodeFor, HostedUsageError, HostedOrchError, HostedAuthError, HostedQuotaError } from '../../chunk-7AMIVWUB.js';
10
10
  import { TAPE_ASSERTABLE_TOOLS } from '../../chunk-FKZZWWYC.js';
@@ -4348,6 +4348,14 @@ function criterionMarker(c) {
4348
4348
  function failedResults(verdict) {
4349
4349
  return verdict.criteria_results.filter((r) => outcomeOf(r) === "failed");
4350
4350
  }
4351
+ function isGraded(t) {
4352
+ return t.verdict.state !== "incomplete";
4353
+ }
4354
+ function ungradedCount(verdict) {
4355
+ return verdict.criteria_results.filter(
4356
+ (r) => !isPreSatisfied(r) && outcomeOf(r) !== "passed" && outcomeOf(r) !== "failed"
4357
+ ).length;
4358
+ }
4351
4359
  function flattenLine(text, max = 300) {
4352
4360
  const flat = text.replace(/\s+/g, " ").trim();
4353
4361
  return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
@@ -4355,10 +4363,14 @@ function flattenLine(text, max = 300) {
4355
4363
  function renderGroupedSignatures(trials) {
4356
4364
  const byCriterion = /* @__PURE__ */ new Map();
4357
4365
  const outcomesSeen = /* @__PURE__ */ new Map();
4366
+ const gradedFor = /* @__PURE__ */ new Map();
4358
4367
  for (const trial of trials) {
4359
4368
  for (const result of trial.verdict.criteria_results) {
4360
4369
  const key = result.criterion.text;
4361
4370
  const outcome = isPreSatisfied(result) ? "excluded" : outcomeOf(result);
4371
+ if (outcome === "passed" || outcome === "failed") {
4372
+ gradedFor.set(key, (gradedFor.get(key) ?? 0) + 1);
4373
+ }
4362
4374
  if (outcome === "failed") {
4363
4375
  const entry = byCriterion.get(key) ?? {
4364
4376
  marker: criterionMarker(result.criterion),
@@ -4381,12 +4393,12 @@ function renderGroupedSignatures(trials) {
4381
4393
  else if (seen.size === 1 && seen.has("excluded")) preSatisfiedEverywhere.push(key);
4382
4394
  else notUniformlyEvaluated.push(key);
4383
4395
  }
4384
- const completed = trials.length;
4385
4396
  const blocks = [...byCriterion.entries()].sort((a, b) => b[1].hits.length - a[1].hits.length).map(([text, { marker, hits }], idx) => {
4386
4397
  const lines = hits.map(
4387
4398
  (h) => ` - ${h.label}: ${flattenLine(h.reason)}`
4388
4399
  );
4389
- return `${idx + 1}. ${marker} ${flattenLine(text)} \u2014 failed in ${hits.length} of ${completed} completed trials
4400
+ const graded = gradedFor.get(text) ?? hits.length;
4401
+ return `${idx + 1}. ${marker} ${flattenLine(text)} \u2014 failed in ${hits.length} of ${graded} trials that graded it
4390
4402
  ${lines.join("\n")}`;
4391
4403
  });
4392
4404
  if (blocks.length === 0 && passedEverywhere.length === 0 && preSatisfiedEverywhere.length === 0 && notUniformlyEvaluated.length === 0) {
@@ -4414,18 +4426,19 @@ ${lines.join("\n")}`;
4414
4426
  return [...blocks, ...notes].join("\n");
4415
4427
  }
4416
4428
  function representativeFailingTrial(trials) {
4417
- const failing = trials.filter((t) => !t.verdict.passed);
4429
+ const failing = trials.filter((t) => t.verdict.state === "fail");
4418
4430
  if (failing.length === 0) return null;
4419
4431
  return failing.reduce(
4420
4432
  (worst, t) => failedResults(t.verdict).length > failedResults(worst.verdict).length ? t : worst
4421
4433
  );
4422
4434
  }
4423
4435
  function buildGroupFixUserPrompt(ctx) {
4424
- const completed = ctx.trials.length;
4425
- const passed = ctx.trials.filter((t) => t.verdict.passed).length;
4436
+ const incomplete = ctx.trials.filter((t) => !isGraded(t));
4437
+ const completed = ctx.trials.length - incomplete.length;
4438
+ const passed = ctx.trials.filter((t) => t.verdict.state === "pass").length;
4426
4439
  const representative = representativeFailingTrial(ctx.trials);
4427
4440
  const otherFailing = ctx.trials.filter(
4428
- (t) => !t.verdict.passed && t !== representative
4441
+ (t) => t.verdict.state === "fail" && t !== representative
4429
4442
  );
4430
4443
  const signatures = redactSecrets(
4431
4444
  renderGroupedSignatures(ctx.trials)
@@ -4439,9 +4452,11 @@ function buildGroupFixUserPrompt(ctx) {
4439
4452
  )
4440
4453
  );
4441
4454
  const promptBlock = ctx.task ? redactSecrets(ctx.task.prompt) : `(task file not found at ${ctx.trials[0]?.verdict.task_path ?? "?"} \u2014 criteria above come from the cloud verdicts)`;
4455
+ const tally = completed === 0 ? "no trial in this set was graded end to end" : `${passed} of ${completed} completed trials passed`;
4456
+ const gapNote = incomplete.length > 0 ? ` \xB7 ${incomplete.length} INCOMPLETE (counted in nothing below \u2014 see the last section)` : "";
4442
4457
  const sections = [];
4443
4458
  sections.push(`## Run set (cloud-judged)
4444
- task ${redactSecrets(ctx.taskName)} \xB7 ${ctx.groupId ? `group ${ctx.groupId}` : "single run"} \xB7 ${passed} of ${completed} completed trials passed`);
4459
+ task ${redactSecrets(ctx.taskName)} \xB7 ${ctx.groupId ? `group ${ctx.groupId}` : "single run"} \xB7 ${tally}${gapNote}`);
4445
4460
  sections.push(`## Grouped failure signatures (from the cloud judge)
4446
4461
  ${escapeTagContent(signatures)}`);
4447
4462
  sections.push(`## Task prompt (what the agent was told to do)
@@ -4463,6 +4478,17 @@ ${escapeTagContent(trace)}
4463
4478
  return `- ${t.label} \u2014 failed: ${failed || "(see verdict)"} \u2014 trace at ${join(t.runDir, "events.jsonl")}`;
4464
4479
  });
4465
4480
  sections.push(`## Other failing trials (traces on disk)
4481
+ ${escapeTagContent(redactSecrets(lines.join("\n")))}`);
4482
+ }
4483
+ if (incomplete.length > 0) {
4484
+ const lines = incomplete.map(
4485
+ (t) => `- ${t.label} \u2014 ${ungradedCount(t.verdict)} criterion(s) never graded \u2014 trace at ${join(t.runDir, "events.jsonl")}`
4486
+ );
4487
+ sections.push(`## Trials the grader never finished (INCOMPLETE)
4488
+ The grader never reached every criterion in these trials, so they are neither
4489
+ passes nor failures and are counted in no fraction above. Do NOT treat them as
4490
+ evidence for or against any fix: a criterion that never ran is a grader or seed
4491
+ gap, not something the agent did wrong.
4466
4492
  ${escapeTagContent(redactSecrets(lines.join("\n")))}`);
4467
4493
  }
4468
4494
  if (passed > 0 && passed < completed) {
@@ -4495,7 +4521,7 @@ var DEFAULT_AGENT_FILE = "examples/agents/scripted-triage-agent.ts";
4495
4521
  var DEFAULT_AGENT_COMMAND = `node ${DEFAULT_AGENT_FILE}`;
4496
4522
  var MANIFEST_SCHEMA_URL = "https://pome.sh/schemas/v1/pome.json";
4497
4523
  function readPackageVersion() {
4498
- if ("0.23.4".length > 0) return "0.23.4";
4524
+ if ("0.23.5".length > 0) return "0.23.5";
4499
4525
  try {
4500
4526
  const here = dirname(fileURLToPath(import.meta.url));
4501
4527
  const candidates = [
@@ -4988,7 +5014,7 @@ function createProgram() {
4988
5014
  taskForRuns.config.runs
4989
5015
  );
4990
5016
  if (k > 1) {
4991
- const { runTrialGroup } = await import('../../runTrialGroup-AVNW3D3L.js');
5017
+ const { runTrialGroup } = await import('../../runTrialGroup-NIBA5KRF.js');
4992
5018
  const fileForRerun = relative(process.cwd(), file);
4993
5019
  const rerunCommand = defaultTask ? options.trials !== void 0 ? `pome run -n ${k}` : "pome run" : `pome run ${fileForRerun && !fileForRerun.startsWith("..") ? fileForRerun : file} -n ${k}`;
4994
5020
  const groupResult = await runTrialGroup({
@@ -5202,6 +5228,31 @@ function createProgram() {
5202
5228
  return;
5203
5229
  }
5204
5230
  if (!discovery.set) {
5231
+ const incomplete = discovery.incompleteSet;
5232
+ if (incomplete) {
5233
+ const ungradedTrials = incomplete.trials.filter(
5234
+ (t) => t.verdict.state === "incomplete"
5235
+ ).length;
5236
+ const gradedFailures = incomplete.trials.reduce(
5237
+ (n, t) => n + t.verdict.criteria_results.filter((r) => outcomeOf(r) === "failed").length,
5238
+ 0
5239
+ );
5240
+ const which = `task ${incomplete.taskName}${incomplete.groupId ? ` \xB7 group ${incomplete.groupId}` : ""}`;
5241
+ console.error(
5242
+ `Not routed to fix-prompt: no run set under ${root} failed outright. The most recent non-passing one (${which}) is INCOMPLETE \u2014 ${ungradedTrials} of ${incomplete.trials.length} trial(s) have criteria the grader never graded.`
5243
+ );
5244
+ if (gradedFailures > 0) {
5245
+ console.error(
5246
+ `${gradedFailures} criterion result(s) in that set WERE graded and did fail, so this is not only a grading gap \u2014 but no trial in it was graded end to end, and a fix prompt built from a partial grading would claim more than was checked. Re-run \`pome run ${incomplete.taskPath}\` to grade the rest, or point fix-prompt straight at one trial (\`pome fix-prompt ${incomplete.trials[0].runDir}\`) to build one from the partial grading anyway.`
5247
+ );
5248
+ } else {
5249
+ console.error(
5250
+ `Nothing in that set was graded and failed, so it is a grader/seed gap, not an agent defect, and fix-prompt will not hand it to your coding agent. Re-run \`pome run ${incomplete.taskPath}\` to grade those criteria; if they come back ungraded, the gap is in the task's checks or its seed, not in your prompt.`
5251
+ );
5252
+ }
5253
+ process.exitCode = 1;
5254
+ return;
5255
+ }
5205
5256
  console.error(
5206
5257
  `Nothing to fix: the latest run sets under ${root} all passed.`
5207
5258
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pome-sh/cli",
3
- "version": "0.23.4",
3
+ "version": "0.23.5",
4
4
  "description": "Digital-twin testing for AI agents \u2014 run tasks against resettable local or hosted twins and record tool-call traces for evaluation on pome.sh.",
5
5
  "keywords": [
6
6
  "ai",