deepline 0.1.176 → 0.1.177

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.
@@ -607,17 +607,18 @@ var SDK_RELEASE = {
607
607
  // 0.1.111 ships dataset-native tool list getters and result row datasets.
608
608
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
609
609
  // fields shipped in 0.1.153.
610
- version: "0.1.176",
610
+ // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
611
+ version: "0.1.177",
611
612
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
612
613
  supportPolicy: {
613
- latest: "0.1.176",
614
+ latest: "0.1.177",
614
615
  minimumSupported: "0.1.53",
615
616
  deprecatedBelow: "0.1.53",
616
617
  commandMinimumSupported: [
617
618
  {
618
619
  command: "enrich",
619
- minimumSupported: "0.1.154",
620
- reason: "Older SDK CLI enrich generated stale play source; SDK CLI enrich 0.1.153 generated removed StepOptions fields in play source."
620
+ minimumSupported: "0.1.175",
621
+ reason: "Older SDK CLI enrich generated stale play source; SDK CLI enrich 0.1.153 generated removed StepOptions fields in play source; SDK CLI enrich before 0.1.175 could silently succeed on the empty-waterfall bug when a requested waterfall returned no values for selected rows."
621
622
  },
622
623
  {
623
624
  command: "plays",
@@ -7743,7 +7744,7 @@ import {
7743
7744
  writeFile as writeFile3
7744
7745
  } from "fs/promises";
7745
7746
  import { homedir as homedir7, tmpdir as tmpdir2 } from "os";
7746
- import { dirname as dirname7, join as join7, resolve as resolve9 } from "path";
7747
+ import { basename as basename2, dirname as dirname7, extname, join as join7, resolve as resolve9 } from "path";
7747
7748
 
7748
7749
  // src/cli/commands/play.ts
7749
7750
  import { createHash as createHash2 } from "crypto";
@@ -17866,14 +17867,39 @@ function enrichOutputJson(exportResult) {
17866
17867
  ...exportResult.partial ? { rows: exportResult.rows, partial: true } : {}
17867
17868
  };
17868
17869
  }
17870
+ function enrichReportJson(report) {
17871
+ if (!report) {
17872
+ return {};
17873
+ }
17874
+ return {
17875
+ ...report.jobs.length > 0 ? {
17876
+ failure_report: {
17877
+ path: report.path,
17878
+ jobs: report.jobs.length
17879
+ }
17880
+ } : {},
17881
+ ...report.issues.length > 0 ? {
17882
+ enrich_issues: {
17883
+ path: report.path,
17884
+ issues: report.issues.length,
17885
+ items: report.issues
17886
+ }
17887
+ } : {}
17888
+ };
17889
+ }
17869
17890
  async function buildEnrichFailureReport(input2) {
17891
+ const fallbackRowsInfo = input2.exportResult ? null : extractCanonicalRowsInfo(input2.status);
17870
17892
  return maybeEmitEnrichFailureReport({
17871
17893
  config: input2.config,
17872
17894
  rows: input2.rows,
17873
17895
  rowRange: input2.rowRange,
17874
17896
  client: input2.client,
17875
17897
  outputPath: input2.reportOutputPath ?? input2.exportResult?.path ?? input2.outputPath ?? null,
17876
- status: input2.status
17898
+ followUpOutputPath: input2.followUpOutputPath,
17899
+ status: input2.status,
17900
+ rowSetComplete: input2.exportResult ? !input2.exportResult.partial : Boolean(
17901
+ fallbackRowsInfo?.complete && input2.rows.length >= fallbackRowsInfo.totalRows
17902
+ )
17877
17903
  });
17878
17904
  }
17879
17905
  function recordField(value, key) {
@@ -18150,7 +18176,7 @@ function emitPlainBatchRunFailure(input2) {
18150
18176
  `Batch ${input2.chunkIndex + 1}/${input2.chunkCount} failed for rows ${input2.rows.rowStart}:${input2.rows.rowEnd}.
18151
18177
  `
18152
18178
  );
18153
- const runId = extractRunId(input2.status);
18179
+ const runId = extractRunId(input2.status) ?? firstCollectedStringField(input2.status, "runId");
18154
18180
  const error = firstCollectedStringField(input2.status, "error") ?? firstCollectedStringField(input2.status, "message");
18155
18181
  if (runId) {
18156
18182
  process.stderr.write(`Run id: ${runId}
@@ -18366,6 +18392,38 @@ function collectHardFailureAliasSpecs(config) {
18366
18392
  }
18367
18393
  return aliases;
18368
18394
  }
18395
+ function collectWaterfallAliasSpecs(config) {
18396
+ const aliases = [];
18397
+ const seen = /* @__PURE__ */ new Set();
18398
+ for (const command of config.commands) {
18399
+ if (!("with_waterfall" in command)) {
18400
+ continue;
18401
+ }
18402
+ const alias = command.with_waterfall.trim();
18403
+ if (!alias || seen.has(alias)) {
18404
+ continue;
18405
+ }
18406
+ const activeChildren = command.commands.filter(
18407
+ (child) => !("with_waterfall" in child) && !child.disabled
18408
+ );
18409
+ if (activeChildren.length === 0) {
18410
+ continue;
18411
+ }
18412
+ seen.add(alias);
18413
+ const childOperations = activeChildren.map((child) => child.operation?.trim() || child.tool.trim()).filter(Boolean);
18414
+ aliases.push({
18415
+ alias,
18416
+ childAliases: activeChildren.map((child) => child.alias).filter(Boolean),
18417
+ childToolsByAlias: new Map(
18418
+ activeChildren.map(
18419
+ (child) => [child.alias, child.tool.trim()]
18420
+ )
18421
+ ),
18422
+ operation: childOperations.length > 0 ? childOperations.join(", ") : void 0
18423
+ });
18424
+ }
18425
+ return aliases;
18426
+ }
18369
18427
  function hardFailureOperationByAlias(config) {
18370
18428
  const operations = /* @__PURE__ */ new Map();
18371
18429
  if (!config) {
@@ -18378,6 +18436,60 @@ function hardFailureOperationByAlias(config) {
18378
18436
  }
18379
18437
  return operations;
18380
18438
  }
18439
+ function isWaterfallResultMeaningful(value) {
18440
+ if (Array.isArray(value)) {
18441
+ return value.some(isWaterfallResultMeaningful);
18442
+ }
18443
+ if (value && typeof value === "object") {
18444
+ const record = value;
18445
+ const status = typeof record.status === "string" ? record.status.trim().toLowerCase() : "";
18446
+ if (status === "error" || status === "failed") {
18447
+ return false;
18448
+ }
18449
+ if (typeof record.error === "string" && record.error.trim()) {
18450
+ return false;
18451
+ }
18452
+ if (Object.prototype.hasOwnProperty.call(record, "matched_result")) {
18453
+ return isWaterfallResultMeaningful(record.matched_result);
18454
+ }
18455
+ for (const key of [
18456
+ "value",
18457
+ "email",
18458
+ "phone",
18459
+ "phone_number",
18460
+ "mobile_phone",
18461
+ "mobile_phone_number",
18462
+ "url",
18463
+ "domain",
18464
+ "name",
18465
+ "result",
18466
+ "data"
18467
+ ]) {
18468
+ if (isWaterfallResultMeaningful(record[key])) {
18469
+ return true;
18470
+ }
18471
+ }
18472
+ return Object.entries(record).some(
18473
+ ([key, entry]) => !ENRICH_FLATTENED_CONTROL_FIELDS.has(key) && isWaterfallResultMeaningful(entry)
18474
+ );
18475
+ }
18476
+ return isNonEmptyCsvCell(value);
18477
+ }
18478
+ function aliasHasMeaningfulResult(row, alias) {
18479
+ return aliasFailureCellCandidates(row, alias).some(
18480
+ isWaterfallResultMeaningful
18481
+ );
18482
+ }
18483
+ function stableRowSnapshot(value) {
18484
+ if (Array.isArray(value)) {
18485
+ return `[${value.map(stableRowSnapshot).join(",")}]`;
18486
+ }
18487
+ if (value && typeof value === "object") {
18488
+ const record = value;
18489
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableRowSnapshot(record[key])}`).join(",")}}`;
18490
+ }
18491
+ return JSON.stringify(value);
18492
+ }
18381
18493
  function cellFailureError(value) {
18382
18494
  const parsed = parseMaybeJsonObject(value);
18383
18495
  if (!isRecord7(parsed)) {
@@ -18523,6 +18635,112 @@ function collectEnrichFailureJobs(input2) {
18523
18635
  });
18524
18636
  return jobs;
18525
18637
  }
18638
+ function collectEmptyWaterfallIssues(input2) {
18639
+ const aliases = collectWaterfallAliasSpecs(input2.config);
18640
+ if (aliases.length === 0 || input2.rows.length === 0 || !input2.rowSetComplete) {
18641
+ return [];
18642
+ }
18643
+ const runId = extractRunId(input2.status) ?? firstCollectedStringField(input2.status, "runId");
18644
+ const followUpCommands = collectEnrichFollowUpCommands({
18645
+ status: input2.status,
18646
+ runId,
18647
+ outputPath: input2.followUpOutputPath ?? input2.outputPath
18648
+ });
18649
+ const issues = [];
18650
+ aliases.forEach((spec) => {
18651
+ if (waterfallExecutionSignal(input2.status, spec) === "all_condition_skipped") {
18652
+ return;
18653
+ }
18654
+ const logicalRows = /* @__PURE__ */ new Map();
18655
+ input2.rows.forEach((row, rowIndex) => {
18656
+ const sourceRowId = sourceRowIndexFromEnrichRow(
18657
+ row,
18658
+ 0,
18659
+ Number.MAX_SAFE_INTEGER
18660
+ );
18661
+ const rowKey = sourceRowId !== null ? `source:${sourceRowId}` : `snapshot:${stableRowSnapshot(row)}`;
18662
+ const existing = logicalRows.get(rowKey);
18663
+ const hasMeaningfulResult = aliasHasMeaningfulResult(row, spec.alias);
18664
+ logicalRows.set(rowKey, {
18665
+ hasMeaningfulResult: (existing?.hasMeaningfulResult ?? false) || hasMeaningfulResult
18666
+ });
18667
+ });
18668
+ const emptyRows = Array.from(logicalRows.values()).filter(
18669
+ (row) => !row.hasMeaningfulResult
18670
+ );
18671
+ if (emptyRows.length !== logicalRows.size) {
18672
+ return;
18673
+ }
18674
+ const selectedRows = logicalRows.size;
18675
+ const message = `Waterfall ${spec.alias} produced 0 values for ${selectedRows} selected row(s).`;
18676
+ issues.push({
18677
+ issue_id: `${spec.alias}-empty-waterfall`,
18678
+ kind: "empty_waterfall",
18679
+ severity: "needs_attention",
18680
+ column: spec.alias,
18681
+ selected_rows: selectedRows,
18682
+ rows_with_value: 0,
18683
+ rows_without_value: selectedRows,
18684
+ message,
18685
+ next_steps: enrichIssueNextSteps({ runId }),
18686
+ follow_up_commands: followUpCommands,
18687
+ ...spec.operation ? { operation: spec.operation } : {},
18688
+ ...runId ? { run_id: runId } : {}
18689
+ });
18690
+ });
18691
+ return issues;
18692
+ }
18693
+ function waterfallExecutionSignal(status, spec) {
18694
+ const childAliases = spec.childAliases ?? [];
18695
+ if (childAliases.length === 0) {
18696
+ return "unknown";
18697
+ }
18698
+ const summaries = collectColumnStats(status);
18699
+ let sawChildStats = false;
18700
+ let everyChildStatOnlyConditionSkipped = true;
18701
+ const childAliasesWithStats = /* @__PURE__ */ new Set();
18702
+ for (const childAlias of childAliases) {
18703
+ for (const stat2 of summaries.map((summary) => summary[childAlias]).filter(isRecord7)) {
18704
+ sawChildStats = true;
18705
+ childAliasesWithStats.add(childAlias);
18706
+ const execution = isRecord7(stat2.execution) ? stat2.execution : null;
18707
+ const executedSummary = parseExecutionSummary(
18708
+ execution?.["completed:executed"]
18709
+ );
18710
+ const skippedConditionSummary = parseExecutionSummary(
18711
+ execution?.["skipped:condition"]
18712
+ );
18713
+ const executedCount = executedSummary?.count ?? 0;
18714
+ const skippedConditionCount = skippedConditionSummary?.count ?? 0;
18715
+ const totalCount = executedSummary?.total ?? skippedConditionSummary?.total;
18716
+ const onlyConditionSkips = totalCount !== void 0 && skippedConditionCount >= totalCount && executedCount <= skippedConditionCount;
18717
+ if (executedCount > 0 && !onlyConditionSkips || numericFailedRowSignal(execution?.failed) > 0 || numericFailedRowSignal(stat2.failed) > 0 || numericFailedRowSignal(stat2.failedRows) > 0) {
18718
+ return "executed";
18719
+ }
18720
+ if (!onlyConditionSkips) {
18721
+ everyChildStatOnlyConditionSkipped = false;
18722
+ }
18723
+ }
18724
+ }
18725
+ if (sawChildStats && childAliasesWithStats.size === childAliases.length && everyChildStatOnlyConditionSkipped) {
18726
+ return "all_condition_skipped";
18727
+ }
18728
+ return "unknown";
18729
+ }
18730
+ function mergeEnrichFailureJobs(...jobGroups) {
18731
+ const merged = [];
18732
+ const seen = /* @__PURE__ */ new Set();
18733
+ for (const jobs of jobGroups) {
18734
+ for (const job of jobs) {
18735
+ if (seen.has(job.job_id)) {
18736
+ continue;
18737
+ }
18738
+ seen.add(job.job_id);
18739
+ merged.push(job);
18740
+ }
18741
+ }
18742
+ return merged;
18743
+ }
18526
18744
  function collectStatusFailureJobs(input2) {
18527
18745
  const aliases = collectHardFailureAliasSpecs(input2.config);
18528
18746
  if (aliases.length === 0) {
@@ -18616,14 +18834,23 @@ function collectCompleteStatusFailureMessages(input2) {
18616
18834
  return complete;
18617
18835
  }
18618
18836
  function parseExecutionCount(value) {
18837
+ return parseExecutionSummary(value)?.count ?? null;
18838
+ }
18839
+ function parseExecutionSummary(value) {
18619
18840
  if (typeof value === "number" && Number.isFinite(value)) {
18620
- return Math.max(0, Math.trunc(value));
18841
+ return { count: Math.max(0, Math.trunc(value)) };
18621
18842
  }
18622
18843
  if (typeof value !== "string") {
18623
18844
  return null;
18624
18845
  }
18625
- const match = value.trim().match(/^(\d+)\s*\//);
18626
- return match?.[1] ? Number.parseInt(match[1], 10) : null;
18846
+ const match = value.trim().match(/^(\d+)(?:\s*\/\s*(\d+))?/);
18847
+ if (!match?.[1]) {
18848
+ return null;
18849
+ }
18850
+ return {
18851
+ count: Number.parseInt(match[1], 10),
18852
+ ...match[2] ? { total: Number.parseInt(match[2], 10) } : {}
18853
+ };
18627
18854
  }
18628
18855
  function executionSummaryText(count, total) {
18629
18856
  const percent = total > 0 ? Math.round(count / total * 100) : 0;
@@ -18749,19 +18976,145 @@ function summarizeFailedJobError(value) {
18749
18976
  const text = String(value ?? "").trim();
18750
18977
  return text.length > 500 ? `${text.slice(0, 497)}...` : text;
18751
18978
  }
18979
+ function shellSingleQuote2(value) {
18980
+ return `'${value.replace(/'/g, `'\\''`)}'`;
18981
+ }
18982
+ function addEnrichFollowUpCommand(commands, seen, command) {
18983
+ const normalized = command.command.trim();
18984
+ if (!normalized || seen.has(normalized)) {
18985
+ return;
18986
+ }
18987
+ seen.add(normalized);
18988
+ commands.push({ ...command, command: normalized });
18989
+ }
18990
+ function datasetSelectorForEnrichCommand(record, fallbackPath) {
18991
+ const candidates = [record.path, record.tableNamespace, fallbackPath];
18992
+ for (const candidate of candidates) {
18993
+ if (typeof candidate === "string" && candidate.trim()) {
18994
+ return candidate.trim();
18995
+ }
18996
+ }
18997
+ return null;
18998
+ }
18999
+ function collectEnrichFollowUpCommands(input2) {
19000
+ const commands = [];
19001
+ const seen = /* @__PURE__ */ new Set();
19002
+ if (input2.runId) {
19003
+ addEnrichFollowUpCommand(commands, seen, {
19004
+ label: "inspect run",
19005
+ command: `deepline runs get ${input2.runId} --full --json`
19006
+ });
19007
+ }
19008
+ collectDatasetFollowUpCommands(input2.status, {
19009
+ runId: input2.runId,
19010
+ outputPath: input2.outputPath,
19011
+ commands,
19012
+ seen,
19013
+ path: "result",
19014
+ depth: 0
19015
+ });
19016
+ if (input2.runId && input2.outputPath && !commands.some((command) => command.label === "export enrich rows")) {
19017
+ addEnrichFollowUpCommand(commands, seen, {
19018
+ label: "export enrich rows",
19019
+ path: GENERATED_ENRICH_ROWS_TABLE_NAMESPACE,
19020
+ command: `deepline runs export ${input2.runId} --dataset ${GENERATED_ENRICH_ROWS_TABLE_NAMESPACE} --out ${shellSingleQuote2(
19021
+ resolve9(input2.outputPath)
19022
+ )}`
19023
+ });
19024
+ }
19025
+ return commands.slice(0, 8);
19026
+ }
19027
+ function sidecarEnrichRowsExportPath(outputPath) {
19028
+ const resolved = resolve9(outputPath);
19029
+ const ext = extname(resolved) || ".csv";
19030
+ const stem = basename2(resolved, ext);
19031
+ return join7(dirname7(resolved), `${stem}.deepline-enrich-rows${ext}`);
19032
+ }
19033
+ function collectDatasetFollowUpCommands(value, state) {
19034
+ if (state.depth > 12 || !value || typeof value !== "object" || state.commands.length >= 8) {
19035
+ return;
19036
+ }
19037
+ if (Array.isArray(value)) {
19038
+ value.slice(0, 50).forEach(
19039
+ (entry, index) => collectDatasetFollowUpCommands(entry, {
19040
+ ...state,
19041
+ path: `${state.path}.${index}`,
19042
+ depth: state.depth + 1
19043
+ })
19044
+ );
19045
+ return;
19046
+ }
19047
+ const record = value;
19048
+ const isDataset = record.kind === "dataset";
19049
+ if (isDataset) {
19050
+ const selector = datasetSelectorForEnrichCommand(record, state.path);
19051
+ const labelPath = typeof record.path === "string" && record.path.trim() ? record.path.trim() : selector ?? state.path;
19052
+ if (typeof record.queryDatasetCommand === "string") {
19053
+ addEnrichFollowUpCommand(state.commands, state.seen, {
19054
+ label: `query ${labelPath}`,
19055
+ path: labelPath,
19056
+ command: record.queryDatasetCommand
19057
+ });
19058
+ }
19059
+ const exportCommand = typeof record.slowExportAsCsvCommand === "string" ? record.slowExportAsCsvCommand : typeof record.fullExportCommand === "string" ? record.fullExportCommand : null;
19060
+ if (exportCommand) {
19061
+ addEnrichFollowUpCommand(state.commands, state.seen, {
19062
+ label: `export ${labelPath}`,
19063
+ path: labelPath,
19064
+ command: exportCommand
19065
+ });
19066
+ } else if (state.runId && state.outputPath && selector) {
19067
+ addEnrichFollowUpCommand(state.commands, state.seen, {
19068
+ label: selector === GENERATED_ENRICH_ROWS_TABLE_NAMESPACE ? "export enrich rows" : `export ${labelPath}`,
19069
+ path: selector,
19070
+ command: `deepline runs export ${state.runId} --dataset ${shellSingleQuote2(
19071
+ selector
19072
+ )} --out ${shellSingleQuote2(resolve9(state.outputPath))}`
19073
+ });
19074
+ }
19075
+ }
19076
+ for (const [key, child] of Object.entries(record)) {
19077
+ if (key === "preview" || key === "access") {
19078
+ continue;
19079
+ }
19080
+ collectDatasetFollowUpCommands(child, {
19081
+ ...state,
19082
+ path: `${state.path}.${key}`,
19083
+ depth: state.depth + 1
19084
+ });
19085
+ if (state.commands.length >= 8) {
19086
+ return;
19087
+ }
19088
+ }
19089
+ }
19090
+ function enrichIssueNextSteps(input2) {
19091
+ const steps = [
19092
+ "Inspect the run output and returned dataset handles.",
19093
+ "If runs get returns queryDatasetCommand or slowExportAsCsvCommand, run that command to fetch durable rows from the customer DB.",
19094
+ "For provider-backed waterfalls, execute the child provider tool on a sample row to confirm the provider returns the expected field."
19095
+ ];
19096
+ if (!input2.runId) {
19097
+ return steps;
19098
+ }
19099
+ return [
19100
+ `Inspect run output: deepline runs get ${input2.runId} --full --json`,
19101
+ ...steps.slice(1)
19102
+ ];
19103
+ }
18752
19104
  function formatFailureReportCommand(reportPath) {
18753
19105
  const quoted = reportPath.replace(/'/g, `'\\''`);
18754
19106
  return `Inspect failed jobs: jq -r '.jobs[] | [.job_id,.row_id,.col_index,.column,.status,.last_error] | @tsv' '${quoted}'`;
18755
19107
  }
18756
19108
  async function persistEnrichFailureReport(input2) {
18757
- if (input2.jobs.length === 0) {
19109
+ if (input2.jobs.length === 0 && input2.issues.length === 0) {
18758
19110
  return null;
18759
19111
  }
18760
19112
  const stateDir = join7(homedir7(), ".local", "deepline", "runtime", "state");
19113
+ const reportPrefix = input2.jobs.length > 0 ? "run-block-failures" : "enrich-issues";
18761
19114
  await mkdir3(stateDir, { recursive: true });
18762
19115
  const reportPath = join7(
18763
19116
  stateDir,
18764
- `run-block-failures-${Math.floor(Date.now() / 1e3)}-${process.pid}.json`
19117
+ `${reportPrefix}-${Math.floor(Date.now() / 1e3)}-${process.pid}.json`
18765
19118
  );
18766
19119
  const report = {
18767
19120
  generated_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -18776,6 +19129,7 @@ async function persistEnrichFailureReport(input2) {
18776
19129
  },
18777
19130
  jobs: input2.jobs,
18778
19131
  failed_jobs: input2.jobs,
19132
+ enrich_issues: input2.issues,
18779
19133
  canceled_jobs: [],
18780
19134
  pending_jobs: [],
18781
19135
  missing_job_ids: []
@@ -18788,48 +19142,100 @@ async function persistEnrichFailureReport(input2) {
18788
19142
  return reportPath;
18789
19143
  }
18790
19144
  async function maybeEmitEnrichFailureReport(input2) {
19145
+ const fallbackRowsInfo = extractCanonicalRowsInfo(input2.status);
19146
+ const rowSetComplete = input2.rowSetComplete ?? Boolean(
19147
+ fallbackRowsInfo?.complete && input2.rows.length >= fallbackRowsInfo.totalRows
19148
+ );
18791
19149
  const rowJobs = collectEnrichFailureJobs({
18792
19150
  config: input2.config,
18793
19151
  rows: input2.rows,
18794
19152
  rowStart: input2.rowRange.rowStart
18795
19153
  });
18796
- const jobs = rowJobs.length > 0 || input2.status === void 0 ? rowJobs : collectStatusFailureJobs({
19154
+ const enrichIssues = collectEmptyWaterfallIssues({
19155
+ config: input2.config,
19156
+ rows: input2.rows,
19157
+ status: input2.waterfallStatus ?? input2.status,
19158
+ outputPath: input2.outputPath,
19159
+ followUpOutputPath: input2.followUpOutputPath,
19160
+ rowSetComplete
19161
+ });
19162
+ const statusJobs = input2.status === void 0 || rowJobs.length > 0 ? [] : collectStatusFailureJobs({
18797
19163
  config: input2.config,
18798
19164
  status: input2.status,
18799
19165
  rowRange: input2.statusRowRange ?? input2.rowRange
18800
19166
  });
18801
- if (jobs.length === 0) {
19167
+ const jobs = rowJobs.length > 0 || statusJobs.length > 0 || input2.status === void 0 ? mergeEnrichFailureJobs(rowJobs, statusJobs) : [];
19168
+ if (jobs.length === 0 && enrichIssues.length === 0) {
18802
19169
  return null;
18803
19170
  }
18804
19171
  const reportPath = await persistEnrichFailureReport({
18805
19172
  jobs,
19173
+ issues: enrichIssues,
18806
19174
  apiUrl: input2.client.baseUrl,
18807
19175
  outputPath: input2.outputPath,
18808
19176
  rows: input2.rowRange
18809
19177
  });
18810
- process.stderr.write("Execution failed.\n");
18811
- process.stderr.write("Failure preview (up to 5 failed jobs):\n");
18812
- process.stderr.write("job_id row_id col_index column status error\n");
18813
- for (const job of jobs.slice(0, 5)) {
18814
- process.stderr.write(
18815
- `${job.job_id} ${job.row_id} ${job.col_index} ${job.column} ${job.status} ${summarizeFailedJobError(job.last_error)}
19178
+ if (jobs.length > 0) {
19179
+ process.stderr.write("Execution failed.\n");
19180
+ process.stderr.write("Failure preview (up to 5 failed jobs):\n");
19181
+ process.stderr.write("job_id row_id col_index column status error\n");
19182
+ for (const job of jobs.slice(0, 5)) {
19183
+ process.stderr.write(
19184
+ `${job.job_id} ${job.row_id} ${job.col_index} ${job.column} ${job.status} ${summarizeFailedJobError(job.last_error)}
18816
19185
  `
18817
- );
19186
+ );
19187
+ }
19188
+ if (jobs.length > 5) {
19189
+ process.stderr.write(`Showing 5 of ${jobs.length} failed jobs.
19190
+ `);
19191
+ }
18818
19192
  }
18819
- if (jobs.length > 5) {
18820
- process.stderr.write(`Showing 5 of ${jobs.length} failed jobs.
19193
+ if (enrichIssues.length > 0) {
19194
+ process.stderr.write("Enrichment needs attention.\n");
19195
+ process.stderr.write("Issue preview (up to 5 enrichment issues):\n");
19196
+ process.stderr.write(
19197
+ "issue_id column severity selected_rows rows_with_value message\n"
19198
+ );
19199
+ for (const issue of enrichIssues.slice(0, 5)) {
19200
+ process.stderr.write(
19201
+ `${issue.issue_id} ${issue.column} ${issue.severity} ${issue.selected_rows} ${issue.rows_with_value} ${issue.message}
19202
+ `
19203
+ );
19204
+ for (const step of issue.next_steps.slice(0, 2)) {
19205
+ process.stderr.write(`Next: ${step}
19206
+ `);
19207
+ }
19208
+ for (const command of issue.follow_up_commands.slice(0, 4)) {
19209
+ process.stderr.write(`Command: ${command.label}: ${command.command}
18821
19210
  `);
19211
+ }
19212
+ }
19213
+ if (enrichIssues.length > 5) {
19214
+ process.stderr.write(
19215
+ `Showing 5 of ${enrichIssues.length} enrichment issues.
19216
+ `
19217
+ );
19218
+ }
18822
19219
  }
18823
19220
  if (reportPath) {
18824
- process.stderr.write(`Full failure report: ${reportPath}
19221
+ if (jobs.length > 0) {
19222
+ process.stderr.write(`Full failure report: ${reportPath}
18825
19223
  `);
18826
- process.stderr.write(`${formatFailureReportCommand(reportPath)}
19224
+ process.stderr.write(`${formatFailureReportCommand(reportPath)}
18827
19225
  `);
18828
- process.stderr.write("Enrichment failed. See failure report above.\n");
18829
- return { path: reportPath, jobs };
19226
+ } else {
19227
+ process.stderr.write(`Full enrich report: ${reportPath}
19228
+ `);
19229
+ }
19230
+ process.stderr.write(
19231
+ jobs.length > 0 ? "Enrichment failed. See report above.\n" : "Enrichment produced no values for at least one requested waterfall. See issue preview above.\n"
19232
+ );
19233
+ return { path: reportPath, jobs, issues: enrichIssues };
18830
19234
  }
18831
- process.stderr.write("Enrichment failed.\n");
18832
- return { path: "", jobs };
19235
+ process.stderr.write(
19236
+ jobs.length > 0 ? "Enrichment failed.\n" : "Enrichment needs attention.\n"
19237
+ );
19238
+ return { path: "", jobs, issues: enrichIssues };
18833
19239
  }
18834
19240
  function mergePreferredColumns(preferredColumns, rows) {
18835
19241
  const columns = [];
@@ -19495,6 +19901,7 @@ function registerEnrichCommand(program) {
19495
19901
  const inPlaceFinalOutputPath = options.inPlace ? resolve9(inputCsv) : null;
19496
19902
  const inPlaceCommitOutputPath = options.inPlace ? (await lstat(inputCsv)).isSymbolicLink() ? await realpath(inputCsv) : inPlaceFinalOutputPath : null;
19497
19903
  const failureReportOutputPath = options.inPlace ? inPlaceFinalOutputPath : null;
19904
+ const enrichIssueFollowUpOutputPath = options.inPlace && inPlaceFinalOutputPath ? sidecarEnrichRowsExportPath(inPlaceFinalOutputPath) : null;
19498
19905
  let inPlaceCommitted = false;
19499
19906
  const commitInPlaceOutput = async (exportResult) => {
19500
19907
  if (!inPlaceTempOutputPath || !inPlaceCommitOutputPath || !inPlaceFinalOutputPath) {
@@ -19583,6 +19990,7 @@ function registerEnrichCommand(program) {
19583
19990
  }
19584
19991
  let workingMergeSourceCsvPath = sourceCsvPath;
19585
19992
  let lastStatus = null;
19993
+ const batchStatuses = [];
19586
19994
  let finalExportResult = null;
19587
19995
  let totalEnrichedRows = 0;
19588
19996
  const batchFailureRows = [];
@@ -19620,6 +20028,7 @@ function registerEnrichCommand(program) {
19620
20028
  suppressOpen: true
19621
20029
  });
19622
20030
  lastStatus = chunk.status;
20031
+ batchStatuses.push(chunk.status);
19623
20032
  if (chunk.captured.result !== 0) {
19624
20033
  if (chunk.exportResult) {
19625
20034
  finalExportResult = chunk.exportResult;
@@ -19639,7 +20048,10 @@ function registerEnrichCommand(program) {
19639
20048
  statusRowRange: chunkRows,
19640
20049
  client: client2,
19641
20050
  outputPath: failureReportOutputPath ?? finalExportResult?.path ?? outputPath,
19642
- status: chunk.status
20051
+ followUpOutputPath: enrichIssueFollowUpOutputPath,
20052
+ status: chunk.status,
20053
+ waterfallStatus: batchStatuses,
20054
+ ...finalExportResult ? { rowSetComplete: !finalExportResult.partial } : {}
19643
20055
  });
19644
20056
  const committedExportResult2 = finalExportResult ? await commitInPlaceOutput(finalExportResult) : null;
19645
20057
  if (options.json) {
@@ -19661,12 +20073,7 @@ function registerEnrichCommand(program) {
19661
20073
  partial: true
19662
20074
  } : {}
19663
20075
  } : null,
19664
- ...failureReport3 ? {
19665
- failure_report: {
19666
- path: failureReport3.path,
19667
- jobs: failureReport3.jobs.length
19668
- }
19669
- } : {}
20076
+ ...enrichReportJson(failureReport3)
19670
20077
  });
19671
20078
  } else {
19672
20079
  if (committedExportResult2) {
@@ -19705,7 +20112,10 @@ function registerEnrichCommand(program) {
19705
20112
  },
19706
20113
  client: client2,
19707
20114
  outputPath: failureReportOutputPath ?? finalExportResult?.path ?? outputPath,
19708
- status: lastStatus
20115
+ followUpOutputPath: enrichIssueFollowUpOutputPath,
20116
+ status: lastStatus,
20117
+ waterfallStatus: batchStatuses,
20118
+ ...finalExportResult ? { rowSetComplete: !finalExportResult.partial } : {}
19709
20119
  });
19710
20120
  finalExportResult = await commitInPlaceOutput(finalExportResult);
19711
20121
  if (options.json) {
@@ -19737,12 +20147,7 @@ function registerEnrichCommand(program) {
19737
20147
  enrichedRows: totalEnrichedRows,
19738
20148
  path: finalExportResult.path
19739
20149
  } : null,
19740
- ...failureReport2 ? {
19741
- failure_report: {
19742
- path: failureReport2.path,
19743
- jobs: failureReport2.jobs.length
19744
- }
19745
- } : {}
20150
+ ...enrichReportJson(failureReport2)
19746
20151
  });
19747
20152
  }
19748
20153
  if (failureReport2) {
@@ -19773,6 +20178,7 @@ function registerEnrichCommand(program) {
19773
20178
  exportResult: committedExportResult2,
19774
20179
  outputPath,
19775
20180
  reportOutputPath: failureReportOutputPath,
20181
+ followUpOutputPath: enrichIssueFollowUpOutputPath,
19776
20182
  status
19777
20183
  });
19778
20184
  if (options.json) {
@@ -19780,12 +20186,7 @@ function registerEnrichCommand(program) {
19780
20186
  ok: false,
19781
20187
  run: status,
19782
20188
  output: enrichOutputJson(committedExportResult2),
19783
- ...failureReport2 ? {
19784
- failure_report: {
19785
- path: failureReport2.path,
19786
- jobs: failureReport2.jobs.length
19787
- }
19788
- } : {}
20189
+ ...enrichReportJson(failureReport2)
19789
20190
  });
19790
20191
  }
19791
20192
  process.exitCode = failureReport2 ? EXIT_SERVER2 : captured.result;
@@ -19800,6 +20201,7 @@ function registerEnrichCommand(program) {
19800
20201
  exportResult,
19801
20202
  outputPath,
19802
20203
  reportOutputPath: failureReportOutputPath,
20204
+ followUpOutputPath: enrichIssueFollowUpOutputPath,
19803
20205
  status
19804
20206
  });
19805
20207
  const committedExportResult2 = await commitInPlaceOutput(exportResult);
@@ -19814,12 +20216,7 @@ function registerEnrichCommand(program) {
19814
20216
  ok: !failureReport2,
19815
20217
  run,
19816
20218
  output: enrichOutputJson(committedExportResult2),
19817
- ...failureReport2 ? {
19818
- failure_report: {
19819
- path: failureReport2.path,
19820
- jobs: failureReport2.jobs.length
19821
- }
19822
- } : {}
20219
+ ...enrichReportJson(failureReport2)
19823
20220
  });
19824
20221
  if (failureReport2) {
19825
20222
  process.exitCode = EXIT_SERVER2;
@@ -19832,7 +20229,9 @@ function registerEnrichCommand(program) {
19832
20229
  rowRange: rows,
19833
20230
  client: client2,
19834
20231
  outputPath: failureReportOutputPath ?? exportResult?.path ?? null,
19835
- status
20232
+ followUpOutputPath: enrichIssueFollowUpOutputPath,
20233
+ status,
20234
+ ...exportResult ? { rowSetComplete: !exportResult.partial } : {}
19836
20235
  });
19837
20236
  const committedExportResult = await commitInPlaceOutput(exportResult);
19838
20237
  if (committedExportResult) {
@@ -19917,7 +20316,7 @@ import {
19917
20316
  writeFileSync as writeFileSync10
19918
20317
  } from "fs";
19919
20318
  import { homedir as homedir8, platform } from "os";
19920
- import { basename as basename2, dirname as dirname8, join as join8, resolve as resolve10 } from "path";
20319
+ import { basename as basename3, dirname as dirname8, join as join8, resolve as resolve10 } from "path";
19921
20320
  import { gzipSync } from "zlib";
19922
20321
  import { randomUUID as randomUUID2 } from "crypto";
19923
20322
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -19936,7 +20335,7 @@ function homeDir() {
19936
20335
  function detectShellContext() {
19937
20336
  const shellPath = process.env.SHELL?.trim() || process.env.ComSpec?.trim() || process.env.COMSPEC?.trim() || "";
19938
20337
  return {
19939
- shell: shellPath ? basename2(shellPath).replace(/\.exe$/i, "") : "unknown",
20338
+ shell: shellPath ? basename3(shellPath).replace(/\.exe$/i, "") : "unknown",
19940
20339
  shell_path: shellPath || null,
19941
20340
  os: platform(),
19942
20341
  cwd: process.cwd()
@@ -20042,10 +20441,10 @@ function newestSessionFile(agent) {
20042
20441
  return newest;
20043
20442
  }
20044
20443
  function sessionIdFromClaudeFilePath(filePath) {
20045
- return basename2(filePath, ".jsonl");
20444
+ return basename3(filePath, ".jsonl");
20046
20445
  }
20047
20446
  function sessionIdFromCodexFilePath(filePath) {
20048
- const match = basename2(filePath, ".jsonl").match(UUID_IN_TEXT_RE);
20447
+ const match = basename3(filePath, ".jsonl").match(UUID_IN_TEXT_RE);
20049
20448
  return match?.[0] ?? null;
20050
20449
  }
20051
20450
  function readCodexSessionId(filePath) {
@@ -20333,19 +20732,19 @@ async function handleSessionsSend(options) {
20333
20732
  }
20334
20733
  const response2 = await uploadPayload("/api/v2/cli/send-session", {
20335
20734
  file: readFileSync8(filePath).toString("base64"),
20336
- filename: basename2(filePath)
20735
+ filename: basename3(filePath)
20337
20736
  });
20338
20737
  printCommandEnvelope(
20339
20738
  {
20340
20739
  ...response2,
20341
20740
  ok: true,
20342
- filename: basename2(filePath),
20741
+ filename: basename3(filePath),
20343
20742
  render: {
20344
20743
  sections: [
20345
20744
  {
20346
20745
  title: "sessions send",
20347
20746
  lines: [
20348
- `File '${basename2(filePath)}' uploaded to #internal-reports.`
20747
+ `File '${basename3(filePath)}' uploaded to #internal-reports.`
20349
20748
  ]
20350
20749
  }
20351
20750
  ]