@moda-ai/cli 1.13.9 → 1.13.10

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.
@@ -214,10 +214,18 @@ function hydrateArtifactBody(artifact, rootDir) {
214
214
  }
215
215
  const evidenceLine = firstEvidenceLineNumber(artifact.evidence);
216
216
  const evidencePath = firstEvidencePath(artifact);
217
- if (evidencePath && evidenceLine !== undefined) {
217
+ if (evidencePath) {
218
218
  const text = readRepoRelativeText(rootDir, evidencePath);
219
219
  if (text !== undefined) {
220
- return extractSnippetAroundLine(text, evidenceLine, artifact.type === 'tool' ? 30 : 80);
220
+ if (evidenceLine !== undefined) {
221
+ return extractSnippetAroundLine(text, evidenceLine, artifact.type === 'tool' ? 30 : 80);
222
+ }
223
+ // No line number (common for agent/hosted analyst citations): fall back to
224
+ // the whole evidence file so the body is still captured in full.
225
+ if (artifact.type === 'prompt' || isDedicatedToolFilePath(evidencePath))
226
+ return text;
227
+ if (artifact.type === 'tool')
228
+ return extractToolBodyFromText(text, artifact.name) ?? text;
221
229
  }
222
230
  }
223
231
  return undefined;
@@ -97,15 +97,34 @@ export async function runRemoteHarnessAnalyst(options) {
97
97
  });
98
98
  writeHumanProgress(context, `Moda Analyst (remote): analysis run ${runId} accepted; waiting for results...`);
99
99
  const final = await pollRemoteAnalyze({ ingestUrl, apiKey, runId, context });
100
- if (final.status !== 'completed' || !final.report) {
101
- const detail = final.status === 'failed'
102
- ? `Remote harness analysis failed: ${final.error ?? 'unknown error'}.`
103
- : `Remote harness analysis did not finish before the timeout (last status: ${final.status}).`;
104
- throw new CliInputError(detail, [
105
- `Retry \`moda harness analyze --remote\`${runId ? ` (run ${runId})` : ''}.`,
106
- 'Or run `moda harness analyze --analyst=local-scan` to analyze locally.',
107
- 'Increase MODA_HARNESS_REMOTE_TIMEOUT_MS if large repos need longer.',
108
- ].join('\n'));
100
+ // A run that returned no report at all is a hard failure (timeout, or the
101
+ // hosted agent crashed before producing anything) — nothing to recover.
102
+ if (!final.report) {
103
+ throw remoteAnalyzeHardFailure(final, runId);
104
+ }
105
+ // A `failed` run only qualifies for client-side recovery when the server tells
106
+ // us the failure was its own REPORT VALIDATION (it produced a report but the
107
+ // report-contract check failed). That is the one class the client can recover:
108
+ // the uploaded snapshot is truncated/sampled, so the hosted analyst commonly
109
+ // leaves prompt/tool `body` (or citation lines) empty, and the client — which
110
+ // has the full local repo — can backfill those source-dependent gaps and
111
+ // re-validate. Any other `failed` run (a crashed/aborted agent, which carries
112
+ // an `error` string and no structured validation verdict) stays a hard failure
113
+ // even if a partial report leaked through, so we never write or sync a report
114
+ // from a hosted run the server aborted for a reason the client cannot repair.
115
+ if (final.status === 'failed' && !serverFailedReportValidation(final)) {
116
+ throw remoteAnalyzeHardFailure(final, runId);
117
+ }
118
+ // The hosted analyst returned a report that failed SERVER-side validation —
119
+ // most often because it enumerated prompt/tool artifacts but left their `body`
120
+ // empty, or because the uploaded snapshot was truncated so the server never
121
+ // saw the source. The client has the full local repo, so `repairExternalAnalystReport`
122
+ // backfills those bodies from disk here and we re-validate locally before
123
+ // deciding the outcome (a report that stays invalid after repair still exits 1
124
+ // and is never synced). This lets `--remote` succeed even when the server
125
+ // could not complete the report.
126
+ if (final.status === 'failed') {
127
+ writeHumanProgress(context, 'Moda Analyst (remote): server validation incomplete; completing artifact bodies from local source...');
109
128
  }
110
129
  let report = repairExternalAnalystReport(normalizeHarnessReport(final.report), rootDir);
111
130
  if (snapshot.truncated) {
@@ -145,6 +164,33 @@ export async function runRemoteHarnessAnalyst(options) {
145
164
  });
146
165
  return { exitCode: validation.status === 'pass' ? 0 : 1 };
147
166
  }
167
+ /** Build the terminal error for a remote run that cannot be recovered client-side. */
168
+ function remoteAnalyzeHardFailure(final, runId) {
169
+ const detail = final.status === 'failed'
170
+ ? `Remote harness analysis failed: ${final.error ?? 'unknown error'}.`
171
+ : `Remote harness analysis did not finish before the timeout (last status: ${final.status}).`;
172
+ return new CliInputError(detail, [
173
+ `Retry \`moda harness analyze --remote\`${runId ? ` (run ${runId})` : ''}.`,
174
+ 'Or run `moda harness analyze --analyst=local-scan` to analyze locally.',
175
+ 'Increase MODA_HARNESS_REMOTE_TIMEOUT_MS if large repos need longer.',
176
+ ].join('\n'));
177
+ }
178
+ /**
179
+ * True when a `failed` hosted run failed specifically because its own report
180
+ * validation failed — the only server failure the client can recover. The hosted
181
+ * service attaches a structured validation verdict (`{ status: 'fail', ... }`)
182
+ * only when it produced a report that failed the report-contract check
183
+ * (`apps/moda-harness-analyze` server.ts); an operational failure (crashed agent)
184
+ * reports an `error` string with no validation object. Gating recovery on this
185
+ * verdict keeps crashed/aborted runs as hard failures, and the mandatory
186
+ * client-side re-validation still rejects any report that stays invalid after
187
+ * body/citation repair.
188
+ */
189
+ function serverFailedReportValidation(final) {
190
+ const { validation } = final;
191
+ return typeof validation === 'object' && validation !== null && !Array.isArray(validation)
192
+ && validation.status === 'fail';
193
+ }
148
194
  /** Map CLI flags to the optional server-side analyze options in the wire contract. */
149
195
  function buildRemoteAnalyzeOptions(flags) {
150
196
  const options = {};
package/dist/harness.js CHANGED
@@ -730,6 +730,10 @@ function parseHarnessReportCandidate(candidate) {
730
730
  export function repairExternalAnalystReport(report, rootDir) {
731
731
  const normalized = normalizeHarnessReport(report);
732
732
  repairCitationLines(normalized, rootDir);
733
+ // Agent/hosted analysts reliably enumerate prompt/tool artifacts but often omit
734
+ // the verbatim `body`. Backfill it from source the same way local-scan does so
735
+ // external reports satisfy the prompt/tool body requirement.
736
+ hydratePromptAndToolBodies(normalized.graph, rootDir);
733
737
  repairReportSummaryCounts(normalized);
734
738
  return normalized;
735
739
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moda-ai/cli",
3
- "version": "1.13.9",
3
+ "version": "1.13.10",
4
4
  "description": "CLI for Moda - AI agent analytics and observability",
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-07-08T05:27:07.614Z",
4
- "cli_version": "1.13.9",
3
+ "bundled_at": "2026-07-08T06:08:46.984Z",
4
+ "cli_version": "1.13.10",
5
5
  "skills": [
6
6
  {
7
7
  "id": "integration-node-anthropic",