@eigenpal/cli 0.19.1 → 0.20.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @eigenpal/cli
2
2
 
3
+ ## 0.20.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0ce2092: `eigenpal workflow dataset review-request` (and the `eigenpal agents dataset` mirror) is the agent-friendly review loop: create a request with reviewer instructions, `--focus` / `--focus-reason` paths, `--ignore` paths reviewers can skip, and per-example `--item-note` / `--field-note` comments; `list`/`get`/`events` expose `progress.complete`, `fieldDecisions`, and `inputDrifted`; `item --action` records approve/reject/comment/edit plus per-field `field-decision` (clear with `--clear` / `--decision null`); `update --status closed` finishes the request. There is no auto-apply — pull and reconcile examples manually. Reject is recommendation-only; nothing is deleted.
8
+
3
9
  ## 0.17.5
4
10
 
5
11
  ### Patch Changes
package/dist/cli.js CHANGED
@@ -7738,6 +7738,9 @@ var init_common = __esm(() => {
7738
7738
  VERIFICATION: "vrf",
7739
7739
  DATASET: "ds",
7740
7740
  DATASET_EXAMPLE: "dex",
7741
+ DATASET_REVIEW_REQUEST: "dsr",
7742
+ DATASET_REVIEW_ITEM: "dsri",
7743
+ DATASET_REVIEW_EVENT: "dsre",
7741
7744
  EXPERIMENT: "exp",
7742
7745
  EXPERIMENT_RUN: "exr",
7743
7746
  EXPERIMENT_EVALUATION: "evl",
@@ -15451,15 +15454,21 @@ var require_dist = __commonJS(function(exports, module) {
15451
15454
  });
15452
15455
 
15453
15456
  // ../types/src/validation/ajv.ts
15454
- var import_ajv, import_ajv_formats, import_json_schema_draft_07, eigenpalAjv;
15457
+ function getEigenpalAjv() {
15458
+ if (!sharedAjv) {
15459
+ const ajv = new import_ajv.default({ allErrors: true, strict: false, verbose: true });
15460
+ import_ajv_formats.default(ajv);
15461
+ ajv.addKeyword({ keyword: "x-eigenpal-type", metaSchema: { type: "string" } });
15462
+ ajv.addMetaSchema({ ...import_json_schema_draft_07.default, $id: "https://json-schema.org/draft-07/schema#" });
15463
+ sharedAjv = ajv;
15464
+ }
15465
+ return sharedAjv;
15466
+ }
15467
+ var import_ajv, import_ajv_formats, import_json_schema_draft_07, sharedAjv = null;
15455
15468
  var init_ajv = __esm(() => {
15456
15469
  import_ajv = __toESM(require_ajv(), 1);
15457
15470
  import_ajv_formats = __toESM(require_dist(), 1);
15458
15471
  import_json_schema_draft_07 = __toESM(require_json_schema_draft_07(), 1);
15459
- eigenpalAjv = new import_ajv.default({ allErrors: true, strict: false, verbose: true });
15460
- import_ajv_formats.default(eigenpalAjv);
15461
- eigenpalAjv.addKeyword({ keyword: "x-eigenpal-type", metaSchema: { type: "string" } });
15462
- eigenpalAjv.addMetaSchema({ ...import_json_schema_draft_07.default, $id: "https://json-schema.org/draft-07/schema#" });
15463
15472
  });
15464
15473
 
15465
15474
  // ../types/src/validation/workspace-schema.ts
@@ -15481,8 +15490,9 @@ function validateWorkspaceSchema(content, _filename) {
15481
15490
  if (schema.type !== "object") {
15482
15491
  errors.push('Root "type" must be "object"');
15483
15492
  }
15484
- if (!eigenpalAjv.validateSchema(schema)) {
15485
- const metaErrors = eigenpalAjv.errors ?? [];
15493
+ const ajv = getEigenpalAjv();
15494
+ if (!ajv.validateSchema(schema)) {
15495
+ const metaErrors = ajv.errors ?? [];
15486
15496
  for (const err of metaErrors) {
15487
15497
  errors.push(`${err.instancePath || "/"}: ${err.message ?? "invalid schema"}`);
15488
15498
  }
@@ -206380,7 +206390,7 @@ import { fileURLToPath as fileURLToPath8 } from "url";
206380
206390
  // package.json
206381
206391
  var package_default = {
206382
206392
  name: "@eigenpal/cli",
206383
- version: "0.19.1",
206393
+ version: "0.20.0",
206384
206394
  description: "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
206385
206395
  keywords: [
206386
206396
  "eigenpal",
@@ -210037,6 +210047,393 @@ function toCsv(rows, columns) {
210037
210047
  `);
210038
210048
  }
210039
210049
 
210050
+ // src/commands/dataset-review-request-briefing.ts
210051
+ function splitOnce(value, separator) {
210052
+ const index = value.indexOf(separator);
210053
+ if (index < 0)
210054
+ return [value, undefined];
210055
+ return [value.slice(0, index), value.slice(index + separator.length)];
210056
+ }
210057
+ function parseFocusReasonFlag(value) {
210058
+ const [path, reason] = splitOnce(value, "=");
210059
+ const trimmedPath = path.trim();
210060
+ if (!trimmedPath) {
210061
+ throw new Error("--focus-reason requires path=reason");
210062
+ }
210063
+ const trimmedReason = reason?.trim();
210064
+ if (!trimmedReason) {
210065
+ throw new Error("--focus-reason requires path=reason");
210066
+ }
210067
+ return { path: trimmedPath, reason: trimmedReason };
210068
+ }
210069
+ function parseItemNoteFlag(value) {
210070
+ const [exampleName, comment] = splitOnce(value, "=");
210071
+ const trimmedName = exampleName.trim();
210072
+ const trimmedComment = comment?.trim();
210073
+ if (!trimmedName || !trimmedComment) {
210074
+ throw new Error("--item-note requires exampleName=comment");
210075
+ }
210076
+ return { exampleName: trimmedName, comment: trimmedComment };
210077
+ }
210078
+ function parseFieldNoteFlag(value) {
210079
+ const [left, comment] = splitOnce(value, "=");
210080
+ const trimmedComment = comment?.trim();
210081
+ const dot = left.indexOf(".");
210082
+ const exampleName = (dot < 0 ? left : left.slice(0, dot)).trim();
210083
+ const path = (dot < 0 ? "" : left.slice(dot + 1)).trim();
210084
+ if (!exampleName || !path || !trimmedComment) {
210085
+ throw new Error("--field-note requires exampleName.path=comment");
210086
+ }
210087
+ return { exampleName, fields: [{ path, comment: trimmedComment }] };
210088
+ }
210089
+ function mergeFocusFields(paths, reasons) {
210090
+ const byPath = new Map;
210091
+ for (const path of paths) {
210092
+ const trimmed = path.trim();
210093
+ if (!trimmed)
210094
+ continue;
210095
+ byPath.set(trimmed, { path: trimmed, reason: null });
210096
+ }
210097
+ for (const field of reasons) {
210098
+ byPath.set(field.path, field);
210099
+ }
210100
+ return [...byPath.values()];
210101
+ }
210102
+ function mergeItemNotes(notes) {
210103
+ const byName = new Map;
210104
+ for (const note of notes) {
210105
+ const current = byName.get(note.exampleName) ?? { exampleName: note.exampleName };
210106
+ if (note.comment)
210107
+ current.comment = note.comment;
210108
+ if (note.fields?.length) {
210109
+ current.fields = [...current.fields ?? [], ...note.fields];
210110
+ }
210111
+ byName.set(note.exampleName, current);
210112
+ }
210113
+ return [...byName.values()];
210114
+ }
210115
+ function parseJsonObjectFlag(raw, flag) {
210116
+ try {
210117
+ return JSON.parse(raw);
210118
+ } catch {
210119
+ throw new Error(`${flag} must be valid JSON`);
210120
+ }
210121
+ }
210122
+
210123
+ // src/commands/dataset-review-request.ts
210124
+ function datasetReviewRequestsPath(automationId, reviewId) {
210125
+ const base = `/v1/automations/${automationId}/dataset-review-requests`;
210126
+ return reviewId ? `${base}/${reviewId}` : base;
210127
+ }
210128
+ function datasetReviewRequestEventsPath(automationId, reviewId) {
210129
+ return `${datasetReviewRequestsPath(automationId, reviewId)}/events`;
210130
+ }
210131
+ function datasetReviewRequestItemsPath(automationId, reviewId, itemId) {
210132
+ const base = `${datasetReviewRequestsPath(automationId, reviewId)}/items`;
210133
+ return itemId ? `${base}/${itemId}` : base;
210134
+ }
210135
+ function collectRepeatable(val2, prev) {
210136
+ return [...prev, val2];
210137
+ }
210138
+ function formatProgress(progress) {
210139
+ if (!progress)
210140
+ return "-";
210141
+ if (progress.complete) {
210142
+ return progress.rejected ? `complete (${progress.rejected} rejected)` : "complete";
210143
+ }
210144
+ const remaining = progress.remaining ?? 0;
210145
+ const total = progress.total ?? remaining;
210146
+ return `${remaining}/${total} remaining`;
210147
+ }
210148
+ function parseFieldDecision(opts) {
210149
+ if (opts.clear) {
210150
+ if (opts.decision !== undefined) {
210151
+ throw new Error("pass either --clear or --decision, not both");
210152
+ }
210153
+ return null;
210154
+ }
210155
+ if (opts.decision === undefined) {
210156
+ throw new Error("--decision or --clear is required when --action field-decision");
210157
+ }
210158
+ const normalized = opts.decision.trim().toLowerCase();
210159
+ if (normalized === "null" || normalized === "clear") {
210160
+ return null;
210161
+ }
210162
+ if (normalized === "approved" || normalized === "rejected") {
210163
+ return normalized;
210164
+ }
210165
+ throw new Error("--decision must be approved, rejected, null, or clear");
210166
+ }
210167
+ function buildReviewItemPatchBody(action, opts) {
210168
+ const body = {
210169
+ action,
210170
+ expectedUpdatedAt: opts.expectedUpdatedAt
210171
+ };
210172
+ if (opts.comment !== undefined)
210173
+ body.comment = opts.comment;
210174
+ if (opts.fieldPath !== undefined)
210175
+ body.fieldPath = opts.fieldPath;
210176
+ if (action === "edit") {
210177
+ if (opts.expectedJson === undefined) {
210178
+ throw new Error("--expected-json is required when --action edit");
210179
+ }
210180
+ body.expected = parseJsonObjectFlag(opts.expectedJson, "--expected-json");
210181
+ } else if (opts.expectedJson !== undefined) {
210182
+ throw new Error("--expected-json is only valid with --action edit");
210183
+ }
210184
+ if (action === "field-decision") {
210185
+ if (!opts.fieldPath?.trim()) {
210186
+ throw new Error("--field-path is required when --action field-decision");
210187
+ }
210188
+ body.decision = parseFieldDecision({ decision: opts.decision, clear: opts.clear });
210189
+ } else if (opts.decision !== undefined || opts.clear) {
210190
+ throw new Error("--decision / --clear are only valid with --action field-decision");
210191
+ }
210192
+ return body;
210193
+ }
210194
+ function registerDatasetReviewRequestCommands(dataset, resolveAutomation) {
210195
+ const reviewRequest = dataset.command("review-request").description("Review dataset ground truth with a human. Snapshot examples for review and poll progress until reviewers finish.").action(() => {
210196
+ process.stderr.write("`dataset review-request` requires a subcommand. Run `dataset review-request --help`.\n");
210197
+ process.exit(2);
210198
+ });
210199
+ addJsonFlag(withPagination(withBaseUrl(reviewRequest.command("list <automation-id>")).option("--status <csv>", "Filter by review status (comma-separated: draft,open,paused,closed)"), 50)).action(action(async (automationRef, opts) => {
210200
+ const { client, automationId } = await resolveAutomation(automationRef, opts);
210201
+ const params = {
210202
+ limit: String(opts.limit),
210203
+ offset: String(opts.offset)
210204
+ };
210205
+ if (opts.status)
210206
+ params.status = opts.status;
210207
+ const raw = await client.get(datasetReviewRequestsPath(automationId), params);
210208
+ renderListResult(raw, [
210209
+ { key: "id", header: "id" },
210210
+ { key: "title", header: "title" },
210211
+ { key: "status", header: "status" },
210212
+ {
210213
+ key: "progress",
210214
+ header: "progress",
210215
+ format: (value) => formatProgress(value)
210216
+ },
210217
+ { key: "createdAt", header: "createdAt", format: formatTimestamp }
210218
+ ], { ...opts, entityLabel: "review request" });
210219
+ }));
210220
+ addJsonFlag(withBaseUrl(reviewRequest.command("create <automation-id>"))).description("Request human review of dataset ground truth. Snapshot examples and poll progress until review is complete.").requiredOption("--title <title>", "Review request title").requiredOption("--example-name <name>", "Example folder name to include (repeatable)", collectRepeatable, []).option("--instructions <text>", "Note shown to the reviewer for the whole request").option("--focus <path>", "Expected-output path to highlight (repeatable)", collectRepeatable, []).option("--focus-reason <spec>", "Reason a focus path needs review, as path=reason (repeatable)", collectRepeatable, []).option("--ignore <path>", "Expected-output path reviewers can skip (repeatable)", collectRepeatable, []).option("--item-note <spec>", "Example-level note seeded at create time, as exampleName=comment (repeatable)", collectRepeatable, []).option("--field-note <spec>", "Field-level note seeded at create time, as exampleName.path=comment (repeatable)", collectRepeatable, []).option("--focus-json <json>", "JSON array of { path, reason? } focus fields").option("--notes-json <json>", "JSON array of { exampleName, comment?, fields?: [{ path, comment }] }").option("--status <draft|open|paused>", "Initial lifecycle status (default: draft)").addHelpText("after", `
210221
+ Examples:
210222
+ $ eigenpal workflow dataset review-request create wf_abc123 \\
210223
+ --title "Q1 invoice GT review" \\
210224
+ --example-name invoice-foo --example-name invoice-bar \\
210225
+ --instructions "Check IBAN checksums and totals." \\
210226
+ --focus vendor.iban --focus-reason 'vendor.iban=OCR often mangles IBANs' \\
210227
+ --ignore currency \\
210228
+ --field-note 'invoice-foo.total=off by 0.01 last run' \\
210229
+ --status open --json
210230
+
210231
+ $ eigenpal workflow dataset review-request list wf_abc123 --status open --json
210232
+ $ eigenpal workflow dataset review-request get wf_abc123 dsr_... --json
210233
+
210234
+ Agent loop: create with focus/ignore and per-example notes → poll \`get --json\`
210235
+ until \`.progress.complete\` → record per-field decisions and per-example
210236
+ approve/reject with comments → read \`events\` → \`dataset pull\` and MANUAL
210237
+ per-example reconcile into the live dataset (no auto-apply exists by design;
210238
+ reviewers can err) → \`update --status closed\`.
210239
+
210240
+ \`reject\` is a recommendation only — nothing is deleted by any review endpoint.
210241
+ Use \`--json\` from agents; API errors exit non-zero.
210242
+ `).action(action(async (automationRef, opts) => {
210243
+ if (opts.exampleName.length === 0) {
210244
+ throw new Error("pass at least one --example-name");
210245
+ }
210246
+ const { client, automationId } = await resolveAutomation(automationRef, opts);
210247
+ const focusFromFlags = mergeFocusFields(opts.focus ?? [], (opts.focusReason ?? []).map(parseFocusReasonFlag));
210248
+ const focusFromJson = opts.focusJson ? parseJsonObjectFlag(opts.focusJson, "--focus-json") : [];
210249
+ if (opts.focusJson && !Array.isArray(focusFromJson)) {
210250
+ throw new Error("--focus-json must be a JSON array");
210251
+ }
210252
+ const notesFromFlags = mergeItemNotes([
210253
+ ...(opts.itemNote ?? []).map(parseItemNoteFlag),
210254
+ ...(opts.fieldNote ?? []).map(parseFieldNoteFlag)
210255
+ ]);
210256
+ const notesFromJson = opts.notesJson ? parseJsonObjectFlag(opts.notesJson, "--notes-json") : [];
210257
+ if (opts.notesJson && !Array.isArray(notesFromJson)) {
210258
+ throw new Error("--notes-json must be a JSON array");
210259
+ }
210260
+ const body = {
210261
+ title: opts.title,
210262
+ exampleNames: opts.exampleName
210263
+ };
210264
+ if (opts.instructions !== undefined)
210265
+ body.instructions = opts.instructions;
210266
+ if (opts.status !== undefined)
210267
+ body.status = opts.status;
210268
+ const focusFields = [...focusFromFlags, ...focusFromJson];
210269
+ if (focusFields.length > 0)
210270
+ body.focusFields = focusFields;
210271
+ const ignoredFields = [
210272
+ ...new Set((opts.ignore ?? []).map((path) => path.trim()).filter(Boolean))
210273
+ ];
210274
+ if (ignoredFields.length > 0)
210275
+ body.ignoredFields = ignoredFields;
210276
+ const itemNotes = mergeItemNotes([...notesFromFlags, ...notesFromJson]);
210277
+ if (itemNotes.length > 0)
210278
+ body.itemNotes = itemNotes;
210279
+ const payload = await client.post(datasetReviewRequestsPath(automationId), body);
210280
+ if (opts.json)
210281
+ return printJson2(payload);
210282
+ const created = payload;
210283
+ success(`Created review request ${ui.bold(created.id ?? "(unknown id)")}` + (created.status ? ` ${ui.dim(`(${created.status})`)}` : ""));
210284
+ }));
210285
+ addJsonFlag(withBaseUrl(reviewRequest.command("get <automation-id> <review-id>"))).description("Fetch one dataset review request with items, progress, focus fields, ignored fields, and events.").action(action(async (automationRef, reviewId, opts) => {
210286
+ const { client, automationId } = await resolveAutomation(automationRef, opts);
210287
+ const payload = await client.get(datasetReviewRequestsPath(automationId, reviewId));
210288
+ if (opts.json)
210289
+ return printJson2(payload);
210290
+ const detail = payload;
210291
+ console.log(table([
210292
+ { field: "id", value: detail.id },
210293
+ { field: "title", value: detail.title },
210294
+ { field: "status", value: detail.status },
210295
+ { field: "progress", value: formatProgress(detail.progress) },
210296
+ { field: "instructions", value: detail.instructions ?? "-" },
210297
+ {
210298
+ field: "focus",
210299
+ value: detail.focusFields?.map((field) => field.reason ? `${field.path} (${field.reason})` : field.path).join(", ") || "-"
210300
+ },
210301
+ {
210302
+ field: "ignored",
210303
+ value: detail.ignoredFields?.join(", ") || "-"
210304
+ }
210305
+ ], [
210306
+ { key: "field", header: "field" },
210307
+ { key: "value", header: "value" }
210308
+ ]));
210309
+ if (detail.items?.length) {
210310
+ console.log(table(detail.items.map((item) => ({
210311
+ exampleName: item.exampleName,
210312
+ status: item.status,
210313
+ inputDrifted: item.inputDrifted ? "yes" : "no"
210314
+ })), [
210315
+ { key: "exampleName", header: "example" },
210316
+ { key: "status", header: "status" },
210317
+ { key: "inputDrifted", header: "inputDrifted" }
210318
+ ]));
210319
+ }
210320
+ }));
210321
+ addJsonFlag(withBaseUrl(reviewRequest.command("update <automation-id> <review-id>"))).description("Update review metadata or lifecycle status. Set --status closed when review is finished.").option("--title <title>", "New review request title").option("--instructions <text>", "Note shown to the reviewer for the whole request").option("--focus <path>", "Replace focus paths (repeatable; use with --focus-reason / --focus-json)", collectRepeatable, []).option("--focus-reason <spec>", "Reason a focus path needs review, as path=reason (repeatable)", collectRepeatable, []).option("--focus-json <json>", "JSON array of { path, reason? } focus fields (replaces focus)").option("--ignore <path>", "Replace ignored paths (repeatable)", collectRepeatable).option("--status <draft|open|paused|closed>", "Lifecycle status").addHelpText("after", `
210322
+ Examples:
210323
+ $ eigenpal workflow dataset review-request update wf_abc123 dsr_... \\
210324
+ --status closed --json
210325
+ $ eigenpal workflow dataset review-request update wf_abc123 dsr_... \\
210326
+ --title "Q1 GT review (round 2)" --instructions "Re-check totals." --json
210327
+
210328
+ At least one of --title, --instructions, --focus/--focus-json, --ignore, or
210329
+ --status is required. Closing a request does not write expected outputs back
210330
+ to the dataset — reconcile manually after \`dataset pull\`.
210331
+ `).action(action(async (automationRef, reviewId, opts) => {
210332
+ const body = {};
210333
+ if (opts.title !== undefined)
210334
+ body.title = opts.title;
210335
+ if (opts.instructions !== undefined)
210336
+ body.instructions = opts.instructions;
210337
+ if (opts.status !== undefined)
210338
+ body.status = opts.status;
210339
+ const focusFlagCount = (opts.focus ?? []).length + (opts.focusReason ?? []).length;
210340
+ const hasFocusInput = focusFlagCount > 0 || opts.focusJson !== undefined;
210341
+ if (hasFocusInput) {
210342
+ const focusFromFlags = mergeFocusFields(opts.focus ?? [], (opts.focusReason ?? []).map(parseFocusReasonFlag));
210343
+ const focusFromJson = opts.focusJson ? parseJsonObjectFlag(opts.focusJson, "--focus-json") : [];
210344
+ if (opts.focusJson && !Array.isArray(focusFromJson)) {
210345
+ throw new Error("--focus-json must be a JSON array");
210346
+ }
210347
+ body.focusFields = [...focusFromFlags, ...focusFromJson];
210348
+ }
210349
+ if (opts.ignore !== undefined) {
210350
+ body.ignoredFields = [
210351
+ ...new Set(opts.ignore.map((path) => path.trim()).filter(Boolean))
210352
+ ];
210353
+ }
210354
+ if (Object.keys(body).length === 0) {
210355
+ throw new Error("pass at least one of --title, --instructions, --focus/--focus-json, --ignore, or --status");
210356
+ }
210357
+ const { client, automationId } = await resolveAutomation(automationRef, opts);
210358
+ const payload = await client.patch(datasetReviewRequestsPath(automationId, reviewId), body);
210359
+ if (opts.json)
210360
+ return printJson2(payload);
210361
+ const updated = payload;
210362
+ success(`Updated review request ${ui.bold(updated.id ?? reviewId)}` + (updated.status ? ` ${ui.dim(`(${updated.status})`)}` : ""));
210363
+ }));
210364
+ addJsonFlag(withBaseUrl(reviewRequest.command("items <automation-id> <review-id>"))).description("List snapshotted review items and their statuses.").option("--status <csv>", "Filter by item status (pending,approved,edited,rejected)").action(action(async (automationRef, reviewId, opts) => {
210365
+ const { client, automationId } = await resolveAutomation(automationRef, opts);
210366
+ const params = {};
210367
+ if (opts.status)
210368
+ params.status = opts.status;
210369
+ const payload = await client.get(datasetReviewRequestItemsPath(automationId, reviewId), params);
210370
+ if (opts.json)
210371
+ return printJson2(payload);
210372
+ const items = payload.items ?? [];
210373
+ console.log(table(items.map((item) => ({
210374
+ exampleName: item.exampleName,
210375
+ status: item.status,
210376
+ inputDrifted: item.inputDrifted ? "yes" : "no"
210377
+ })), [
210378
+ { key: "exampleName", header: "example" },
210379
+ { key: "status", header: "status" },
210380
+ { key: "inputDrifted", header: "inputDrifted" }
210381
+ ]));
210382
+ }));
210383
+ addJsonFlag(withBaseUrl(reviewRequest.command("events <automation-id> <review-id>"))).description("List review activity, including example and field notes.").action(action(async (automationRef, reviewId, opts) => {
210384
+ const { client, automationId } = await resolveAutomation(automationRef, opts);
210385
+ const payload = await client.get(datasetReviewRequestEventsPath(automationId, reviewId));
210386
+ if (opts.json)
210387
+ return printJson2(payload);
210388
+ const events = payload.events ?? [];
210389
+ console.log(table(events.map((event) => ({
210390
+ action: event.action,
210391
+ itemId: event.itemId ?? "-",
210392
+ field: event.diffSummary?.fieldPath ?? "-",
210393
+ comment: event.diffSummary?.comment ?? "-",
210394
+ createdAt: event.createdAt ? formatTimestamp(event.createdAt) : "-"
210395
+ })), [
210396
+ { key: "action", header: "action" },
210397
+ { key: "itemId", header: "item" },
210398
+ { key: "field", header: "field" },
210399
+ { key: "comment", header: "comment" },
210400
+ { key: "createdAt", header: "createdAt" }
210401
+ ]));
210402
+ }));
210403
+ addJsonFlag(withBaseUrl(reviewRequest.command("item <automation-id> <review-id> <item-id>"))).description("Approve, reject, reopen, comment, edit, or record a field-decision on one review item.").requiredOption("--action <approve|reject|reopen|comment|edit|field-decision>", "Item action").requiredOption("--expected-updated-at <iso>", "Item updatedAt the client last observed (optimistic concurrency)").option("--comment <text>", "Note stored on the item or field").option("--field-path <path>", "Dotted expected-output path for comment or field-decision").option("--decision <approved|rejected|null>", "Field decision for --action field-decision (null/clear removes it)").option("--clear", "Clear a field decision (sends decision: null)", false).option("--expected-json <json>", "Replacement expected JSON when --action edit").addHelpText("after", `
210404
+ Examples:
210405
+ $ eigenpal workflow dataset review-request item wf_abc123 dsr_... dsri_... \\
210406
+ --action field-decision --field-path vendor.iban --decision approved \\
210407
+ --expected-updated-at 2026-01-01T00:00:00.000Z --json
210408
+ $ eigenpal workflow dataset review-request item wf_abc123 dsr_... dsri_... \\
210409
+ --action field-decision --field-path vendor.iban --clear \\
210410
+ --expected-updated-at 2026-01-01T00:00:00.000Z --json
210411
+ $ eigenpal workflow dataset review-request item wf_abc123 dsr_... dsri_... \\
210412
+ --action reject --comment "wrong vendor" \\
210413
+ --expected-updated-at 2026-01-01T00:00:00.000Z --json
210414
+
210415
+ \`reject\` marks the example as not recommended for the dataset; it does not
210416
+ delete anything. \`--clear\` / \`--decision null\` removes a prior field
210417
+ decision (API: decision: null). Always pass \`--expected-updated-at\` from
210418
+ the item payload you last observed.
210419
+ `).action(action(async (automationRef, reviewId, itemId, opts) => {
210420
+ const { client, automationId } = await resolveAutomation(automationRef, opts);
210421
+ const body = buildReviewItemPatchBody(opts.action, {
210422
+ expectedUpdatedAt: opts.expectedUpdatedAt,
210423
+ comment: opts.comment,
210424
+ fieldPath: opts.fieldPath,
210425
+ decision: opts.decision,
210426
+ clear: opts.clear,
210427
+ expectedJson: opts.expectedJson
210428
+ });
210429
+ const payload = await client.patch(datasetReviewRequestItemsPath(automationId, reviewId, itemId), body);
210430
+ if (opts.json)
210431
+ return printJson2(payload);
210432
+ const item = payload.item;
210433
+ success(`Updated ${ui.bold(item?.exampleName ?? itemId)}` + (item?.status ? ` ${ui.dim(`(${item.status})`)}` : ""));
210434
+ }));
210435
+ }
210436
+
210040
210437
  // src/commands/agents/validation.ts
210041
210438
  init_src();
210042
210439
  import { existsSync as existsSync7, promises as fs2 } from "node:fs";
@@ -210342,7 +210739,7 @@ function validateValuesAgainstSchemaProps(data, schema, label) {
210342
210739
  }
210343
210740
  function validateValueAgainstSchema(value, schema, label) {
210344
210741
  try {
210345
- const validate = eigenpalAjv.compile(schema);
210742
+ const validate = getEigenpalAjv().compile(schema);
210346
210743
  if (validate(value))
210347
210744
  return [];
210348
210745
  return (validate.errors ?? []).map((err) => {
@@ -210377,6 +210774,11 @@ Layout
210377
210774
  (referenced via { "$file": "input/<path>" }), plus optional
210378
210775
  expected.json, expected/<file>, and meta.json.
210379
210776
  Run \`eigenpal agents dataset validate\` first to check locally.
210777
+
210778
+ After pushing examples for a new agent, request a dataset review before
210779
+ iterating to perfection: \`agents dataset review-request create <agent>
210780
+ --title ... --example-name ... --status open\`, poll \`.progress.complete\`,
210781
+ then close the request when review is finished.
210380
210782
  `).action(action(pushDataset));
210381
210783
  withBaseUrl(dataset.command("pull <agent-id-or-slug>")).description("Download an agent dataset as a .zip archive.").option("--out <path>", "Output .zip path (default: dataset.zip)", DATASET_DIR).action(action(pullDataset));
210382
210784
  addJsonFlag(dataset.command("validate [path]")).description("Validate a local dataset directory against the canonical examples/<name> layout. Defaults to ./dataset/.").option("--agent-dir <dir>", "Agent package directory with optional input/output schemas for extra value-level checks", ".").addHelpText("after", `
@@ -210394,6 +210796,10 @@ Layout (mirrors the server import rules)
210394
210796
  ` + ` workflow-only and rejected for agent datasets: agent runs are evaluated
210395
210797
  ` + ` only when they complete.
210396
210798
  `).action(action(validateDatasetCommand));
210799
+ registerDatasetReviewRequestCommands(dataset, async (agentId, opts) => {
210800
+ const client = buildClient2(opts);
210801
+ return { client, automationId: agentAutomationId(agentId) };
210802
+ });
210397
210803
  }
210398
210804
  async function listDataset(agentId, opts) {
210399
210805
  const client = buildClient2(opts);
@@ -247249,6 +247655,11 @@ Examples:
247249
247655
  \`--mode append\` is the default; \`--mode replace\` deletes every existing example
247250
247656
  + its files first and prompts a typed-slug confirmation in TTY (use \`--yes\` to
247251
247657
  skip in CI). Folder layout reference: \`packages/cli/src/skill/reference/dataset-format.md\`.
247658
+
247659
+ After pushing examples for a new workflow, request a dataset review before
247660
+ iterating to perfection: \`dataset review-request create <workflow-id>
247661
+ --title ... --example-name ... --status open\`, poll \`.progress.complete\`,
247662
+ then close the request when review is finished.
247252
247663
  `);
247253
247664
  addJsonFlag(withBaseUrl(pushCmd)).action(action(async (workflow, opts) => {
247254
247665
  if (opts.mode !== "append" && opts.mode !== "replace") {
@@ -247340,7 +247751,12 @@ skip in CI). Folder layout reference: \`packages/cli/src/skill/reference/dataset
247340
247751
  }));
247341
247752
  registerDatasetCrudCommands(dataset);
247342
247753
  registerDatasetValidateCommand(dataset);
247754
+ registerDatasetReviewRequestCommands(dataset, resolveAutomationForReviewRequest);
247343
247755
  }
247756
+ var resolveAutomationForReviewRequest = async (workflow, opts) => {
247757
+ const { client, workflowId } = await buildClientForWorkflow(workflow, opts);
247758
+ return { client, automationId: workflowId };
247759
+ };
247344
247760
  async function readJsonInput(literal, filePath, flagName) {
247345
247761
  if (literal !== undefined && filePath !== undefined) {
247346
247762
  throw new Error(`pass either --${flagName}-json or --${flagName}-file, not both`);