@moda-ai/cli 1.55.10 → 1.55.11

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.
@@ -0,0 +1,1802 @@
1
+ import {
2
+ INTELLIGENCE_SCHEMA_VERSION,
3
+ asNumber,
4
+ asRecord,
5
+ asString,
6
+ frustrationAnchorIndex,
7
+ frustrationsFromEmotions,
8
+ shellWord
9
+ } from "./cli-4qe6n8am.js";
10
+ import {
11
+ renderMarkdownForTerminal,
12
+ shouldColorTerminalOutput,
13
+ stripAnsi,
14
+ terminalStyles
15
+ } from "./cli-97fnztpy.js";
16
+ import {
17
+ callControlAPI,
18
+ callDataAPI
19
+ } from "./cli-tmqd1f0m.js";
20
+ import {
21
+ apiKeyHeaders
22
+ } from "./cli-m16taf4z.js";
23
+ import {
24
+ CliAuthError,
25
+ CliInputError,
26
+ EXIT_DEGRADED,
27
+ EXIT_OK,
28
+ recordApiRequest,
29
+ resolveApiKey,
30
+ resolveModaBaseUrl,
31
+ safeWriteStderr
32
+ } from "./cli-429zt7g4.js";
33
+ import"./cli-0v6na3yp.js";
34
+
35
+ // src/production-intelligence.ts
36
+ import { existsSync, readFileSync } from "node:fs";
37
+ import { join } from "node:path";
38
+
39
+ // src/ask-stream.ts
40
+ class AskStreamError extends Error {
41
+ elapsedMs;
42
+ toolEvents;
43
+ accepted;
44
+ constructor(message) {
45
+ super(message);
46
+ this.name = "AskStreamError";
47
+ }
48
+ }
49
+ function canFallBackToBufferedAsk(error) {
50
+ return error instanceof AskStreamError && error.accepted !== true;
51
+ }
52
+ function askStreamInterruptedError(error) {
53
+ const secs = Math.round((error.elapsedMs ?? 0) / 1000);
54
+ return new Error(`The live ask stream dropped ${secs}s into a run the server had already accepted (${error.message}). ` + "Not re-running it, to avoid paying for a second agent run; the run may still complete server-side. " + 'Retry with `moda ask "<question>" --no-stream` in a minute (the buffered path has no live progress but survives drops).');
55
+ }
56
+
57
+ class SseParser {
58
+ buffer = "";
59
+ pendingCr = false;
60
+ push(chunk) {
61
+ let text = (this.pendingCr ? "\r" : "") + chunk;
62
+ this.pendingCr = false;
63
+ if (text.endsWith("\r")) {
64
+ this.pendingCr = true;
65
+ text = text.slice(0, -1);
66
+ }
67
+ this.buffer += text.replace(/\r\n/g, `
68
+ `).replace(/\r/g, `
69
+ `);
70
+ const payloads = [];
71
+ let boundary = this.buffer.indexOf(`
72
+
73
+ `);
74
+ while (boundary !== -1) {
75
+ const frame = this.buffer.slice(0, boundary);
76
+ this.buffer = this.buffer.slice(boundary + 2);
77
+ const payload = dataPayloadOf(frame);
78
+ if (payload !== null)
79
+ payloads.push(payload);
80
+ boundary = this.buffer.indexOf(`
81
+
82
+ `);
83
+ }
84
+ return payloads;
85
+ }
86
+ flush() {
87
+ const remaining = this.buffer + (this.pendingCr ? "\r" : "");
88
+ this.buffer = "";
89
+ this.pendingCr = false;
90
+ const payload = dataPayloadOf(remaining.replace(/\r\n/g, `
91
+ `).replace(/\r/g, `
92
+ `));
93
+ return payload !== null ? [payload] : [];
94
+ }
95
+ }
96
+ function dataPayloadOf(frame) {
97
+ const dataLines = [];
98
+ for (const line of frame.split(`
99
+ `)) {
100
+ if (line.startsWith("data:")) {
101
+ dataLines.push(line.slice(5).replace(/^ /, ""));
102
+ }
103
+ }
104
+ if (dataLines.length === 0)
105
+ return null;
106
+ const payload = dataLines.join(`
107
+ `);
108
+ return payload.trim().length > 0 ? payload : null;
109
+ }
110
+ var RENDERED_PHASES = new Set(["planning", "searching", "analyzing", "writing"]);
111
+ var IDLE_TICKER_THRESHOLD_MS = 1e4;
112
+ var IDLE_TICKER_MIN_UPDATE_MS = 15000;
113
+ var IDLE_TICKER_POLL_MS = 1000;
114
+
115
+ class IdleTicker {
116
+ write;
117
+ now;
118
+ startedAt;
119
+ lastEventAt;
120
+ lastPrintAt = 0;
121
+ timer;
122
+ constructor(write, now = Date.now) {
123
+ this.write = write;
124
+ this.now = now;
125
+ this.startedAt = this.now();
126
+ this.lastEventAt = this.startedAt;
127
+ }
128
+ start() {
129
+ if (this.timer)
130
+ return;
131
+ this.startedAt = this.now();
132
+ this.lastEventAt = this.startedAt;
133
+ this.timer = setInterval(() => this.poll(), IDLE_TICKER_POLL_MS);
134
+ this.timer.unref?.();
135
+ }
136
+ touch() {
137
+ this.lastEventAt = this.now();
138
+ }
139
+ stop() {
140
+ if (this.timer !== undefined)
141
+ clearInterval(this.timer);
142
+ this.timer = undefined;
143
+ }
144
+ poll() {
145
+ const at = this.now();
146
+ if (at - this.lastEventAt < IDLE_TICKER_THRESHOLD_MS)
147
+ return;
148
+ if (this.lastPrintAt !== 0 && at - this.lastPrintAt < IDLE_TICKER_MIN_UPDATE_MS)
149
+ return;
150
+ this.lastPrintAt = at;
151
+ const secs = Math.round((at - this.startedAt) / 1000);
152
+ try {
153
+ this.write(`… still working (${secs}s)`);
154
+ } catch {}
155
+ }
156
+ }
157
+
158
+ class AskStreamCollector {
159
+ onProgress;
160
+ answer = "";
161
+ confidence = "medium";
162
+ citations = [];
163
+ nextCommands = [];
164
+ elapsedMs;
165
+ errorMessage;
166
+ terminal = false;
167
+ lastPhase;
168
+ toolNames = new Map;
169
+ toolEvents = 0;
170
+ events = 0;
171
+ constructor(onProgress = () => {}) {
172
+ this.onProgress = onProgress;
173
+ }
174
+ handle(raw) {
175
+ if (this.terminal)
176
+ return;
177
+ const event = raw;
178
+ if (!event || typeof event !== "object" || typeof event.type !== "string") {
179
+ throw new AskStreamError("stream emitted a frame without an event type");
180
+ }
181
+ this.events += 1;
182
+ switch (event.type) {
183
+ case "phase": {
184
+ const phase = typeof event.phase === "string" ? event.phase : "";
185
+ if (RENDERED_PHASES.has(phase) && phase !== this.lastPhase) {
186
+ this.lastPhase = phase;
187
+ this.onProgress(`▸ ${phase}…`);
188
+ }
189
+ break;
190
+ }
191
+ case "thinking": {
192
+ const id = typeof event.id === "string" ? event.id : "";
193
+ if (id === "plan" || id.startsWith("gate-")) {
194
+ const delta = typeof event.delta === "string" ? event.delta : "";
195
+ if (delta.trim().length > 0)
196
+ this.onProgress(`◦ ${oneLine(delta)}`);
197
+ }
198
+ break;
199
+ }
200
+ case "tool_start": {
201
+ this.toolEvents += 1;
202
+ const tool = event.tool;
203
+ const id = typeof tool?.id === "string" ? tool.id : "";
204
+ const name = stripAnsi(typeof tool?.tool === "string" ? tool.tool : "tool");
205
+ const title = stripAnsi(typeof tool?.title === "string" ? tool.title : name);
206
+ if (id)
207
+ this.toolNames.set(id, name);
208
+ this.onProgress(`• ${name} — ${title}…`);
209
+ break;
210
+ }
211
+ case "tool_end": {
212
+ this.toolEvents += 1;
213
+ const id = typeof event.id === "string" ? event.id : "";
214
+ const name = this.toolNames.get(id) ?? "tool";
215
+ this.onProgress(`• ${name} — ${toolEndDetail(event)}`);
216
+ break;
217
+ }
218
+ case "sources": {
219
+ this.citations = Array.isArray(event.citations) ? event.citations.flatMap(normalizeCitation) : [];
220
+ break;
221
+ }
222
+ case "text": {
223
+ if (typeof event.delta === "string")
224
+ this.answer += event.delta;
225
+ break;
226
+ }
227
+ case "done": {
228
+ this.terminal = true;
229
+ this.confidence = normalizeStreamConfidence(event.confidence);
230
+ if (typeof event.elapsedMs === "number")
231
+ this.elapsedMs = event.elapsedMs;
232
+ this.nextCommands = Array.isArray(event.nextCommands) ? event.nextCommands.filter((item) => typeof item === "string") : [];
233
+ break;
234
+ }
235
+ case "error": {
236
+ this.terminal = true;
237
+ this.errorMessage = typeof event.message === "string" && event.message.trim().length > 0 ? event.message : "The analysis agent hit an error and could not complete the request.";
238
+ break;
239
+ }
240
+ default:
241
+ break;
242
+ }
243
+ }
244
+ isTerminal() {
245
+ return this.terminal;
246
+ }
247
+ get eventCount() {
248
+ return this.events;
249
+ }
250
+ get toolEventCount() {
251
+ return this.toolEvents;
252
+ }
253
+ finish() {
254
+ if (!this.terminal) {
255
+ throw new AskStreamError("stream ended before a terminal done/error event");
256
+ }
257
+ if (!this.errorMessage && this.answer.trim().length === 0) {
258
+ throw new AskStreamError("stream completed with an empty answer");
259
+ }
260
+ return {
261
+ answer: this.answer,
262
+ confidence: this.confidence,
263
+ citations: this.citations,
264
+ nextCommands: this.nextCommands,
265
+ ...this.elapsedMs !== undefined ? { elapsedMs: this.elapsedMs } : {},
266
+ ...this.errorMessage ? { errorMessage: this.errorMessage } : {}
267
+ };
268
+ }
269
+ }
270
+ var TOOL_CARD_DETAIL_MAX = 100;
271
+ function oneLine(text) {
272
+ const collapsed = stripAnsi(text).replace(/\s+/g, " ").trim();
273
+ return collapsed.length > TOOL_CARD_DETAIL_MAX ? `${collapsed.slice(0, TOOL_CARD_DETAIL_MAX - 1)}…` : collapsed;
274
+ }
275
+ function toolEndDetail(event) {
276
+ const summary = typeof event.summary === "string" ? event.summary : "";
277
+ const secs = typeof event.ms === "number" ? `${(event.ms / 1000).toFixed(1)}s` : undefined;
278
+ if (event.status === "error") {
279
+ const reason = typeof event.error === "string" && event.error ? event.error : summary || "failed";
280
+ return joinDetail(`failed: ${oneLine(reason)}`, secs);
281
+ }
282
+ const detail = summary || (typeof event.hits === "number" ? `${event.hits} hits` : typeof event.rows === "number" ? `${event.rows} rows` : "done");
283
+ return joinDetail(oneLine(detail), secs);
284
+ }
285
+ function joinDetail(detail, secs) {
286
+ return secs ? `${detail}, ${secs}` : detail;
287
+ }
288
+ function normalizeCitation(item) {
289
+ const record = item;
290
+ if (!record || typeof record !== "object")
291
+ return [];
292
+ const label = typeof record.label === "string" ? record.label : undefined;
293
+ const id = typeof record.id === "string" ? record.id : undefined;
294
+ if (!label && !id)
295
+ return [];
296
+ return [{
297
+ id: id ?? `ref-${typeof record.n === "number" ? record.n : 0}`,
298
+ n: typeof record.n === "number" ? record.n : 0,
299
+ label: label ?? id ?? "Cloud evidence",
300
+ source: typeof record.source === "string" ? record.source : "evidence",
301
+ ...typeof record.snippet === "string" ? { snippet: record.snippet } : {},
302
+ ...typeof record.href === "string" ? { href: record.href } : {}
303
+ }];
304
+ }
305
+ function normalizeStreamConfidence(value) {
306
+ return value === "high" || value === "medium" || value === "low" || value === "unknown" ? value : "medium";
307
+ }
308
+ async function consumeAskSse(chunks, collector, onEvent) {
309
+ const parser = new SseParser;
310
+ const decoder = new TextDecoder;
311
+ for await (const chunk of chunks) {
312
+ const text = typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
313
+ for (const payload of parser.push(text)) {
314
+ collector.handle(parsePayload(payload));
315
+ onEvent?.();
316
+ if (collector.isTerminal())
317
+ return collector.finish();
318
+ }
319
+ }
320
+ for (const payload of parser.flush()) {
321
+ collector.handle(parsePayload(payload));
322
+ onEvent?.();
323
+ if (collector.isTerminal())
324
+ return collector.finish();
325
+ }
326
+ return collector.finish();
327
+ }
328
+ function parsePayload(payload) {
329
+ try {
330
+ return JSON.parse(payload);
331
+ } catch {
332
+ throw new AskStreamError("stream emitted an unparseable frame");
333
+ }
334
+ }
335
+ var DEFAULT_STREAM_TIMEOUT_MS = 250000;
336
+ async function streamAsk(options) {
337
+ const profileOptions = options.profile ? { profile: options.profile } : {};
338
+ const url = `${resolveModaBaseUrl(profileOptions)}/api/cli/ask/stream`;
339
+ const controller = new AbortController;
340
+ const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? DEFAULT_STREAM_TIMEOUT_MS);
341
+ const startedAt = Date.now();
342
+ const onProgress = options.onProgress ?? (() => {});
343
+ const collector = new AskStreamCollector(onProgress);
344
+ const ticker = new IdleTicker(onProgress);
345
+ ticker.start();
346
+ let accepted = false;
347
+ try {
348
+ const fetchImpl = options.fetchImpl ?? fetch;
349
+ let response;
350
+ try {
351
+ recordApiRequest();
352
+ response = await fetchImpl(url, {
353
+ method: "POST",
354
+ headers: {
355
+ ...apiKeyHeaders(resolveApiKey(profileOptions) ?? "", profileOptions),
356
+ "Content-Type": "application/json",
357
+ Accept: "text/event-stream"
358
+ },
359
+ body: JSON.stringify({
360
+ question: options.question,
361
+ daysBack: options.daysBack,
362
+ profile: options.profile,
363
+ harness: options.harness
364
+ }),
365
+ signal: controller.signal
366
+ });
367
+ } catch (error) {
368
+ throw new AskStreamError(`stream request failed: ${error instanceof Error ? error.message : String(error)}`);
369
+ }
370
+ if (!response.ok) {
371
+ throw new AskStreamError(`stream endpoint returned HTTP ${response.status}`);
372
+ }
373
+ const contentType = response.headers.get("content-type") ?? "";
374
+ if (!contentType.includes("text/event-stream")) {
375
+ throw new AskStreamError(`stream endpoint returned unexpected content type '${contentType || "unknown"}'`);
376
+ }
377
+ accepted = true;
378
+ if (!response.body) {
379
+ throw new AskStreamError("stream response had no body");
380
+ }
381
+ try {
382
+ return await consumeAskSse(response.body, collector, () => ticker.touch());
383
+ } catch (error) {
384
+ if (error instanceof AskStreamError)
385
+ throw error;
386
+ throw new AskStreamError(`stream read failed: ${error instanceof Error ? error.message : String(error)}`);
387
+ }
388
+ } catch (error) {
389
+ if (error instanceof AskStreamError) {
390
+ if (error.elapsedMs === undefined)
391
+ error.elapsedMs = Date.now() - startedAt;
392
+ if (error.toolEvents === undefined)
393
+ error.toolEvents = collector.toolEventCount;
394
+ if (error.accepted === undefined)
395
+ error.accepted = accepted || collector.eventCount > 0;
396
+ }
397
+ throw error;
398
+ } finally {
399
+ ticker.stop();
400
+ clearTimeout(timeout);
401
+ }
402
+ }
403
+
404
+ // src/production-intelligence.ts
405
+ var INVESTIGATION_SCHEMA_VERSION = "production_investigation.v0.1";
406
+ var OVERVIEW_SCHEMA_VERSION = "production_overview.v0.1";
407
+ var ASK_SCHEMA_VERSION = "production_ask.v0.1";
408
+ var INVESTIGATION_PROBLEM_LIMIT = 3;
409
+ var INVESTIGATION_PROBLEM_SLOTS = INVESTIGATION_PROBLEM_LIMIT;
410
+ async function runProductionCommand(context) {
411
+ const daysBack = daysBackFlag(context.flags);
412
+ if (context.command === "overview") {
413
+ const briefing = await loadOverviewBriefing({ daysBack, cwd: context.cwd });
414
+ if (context.flags.raw === "true") {
415
+ context.output.writeData(briefing.rawOverview, {
416
+ summary: "Loaded raw Moda overview.",
417
+ nextCommands: ["moda overview", `moda investigate --days-back=${daysBack}`]
418
+ });
419
+ } else if (context.outputMode === "human") {
420
+ if (!context.quiet)
421
+ console.log(renderOverviewBriefingHuman(briefing));
422
+ } else {
423
+ context.output.writeData(briefing, overviewAgentOptions(briefing));
424
+ }
425
+ return { exitCode: 0 };
426
+ }
427
+ if (context.command === "ask") {
428
+ const question = context.positionals.join(" ").trim();
429
+ if (!question) {
430
+ throw new CliInputError("<question> is required", 'Usage: moda ask "what should I fix first?" [--days-back=N] [--no-stream]');
431
+ }
432
+ const result = await askModaForContext(context, question, daysBack);
433
+ if (context.outputMode === "human") {
434
+ if (!context.quiet)
435
+ console.log(renderAskHuman(result));
436
+ } else {
437
+ context.output.writeData(result.intelligence, {
438
+ ...askAgentOptions(result),
439
+ status: result.degraded ? "degraded" : "success"
440
+ });
441
+ }
442
+ return { exitCode: result.degraded ? EXIT_DEGRADED : EXIT_OK };
443
+ }
444
+ if (context.command === "failures") {
445
+ const investigation = await investigateProduction({
446
+ daysBack,
447
+ cwd: context.cwd,
448
+ failureOnly: true,
449
+ toolName: context.flags.tool
450
+ });
451
+ writeInvestigation(context, investigation);
452
+ return { exitCode: 0 };
453
+ }
454
+ if (context.command === "investigate") {
455
+ const investigation = await investigateProduction({
456
+ daysBack,
457
+ cwd: context.cwd,
458
+ conversationId: context.flags.trace ?? context.flags.conversation,
459
+ toolName: context.flags.tool
460
+ });
461
+ writeInvestigation(context, investigation);
462
+ return { exitCode: 0 };
463
+ }
464
+ throw new CliInputError(`Unknown production command '${context.command}'`);
465
+ }
466
+ async function loadOverviewBriefing(options) {
467
+ const endpoint = `/overview?days_back=${options.daysBack}`;
468
+ const overview = await callDataAPI(endpoint);
469
+ const hints = loadHarnessHints(options.cwd);
470
+ const evidenceRefs = [
471
+ {
472
+ id: "overview:data",
473
+ kind: "data_api",
474
+ label: `Overview for last ${options.daysBack} day(s)`,
475
+ endpoint,
476
+ value: overviewMetrics(overview)
477
+ }
478
+ ];
479
+ const findings = findingsFromOverview(overview, options.daysBack, hints, evidenceRefs);
480
+ const topFinding = findings[0] ?? null;
481
+ const conversations = numberAt(overview, ["conversations", "total"]);
482
+ const recent = recentActivity(overview)[0];
483
+ const dataFlowStatus = conversations > 0 || recent ? "flowing" : "quiet";
484
+ const nextCommand = topFinding ? `moda investigate --days-back=${options.daysBack}` : "moda doctor --online";
485
+ return {
486
+ schemaVersion: OVERVIEW_SCHEMA_VERSION,
487
+ generatedAt: new Date().toISOString(),
488
+ daysBack: options.daysBack,
489
+ status: topFinding ? "attention" : dataFlowStatus === "flowing" ? "healthy" : "quiet",
490
+ dataFlow: {
491
+ status: dataFlowStatus,
492
+ conversations,
493
+ ...recent ? { lastEvent: recent } : {}
494
+ },
495
+ harnessSync: localSyncBrief(options.cwd, ".moda/sync-state.json", "Harness graph"),
496
+ promptSync: localSyncBrief(options.cwd, ".moda/prompts.lock.json", "Prompt versions"),
497
+ skillSync: localSkillBrief(options.cwd),
498
+ metrics: overviewMetrics(overview),
499
+ topFinding,
500
+ evidenceRefs,
501
+ nextCommand,
502
+ rawOverview: overview
503
+ };
504
+ }
505
+ async function investigateProduction(options) {
506
+ const warnings = [];
507
+ const sourceStatus = {};
508
+ const hints = loadHarnessHints(options.cwd);
509
+ const overviewEndpoint = `/overview?days_back=${options.daysBack}`;
510
+ const toolEndpoint = options.toolName ? `/tool-failures/${encodeURIComponent(options.toolName)}?days_back=${options.daysBack}&limit=5` : `/tool-failures?days_back=${options.daysBack}`;
511
+ const frustrationsEndpoint = `/emotions?family=frustration&days_back=${options.daysBack}&limit=5${options.conversationId ? `&conversation_id=${encodeURIComponent(options.conversationId)}` : ""}`;
512
+ const problemsEndpoint = `/problems?days_back=${options.daysBack}&limit=${INVESTIGATION_PROBLEM_LIMIT}`;
513
+ const skipped = { data: null, status: "ok" };
514
+ const [overview, tools, frustrations, problems] = await Promise.all([
515
+ safeData(overviewEndpoint),
516
+ safeData(toolEndpoint),
517
+ options.failureOnly ? Promise.resolve(skipped) : safeData(frustrationsEndpoint),
518
+ options.failureOnly ? Promise.resolve(skipped) : safeData(problemsEndpoint)
519
+ ]);
520
+ const sources = options.failureOnly ? { overview, tools } : { overview, tools, frustrations, problems };
521
+ for (const [key, result] of Object.entries(sources)) {
522
+ sourceStatus[key] = result.status;
523
+ if (result.warning)
524
+ warnings.push(result.warning);
525
+ }
526
+ if (options.toolName && tools.status === "ok" && toolRows(tools.data, options.toolName).every((row) => row.count <= 0)) {
527
+ warnings.push(`No failures found for tool "${options.toolName}" in the last ${options.daysBack} day(s) — the tool may have no failures in this window, or the name may not exist in this tenant. ` + "Run `moda tool-failures` to list tools with recorded failures, or widen --days-back.");
528
+ }
529
+ if (options.conversationId) {
530
+ warnings.push(`Scoped to trace ${options.conversationId} for frustration evidence only; overview and tool failures are tenant-wide. ` + `Open the trace itself with \`moda audit ${options.conversationId}\`.`);
531
+ }
532
+ const evidenceRefs = [];
533
+ const findings = [];
534
+ addOverviewEvidence(overview.data, overviewEndpoint, evidenceRefs);
535
+ findings.push(...toolFailureFindings(tools.data, options.daysBack, hints, evidenceRefs, options.toolName));
536
+ if (!options.failureOnly) {
537
+ findings.push(...frustrationFindings(frustrations.data, options.daysBack, evidenceRefs));
538
+ findings.push(...problemFindings(problems.data, options.daysBack, evidenceRefs));
539
+ }
540
+ if (findings.length === 0) {
541
+ findings.push(insufficientDataFinding(overview.data, evidenceRefs));
542
+ }
543
+ findings.sort(compareFindings);
544
+ const boundedFindings = capFindings(findings, 8, INVESTIGATION_PROBLEM_SLOTS);
545
+ const nextCommands = nextCommandsForFindings(boundedFindings, options.daysBack, !options.failureOnly);
546
+ const summary = summarizeInvestigation(boundedFindings);
547
+ if (options.conversationId) {
548
+ summary.text = `${summary.text} (Only frustration evidence is scoped to trace ${options.conversationId}.)`;
549
+ }
550
+ return {
551
+ schemaVersion: INVESTIGATION_SCHEMA_VERSION,
552
+ generatedAt: new Date().toISOString(),
553
+ daysBack: options.daysBack,
554
+ scope: {
555
+ ...options.conversationId ? { conversationId: options.conversationId } : {},
556
+ ...options.toolName ? { toolName: options.toolName } : {},
557
+ ...options.question ? { question: options.question } : {}
558
+ },
559
+ summary,
560
+ findings: boundedFindings,
561
+ evidenceRefs: uniqueEvidenceRefs(evidenceRefs),
562
+ nextCommands,
563
+ warnings,
564
+ sourceStatus
565
+ };
566
+ }
567
+ async function askModa(options) {
568
+ let fallbackReason = "Moda Cloud ask endpoint returned no answer.";
569
+ try {
570
+ const response = await callControlAPI("/cli/ask", {
571
+ method: "POST",
572
+ body: JSON.stringify({
573
+ question: options.question,
574
+ daysBack: options.daysBack,
575
+ profile: options.profile,
576
+ harness: localHarnessSummary(options.cwd)
577
+ })
578
+ }, options.profile ? { profile: options.profile } : {}, {
579
+ timeoutMs: askTimeoutMs(),
580
+ retries: 0,
581
+ retryOnceOnNetworkError: true,
582
+ onNetworkRetry: options.onNetworkRetry
583
+ });
584
+ const cloud = normalizeCloudAsk(response, options.question);
585
+ if (cloud)
586
+ return cloud;
587
+ fallbackReason = "Moda Cloud ask endpoint returned an empty answer.";
588
+ } catch (error) {
589
+ fallbackReason = `Moda Cloud ask endpoint unavailable: ${error instanceof Error ? error.message : String(error)}`;
590
+ }
591
+ const investigation = await investigateProduction({
592
+ daysBack: options.daysBack,
593
+ cwd: options.cwd,
594
+ question: options.question
595
+ });
596
+ const top = investigation.findings[0];
597
+ const answer = top ? `Moda Cloud intelligence is not available, so I synthesized this from production evidence. The strongest signal is: ${top.title}. ${top.summary}` : "Moda Cloud intelligence is not available, and the local Data API evidence did not contain a strong production signal.";
598
+ const confidence = capFallbackConfidence(investigation.summary.confidence);
599
+ const evidenceRefs = investigation.evidenceRefs.slice(0, 12);
600
+ const findings = investigation.findings.slice(0, 5);
601
+ const nextCommands = investigation.nextCommands;
602
+ const warnings = [
603
+ "Cloud ask endpoint unavailable; synthesized answer from local Data API evidence.",
604
+ fallbackReason,
605
+ ...investigation.warnings
606
+ ];
607
+ return {
608
+ schemaVersion: ASK_SCHEMA_VERSION,
609
+ generatedAt: new Date().toISOString(),
610
+ source: "local_fallback",
611
+ degraded: true,
612
+ fallbackReason,
613
+ question: options.question,
614
+ answer,
615
+ confidence,
616
+ evidenceRefs,
617
+ findings,
618
+ nextCommands,
619
+ warnings,
620
+ intelligence: synthesizeIntelligenceV1({
621
+ question: options.question,
622
+ answer,
623
+ confidence,
624
+ evidenceRefs,
625
+ nextCommands,
626
+ warnings,
627
+ source: "local_fallback",
628
+ degraded: true,
629
+ findings
630
+ })
631
+ };
632
+ }
633
+ function shouldStreamAsk(context, isTTY = process.stdout.isTTY === true) {
634
+ if (context.outputMode !== "human" || context.quiet)
635
+ return false;
636
+ if ("no-stream" in context.flags)
637
+ return false;
638
+ if (context.flags.stream === "false")
639
+ return false;
640
+ return isTTY;
641
+ }
642
+ async function askModaForContext(context, question, daysBack) {
643
+ if (shouldStreamAsk(context)) {
644
+ const progressStyle = terminalStyles(shouldColorTerminalOutput({ isTTY: process.stderr.isTTY === true }));
645
+ const dimmed = (line) => line.startsWith("•") || line.startsWith("◦") || line.startsWith("…");
646
+ try {
647
+ const outcome = await streamAsk({
648
+ question,
649
+ daysBack,
650
+ profile: context.profile,
651
+ harness: localHarnessSummary(context.cwd),
652
+ timeoutMs: askTimeoutMs(),
653
+ onProgress: (line) => safeWriteStderr(`${dimmed(line) ? progressStyle.dim(line) : line}
654
+ `)
655
+ });
656
+ return askResultFromStreamOutcome(outcome, question);
657
+ } catch (error) {
658
+ if (error instanceof CliAuthError)
659
+ throw error;
660
+ if (error instanceof AskStreamError && !canFallBackToBufferedAsk(error)) {
661
+ throw askStreamInterruptedError(error);
662
+ }
663
+ if (context.verbose) {
664
+ safeWriteStderr(`[moda] live progress unavailable (${error instanceof Error ? error.message : String(error)}); using buffered ask.
665
+ `);
666
+ }
667
+ }
668
+ }
669
+ return askModa({
670
+ question,
671
+ daysBack,
672
+ cwd: context.cwd,
673
+ profile: context.profile,
674
+ onNetworkRetry: context.verbose ? () => safeWriteStderr(`[moda] network hiccup reaching Moda Cloud; retrying the buffered ask once…
675
+ `) : undefined
676
+ });
677
+ }
678
+ function askResultFromStreamOutcome(outcome, question) {
679
+ const answer = outcome.errorMessage ?? outcome.answer;
680
+ const confidence = outcome.errorMessage ? "unknown" : normalizeConfidence(outcome.confidence);
681
+ const evidenceRefs = outcome.errorMessage ? [] : outcome.citations.map((citation) => ({
682
+ id: citation.id,
683
+ kind: evidenceKind(citation.source),
684
+ label: citation.label,
685
+ href: citation.href
686
+ }));
687
+ const nextCommands = outcome.errorMessage ? [] : outcome.nextCommands;
688
+ const warnings = outcome.errorMessage ? ["The analysis agent failed; returning a generic message."] : [];
689
+ return {
690
+ schemaVersion: ASK_SCHEMA_VERSION,
691
+ generatedAt: new Date().toISOString(),
692
+ source: "cloud",
693
+ degraded: false,
694
+ question,
695
+ answer,
696
+ confidence,
697
+ evidenceRefs,
698
+ findings: [],
699
+ nextCommands,
700
+ warnings,
701
+ intelligence: synthesizeIntelligenceV1({
702
+ question,
703
+ answer,
704
+ confidence,
705
+ evidenceRefs,
706
+ nextCommands,
707
+ warnings,
708
+ source: "cloud",
709
+ degraded: false
710
+ })
711
+ };
712
+ }
713
+ function askTimeoutMs() {
714
+ const raw = Number(process.env.MODA_ASK_TIMEOUT_MS);
715
+ return Number.isFinite(raw) && raw > 0 ? raw : 250000;
716
+ }
717
+ function writeInvestigation(context, investigation) {
718
+ if (context.outputMode === "human") {
719
+ if (!context.quiet)
720
+ console.log(renderInvestigationHuman(investigation));
721
+ return;
722
+ }
723
+ context.output.writeData(investigation, investigationAgentOptions(investigation));
724
+ }
725
+ async function safeData(endpoint) {
726
+ try {
727
+ return { data: await callDataAPI(endpoint), status: "ok" };
728
+ } catch (error) {
729
+ return {
730
+ data: null,
731
+ status: "unavailable",
732
+ warning: `Could not load ${endpoint}: ${error instanceof Error ? error.message : String(error)}`
733
+ };
734
+ }
735
+ }
736
+ function findingsFromOverview(overview, daysBack, hints, evidenceRefs) {
737
+ const findings = [];
738
+ const toolFailureTotal = numberAt(overview, ["tool_failures", "total"]);
739
+ if (toolFailureTotal > 0) {
740
+ const conversations = numberAt(overview, ["tool_failures", "conversations"]);
741
+ const evidenceId = "overview:tool_failures";
742
+ evidenceRefs.push({
743
+ id: evidenceId,
744
+ kind: "data_api",
745
+ label: `${toolFailureTotal} tool failure(s) across ${conversations} trace(s)`,
746
+ endpoint: `/overview?days_back=${daysBack}`,
747
+ value: valueAt(overview, ["tool_failures"])
748
+ });
749
+ findings.push({
750
+ id: "finding_overview_tool_failures",
751
+ kind: "tool_failure",
752
+ title: `${toolFailureTotal} tool failure(s) need investigation`,
753
+ severity: severityForCount(toolFailureTotal, 10, 3),
754
+ confidence: "medium",
755
+ impactScore: 80 + Math.min(toolFailureTotal, 20),
756
+ rankReason: "Tool failures directly block agent tasks and have explicit production evidence.",
757
+ summary: `Moda saw ${toolFailureTotal} failed tool call(s) in the last ${daysBack} day(s).`,
758
+ evidenceRefIds: [evidenceId],
759
+ likelyLocations: defaultLikelyLocations(hints, "tool"),
760
+ recommendedActions: [{
761
+ id: "action_investigate_tools",
762
+ title: "Open the ranked production investigation.",
763
+ rationale: "The overview only has aggregate counts; investigation pulls examples and likely file paths.",
764
+ command: `moda investigate --days-back=${daysBack}`,
765
+ mutability: "read",
766
+ requiresApproval: false
767
+ }]
768
+ });
769
+ }
770
+ const frustrationRate = numberAt(overview, ["frustrations", "rate_pct"]);
771
+ const frustrated = numberAt(overview, ["frustrations", "frustrated"]);
772
+ if (frustrated > 0 || frustrationRate >= 10) {
773
+ const evidenceId = "overview:frustrations";
774
+ evidenceRefs.push({
775
+ id: evidenceId,
776
+ kind: "data_api",
777
+ label: `${frustrationRate}% frustration rate`,
778
+ endpoint: `/overview?days_back=${daysBack}`,
779
+ value: valueAt(overview, ["frustrations"])
780
+ });
781
+ findings.push({
782
+ id: "finding_overview_frustration",
783
+ kind: "frustration",
784
+ title: `${frustrationRate}% user frustration rate`,
785
+ severity: frustrationRate >= 25 ? "high" : "medium",
786
+ confidence: "medium",
787
+ impactScore: 65 + Math.min(frustrationRate, 30),
788
+ rankReason: "Frustration indicates users are getting stuck even when runs may not hard-fail.",
789
+ summary: `Moda classified ${frustrated} frustrated trace(s) in the last ${daysBack} day(s).`,
790
+ evidenceRefIds: [evidenceId],
791
+ likelyLocations: defaultLikelyLocations(hints, "prompt"),
792
+ recommendedActions: [{
793
+ id: "action_investigate_frustration",
794
+ title: "Inspect frustration anchors.",
795
+ rationale: "Frustration anchors show the user turn where the experience degraded.",
796
+ command: `moda frustrations --days-back=${daysBack} --include-window`,
797
+ mutability: "read",
798
+ requiresApproval: false
799
+ }]
800
+ });
801
+ }
802
+ return findings.sort(compareFindings);
803
+ }
804
+ function toolFailureFindings(data, daysBack, hints, evidenceRefs, scopedTool) {
805
+ const tools = toolRows(data, scopedTool);
806
+ return tools.flatMap((tool) => {
807
+ const name = tool.name;
808
+ if (!name)
809
+ return [];
810
+ const count = tool.count;
811
+ if (count <= 0)
812
+ return [];
813
+ const evidenceId = `tool:${name}`;
814
+ const subtype = tool.subtype ? ` (${tool.subtype})` : "";
815
+ evidenceRefs.push({
816
+ id: evidenceId,
817
+ kind: "tool_failure",
818
+ label: `${name}: ${count} failure(s)${subtype}`,
819
+ endpoint: scopedTool ? `/tool-failures/${encodeURIComponent(name)}?days_back=${daysBack}` : `/tool-failures?days_back=${daysBack}`,
820
+ toolName: name,
821
+ value: tool.raw
822
+ });
823
+ const location = likelyToolLocation(name, hints);
824
+ if (location?.path) {
825
+ evidenceRefs.push({
826
+ id: `harness:tool:${name}`,
827
+ kind: "harness",
828
+ label: `Likely tool definition for ${name}`,
829
+ path: location.path,
830
+ toolName: name
831
+ });
832
+ }
833
+ const conversations = tool.conversations > 0 ? tool.conversations : count;
834
+ return [{
835
+ id: `finding_tool_failure_${slug(name)}`,
836
+ kind: "tool_failure",
837
+ title: `${name} is failing in production`,
838
+ severity: severityForCount(count, 10, 3),
839
+ confidence: "high",
840
+ impactScore: 90 + Math.min(count, 25),
841
+ rankReason: `${count} failed call(s) across ${conversations} trace(s), with direct tool failure evidence.`,
842
+ summary: `${name} produced ${count} failed call(s) in the last ${daysBack} day(s).${tool.subtype ? ` The dominant subtype is ${tool.subtype}.` : ""}`,
843
+ evidenceRefIds: location?.path ? [evidenceId, `harness:tool:${name}`] : [evidenceId],
844
+ likelyLocations: [location ?? unknownLocation("tool", `No local definition matched ${name}.`)],
845
+ recommendedActions: [{
846
+ id: `action_tool_detail_${slug(name)}`,
847
+ title: `Inspect ${name} failure examples.`,
848
+ rationale: "Examples include trace anchors and error subtypes.",
849
+ command: `moda tool-failure-detail ${name} --days-back=${daysBack} --include-window`,
850
+ mutability: "read",
851
+ requiresApproval: false
852
+ }]
853
+ }];
854
+ });
855
+ }
856
+ function frustrationFindings(data, daysBack, evidenceRefs) {
857
+ data = frustrationsFromEmotions(asRecord(data) ?? {});
858
+ const summary = asRecord(valueAt(data, ["summary"]));
859
+ const frustrations = Array.isArray(valueAt(data, ["frustrations"])) ? valueAt(data, ["frustrations"]) : [];
860
+ const count = numberValue(summary?.frustrated_count) || frustrations.length;
861
+ const atRisk = numberValue(summary?.at_risk_count);
862
+ if (count <= 0 && atRisk <= 0 && frustrations.length === 0)
863
+ return [];
864
+ const first = asRecord(frustrations[0]);
865
+ const conversationId = asString(first?.conversation_id);
866
+ const quote = firstQuote(first);
867
+ const anchorIndex = frustrationAnchorIndex(first);
868
+ const evidenceId = conversationId ? `frustration:${conversationId}` : "frustration:summary";
869
+ evidenceRefs.push({
870
+ id: evidenceId,
871
+ kind: "frustration",
872
+ label: quote ? `Frustration anchor: "${quote}"` : `${count} frustrated trace(s)`,
873
+ endpoint: `/emotions?family=frustration&days_back=${daysBack}&limit=5`,
874
+ ...conversationId ? { conversationId } : {},
875
+ value: first ?? summary ?? data
876
+ });
877
+ return [{
878
+ id: "finding_frustration_rate",
879
+ kind: "frustration",
880
+ title: count > 0 ? `${count} frustrated trace(s)` : `${atRisk} at-risk trace(s)`,
881
+ severity: count >= 10 ? "high" : "medium",
882
+ confidence: first ? "high" : "medium",
883
+ impactScore: 70 + Math.min(count * 3 + atRisk, 25),
884
+ rankReason: "User frustration is a product-quality signal even when the agent technically completes.",
885
+ summary: first?.primary_cause ? `Primary cause: ${String(first.primary_cause)}.` : `Moda found frustration or risk in the last ${daysBack} day(s).`,
886
+ evidenceRefIds: [evidenceId],
887
+ likelyLocations: [unknownLocation("prompt", "Prompt or policy issue likely; inspect the trace window.")],
888
+ recommendedActions: [{
889
+ id: "action_frustration_window",
890
+ title: "Read the frustration window.",
891
+ rationale: "The user quote and nearby turns reveal what confused or blocked the user.",
892
+ command: conversationId ? `moda context ${shellWord(conversationId)}${anchorIndex !== null ? ` --msg-index=${anchorIndex}` : ""} --window=3` : `moda frustrations --days-back=${daysBack} --include-window`,
893
+ mutability: "read",
894
+ requiresApproval: false
895
+ }]
896
+ }];
897
+ }
898
+ function problemRows(data) {
899
+ const record = asRecord(data);
900
+ const rows = [];
901
+ const push = (items, trust) => {
902
+ if (!Array.isArray(items))
903
+ return;
904
+ for (const item of items) {
905
+ const row = asRecord(item);
906
+ const id = asString(row?.problem_id);
907
+ if (!row || !id)
908
+ continue;
909
+ const lifecycle = asString(row.lifecycle) ?? "open";
910
+ if (lifecycle === "resolved")
911
+ continue;
912
+ rows.push({
913
+ id,
914
+ name: asString(row.display_name) || id,
915
+ cause: asString(row.cause_statement) || asString(row.display_description) || undefined,
916
+ lifecycle,
917
+ trust,
918
+ verification: asString(row.verification_status) ?? undefined,
919
+ affectedConversations: numberValue(row.affected_conversations) || numberValue(asRecord(row.evidence_support)?.attribution_count),
920
+ raw: row
921
+ });
922
+ }
923
+ };
924
+ push(record?.problems, "trusted");
925
+ push(record?.untrusted, "untrusted");
926
+ push(record?.emerging, "emerging");
927
+ return rows.slice(0, INVESTIGATION_PROBLEM_LIMIT);
928
+ }
929
+ function problemFindings(data, daysBack, evidenceRefs) {
930
+ return problemRows(data).map((problem, index) => {
931
+ const evidenceId = `problem:${problem.id}`;
932
+ const trusted = problem.trust === "trusted";
933
+ const trustNote = trusted ? "verified" : problem.trust === "emerging" ? "emerging, not yet ranked" : `untrusted (${problem.verification ?? "unverified"})`;
934
+ evidenceRefs.push({
935
+ id: evidenceId,
936
+ kind: "problem",
937
+ label: `Problem "${problem.name}" (${trustNote})`,
938
+ endpoint: `/problems?days_back=${daysBack}&limit=${INVESTIGATION_PROBLEM_LIMIT}`,
939
+ value: problem.raw
940
+ });
941
+ const affected = problem.affectedConversations;
942
+ return {
943
+ id: `finding_problem_${slug(problem.id)}`,
944
+ kind: "problem",
945
+ title: `Problem: ${problem.name}`,
946
+ severity: trusted ? affected >= 10 ? "critical" : "high" : "medium",
947
+ confidence: trusted ? "high" : "low",
948
+ impactScore: (trusted ? 100 : 60) + Math.min(affected, 25) - index,
949
+ rankReason: trusted ? `Rank #${index + 1} verified Problem in \`moda problems\`${affected > 0 ? `, affecting ${affected} trace(s)` : ""}.` : `Listed by \`moda problems\` as ${trustNote}; treat the cause as a hypothesis.`,
950
+ summary: problem.cause ? `${problem.cause}${problem.lifecycle !== "open" ? ` (${problem.lifecycle})` : ""}` : `Problem ${problem.id} is ${problem.lifecycle} in the last ${daysBack} day(s).`,
951
+ evidenceRefIds: [evidenceId],
952
+ likelyLocations: [],
953
+ recommendedActions: [{
954
+ id: `action_problem_${slug(problem.id)}`,
955
+ title: `Open the Problem dossier for ${problem.name}.`,
956
+ rationale: "The dossier has the cause, evidence, affected traces and verification state.",
957
+ command: `moda problem ${problem.id}`,
958
+ mutability: "read",
959
+ requiresApproval: false
960
+ }]
961
+ };
962
+ });
963
+ }
964
+ function insufficientDataFinding(overview, evidenceRefs) {
965
+ const evidenceId = "overview:data";
966
+ if (!evidenceRefs.some((ref) => ref.id === evidenceId)) {
967
+ evidenceRefs.push({
968
+ id: evidenceId,
969
+ kind: "data_api",
970
+ label: "Overview did not contain actionable production signals",
971
+ endpoint: "/overview",
972
+ value: overview
973
+ });
974
+ }
975
+ return {
976
+ id: "finding_insufficient_data",
977
+ kind: "insufficient_data",
978
+ title: "No strong production issue found yet",
979
+ severity: "info",
980
+ confidence: "medium",
981
+ impactScore: 10,
982
+ rankReason: "Moda needs more recent production traces before ranking failures.",
983
+ summary: "Data flow may be quiet or current signals are below the investigation threshold.",
984
+ evidenceRefIds: [evidenceId],
985
+ likelyLocations: [],
986
+ recommendedActions: [{
987
+ id: "action_doctor_online",
988
+ title: "Validate data flow.",
989
+ rationale: "A quiet overview can mean no traffic or broken ingestion.",
990
+ command: "moda doctor --online",
991
+ mutability: "read",
992
+ requiresApproval: false
993
+ }]
994
+ };
995
+ }
996
+ function normalizeCloudAsk(response, question) {
997
+ const record = asRecord(response);
998
+ const answer = asString(record?.answer);
999
+ if (!answer)
1000
+ return null;
1001
+ const evidenceRefs = Array.isArray(record?.evidence_refs) ? record.evidence_refs.flatMap((item) => {
1002
+ const ref = asRecord(item);
1003
+ if (!ref)
1004
+ return [];
1005
+ return [{
1006
+ id: asString(ref.id) ?? `ask:evidence:${slug(asString(ref.label) ?? "ref")}`,
1007
+ kind: evidenceKind(asString(ref.kind)),
1008
+ label: asString(ref.label) ?? asString(ref.id) ?? "Cloud evidence",
1009
+ path: asString(ref.path) ?? undefined,
1010
+ value: ref.value,
1011
+ href: asString(ref.href) ?? undefined
1012
+ }];
1013
+ }) : [];
1014
+ const nextCommands = Array.isArray(record?.next_commands) ? record.next_commands.filter((item) => typeof item === "string") : ["moda investigate"];
1015
+ const confidence = normalizeConfidence(record?.confidence);
1016
+ const intelligenceRecord = asRecord(record?.intelligence);
1017
+ const intelligence = looksLikeIntelligenceV1(intelligenceRecord) ? coerceIntelligenceV1(intelligenceRecord, { question, answer, confidence }) : synthesizeIntelligenceV1({
1018
+ question,
1019
+ answer,
1020
+ confidence,
1021
+ evidenceRefs,
1022
+ nextCommands,
1023
+ warnings: [],
1024
+ source: "cloud",
1025
+ degraded: false
1026
+ });
1027
+ return {
1028
+ schemaVersion: ASK_SCHEMA_VERSION,
1029
+ generatedAt: new Date().toISOString(),
1030
+ source: "cloud",
1031
+ degraded: false,
1032
+ question,
1033
+ answer,
1034
+ confidence,
1035
+ evidenceRefs,
1036
+ findings: [],
1037
+ nextCommands,
1038
+ warnings: [],
1039
+ intelligence
1040
+ };
1041
+ }
1042
+ function looksLikeIntelligenceV1(record) {
1043
+ if (!record)
1044
+ return false;
1045
+ const version = asString(record.schema_version);
1046
+ if (version && version !== INTELLIGENCE_SCHEMA_VERSION)
1047
+ return false;
1048
+ return version === INTELLIGENCE_SCHEMA_VERSION || typeof record.answer_markdown === "string" || asRecord(record.summary) !== null;
1049
+ }
1050
+ function coerceIntelligenceV1(record, context) {
1051
+ const summaryRecord = asRecord(record.summary);
1052
+ const summary = {
1053
+ text: asString(summaryRecord?.text) ?? context.answer,
1054
+ confidence: confidenceValue(summaryRecord?.confidence) ?? context.confidence
1055
+ };
1056
+ const evidenceRefs = Array.isArray(record.evidence_refs) ? record.evidence_refs.flatMap(coerceV1EvidenceRef) : [];
1057
+ const answerClaims = Array.isArray(record.answer_claims) ? record.answer_claims.flatMap(coerceV1Claim) : [];
1058
+ const findings = Array.isArray(record.findings) ? record.findings.flatMap(coerceV1Finding) : [];
1059
+ const nextCommands = Array.isArray(record.next_commands) ? record.next_commands.flatMap(coerceV1NextCommand) : [];
1060
+ const warnings = stringArray(record.warnings);
1061
+ const meta = asRecord(record.meta);
1062
+ return {
1063
+ schema_version: INTELLIGENCE_SCHEMA_VERSION,
1064
+ generated_at: asString(record.generated_at) ?? new Date().toISOString(),
1065
+ command: asString(record.command) ?? "ask",
1066
+ source: record.source === "local_fallback" ? "local_fallback" : "cloud",
1067
+ degraded: record.degraded === true,
1068
+ question: asString(record.question) ?? context.question,
1069
+ summary,
1070
+ answer_markdown: asString(record.answer_markdown) ?? context.answer,
1071
+ answer_claims: answerClaims,
1072
+ findings,
1073
+ evidence_refs: evidenceRefs,
1074
+ next_commands: nextCommands,
1075
+ warnings,
1076
+ ...meta ? { meta } : {}
1077
+ };
1078
+ }
1079
+ function synthesizeIntelligenceV1(options) {
1080
+ return {
1081
+ schema_version: INTELLIGENCE_SCHEMA_VERSION,
1082
+ generated_at: new Date().toISOString(),
1083
+ command: "ask",
1084
+ source: options.source,
1085
+ degraded: options.degraded,
1086
+ question: options.question,
1087
+ summary: { text: options.answer, confidence: options.confidence },
1088
+ answer_markdown: options.answer,
1089
+ answer_claims: [],
1090
+ findings: (options.findings ?? []).map(productionFindingToV1),
1091
+ evidence_refs: options.evidenceRefs.map(productionEvidenceToV1),
1092
+ next_commands: options.nextCommands.map(commandStringToV1),
1093
+ warnings: options.warnings
1094
+ };
1095
+ }
1096
+ function coerceV1EvidenceRef(item) {
1097
+ const ref = asRecord(item);
1098
+ if (!ref)
1099
+ return [];
1100
+ const label = asString(ref.label);
1101
+ const anchor = coerceV1Anchor(asRecord(ref.anchor));
1102
+ const snippet = asString(ref.snippet);
1103
+ return [{
1104
+ id: asString(ref.id) ?? `ask:evidence:${slug(label ?? "ref")}`,
1105
+ kind: asString(ref.kind) ?? "ask",
1106
+ label: label ?? asString(ref.id) ?? "Cloud evidence",
1107
+ ...anchor ? { anchor } : {},
1108
+ ...snippet ? { snippet } : {},
1109
+ grounding: provenanceValue(ref.grounding) ?? "measured"
1110
+ }];
1111
+ }
1112
+ function coerceV1Anchor(record) {
1113
+ if (!record)
1114
+ return;
1115
+ const anchor = {
1116
+ ...asString(record.conversation_id) ? { conversation_id: asString(record.conversation_id) } : {},
1117
+ ...asNumber(record.message_index) !== null ? { message_index: asNumber(record.message_index) } : {},
1118
+ ...asString(record.run_id) ? { run_id: asString(record.run_id) } : {},
1119
+ ...asString(record.tool_name) ? { tool_name: asString(record.tool_name) } : {}
1120
+ };
1121
+ return Object.keys(anchor).length > 0 ? anchor : undefined;
1122
+ }
1123
+ function coerceV1Claim(item) {
1124
+ const claim = asRecord(item);
1125
+ if (!claim)
1126
+ return [];
1127
+ const text = asString(claim.text);
1128
+ if (!text)
1129
+ return [];
1130
+ const value = asNumber(claim.value);
1131
+ const matchedPath = asString(claim.matched_path);
1132
+ return [{
1133
+ text,
1134
+ ...value !== null ? { value } : {},
1135
+ kind: claim.kind === "percentage" || claim.kind === "count" ? claim.kind : "qualitative",
1136
+ status: claim.status === "grounded" || claim.status === "ungrounded" ? claim.status : "unverifiable",
1137
+ evidence_ref_ids: stringArray(claim.evidence_ref_ids),
1138
+ ...matchedPath ? { matched_path: matchedPath } : {}
1139
+ }];
1140
+ }
1141
+ function coerceV1Finding(item) {
1142
+ const finding = asRecord(item);
1143
+ if (!finding)
1144
+ return [];
1145
+ const id = asString(finding.id);
1146
+ const title = asString(finding.title);
1147
+ if (!id || !title)
1148
+ return [];
1149
+ const severity = finding.severity;
1150
+ const description = asString(finding.description);
1151
+ return [{
1152
+ id,
1153
+ title,
1154
+ ...severity === "critical" || severity === "high" || severity === "medium" || severity === "low" || severity === "info" ? { severity } : {},
1155
+ ...description ? { description } : {},
1156
+ evidence_ref_ids: stringArray(finding.evidence_ref_ids),
1157
+ recommended_actions: Array.isArray(finding.recommended_actions) ? finding.recommended_actions.flatMap(coerceV1RecommendedAction) : []
1158
+ }];
1159
+ }
1160
+ function coerceV1RecommendedAction(item) {
1161
+ const action = asRecord(item);
1162
+ if (!action)
1163
+ return [];
1164
+ const id = asString(action.id);
1165
+ const title = asString(action.title);
1166
+ if (!id || !title)
1167
+ return [];
1168
+ const rationale = asString(action.rationale);
1169
+ const command = asString(action.command);
1170
+ return [{
1171
+ id,
1172
+ title,
1173
+ ...rationale ? { rationale } : {},
1174
+ ...command ? { command } : {},
1175
+ mutability: mutabilityValue(action.mutability) ?? "unknown",
1176
+ requires_approval: action.requires_approval === true
1177
+ }];
1178
+ }
1179
+ function coerceV1NextCommand(item) {
1180
+ if (typeof item === "string")
1181
+ return [commandStringToV1(item)];
1182
+ const record = asRecord(item);
1183
+ if (!record)
1184
+ return [];
1185
+ const command = asString(record.command);
1186
+ if (!command)
1187
+ return [];
1188
+ const purpose = asString(record.purpose);
1189
+ return [{
1190
+ command,
1191
+ ...purpose ? { purpose } : {},
1192
+ mutability: mutabilityValue(record.mutability) ?? mutabilityForCommand(command),
1193
+ requires_approval: typeof record.requires_approval === "boolean" ? record.requires_approval : requiresApprovalForCommand(command)
1194
+ }];
1195
+ }
1196
+ function productionEvidenceToV1(ref) {
1197
+ const anchor = {
1198
+ ...ref.conversationId ? { conversation_id: ref.conversationId } : {},
1199
+ ...ref.runId ? { run_id: ref.runId } : {},
1200
+ ...ref.toolName ? { tool_name: ref.toolName } : {}
1201
+ };
1202
+ return {
1203
+ id: ref.id,
1204
+ kind: ref.kind,
1205
+ label: ref.label,
1206
+ ...Object.keys(anchor).length > 0 ? { anchor } : {},
1207
+ grounding: "measured"
1208
+ };
1209
+ }
1210
+ function productionFindingToV1(finding) {
1211
+ return {
1212
+ id: finding.id,
1213
+ title: finding.title,
1214
+ severity: finding.severity,
1215
+ description: `${finding.summary} ${finding.rankReason}`.trim(),
1216
+ evidence_ref_ids: finding.evidenceRefIds,
1217
+ recommended_actions: finding.recommendedActions.map((action) => ({
1218
+ id: action.id,
1219
+ title: action.title,
1220
+ rationale: action.rationale,
1221
+ command: action.command,
1222
+ mutability: action.mutability,
1223
+ requires_approval: action.requiresApproval
1224
+ }))
1225
+ };
1226
+ }
1227
+ function commandStringToV1(command) {
1228
+ return {
1229
+ command,
1230
+ purpose: purposeForCommand(command),
1231
+ mutability: mutabilityForCommand(command),
1232
+ requires_approval: requiresApprovalForCommand(command)
1233
+ };
1234
+ }
1235
+ function mutabilityForCommand(command) {
1236
+ return command.includes(" sync") ? "sync" : "read";
1237
+ }
1238
+ function requiresApprovalForCommand(command) {
1239
+ return command.includes(" sync") || command.includes(" --yes");
1240
+ }
1241
+ function stringArray(value) {
1242
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
1243
+ }
1244
+ function confidenceValue(value) {
1245
+ return value === "high" || value === "medium" || value === "low" || value === "unknown" ? value : null;
1246
+ }
1247
+ function provenanceValue(value) {
1248
+ return value === "measured" || value === "inferred" ? value : null;
1249
+ }
1250
+ function mutabilityValue(value) {
1251
+ return value === "read" || value === "write" || value === "sync" || value === "unknown" ? value : null;
1252
+ }
1253
+ function investigationAgentOptions(investigation) {
1254
+ return {
1255
+ summary: investigation.summary,
1256
+ findings: investigation.findings.map(agentFinding),
1257
+ evidenceRefs: investigation.evidenceRefs.map(agentEvidence),
1258
+ recommendedActions: investigation.findings.flatMap((finding) => finding.recommendedActions.map(agentAction)),
1259
+ nextCommands: investigation.nextCommands.map(nextCommand),
1260
+ warnings: investigation.warnings
1261
+ };
1262
+ }
1263
+ function overviewAgentOptions(briefing) {
1264
+ return {
1265
+ summary: {
1266
+ text: `Moda health briefing: ${briefing.status}. Next: ${briefing.nextCommand}`,
1267
+ confidence: briefing.topFinding ? briefing.topFinding.confidence : "medium"
1268
+ },
1269
+ findings: briefing.topFinding ? [agentFinding(briefing.topFinding)] : [],
1270
+ evidenceRefs: briefing.evidenceRefs.map(agentEvidence),
1271
+ recommendedActions: briefing.topFinding ? briefing.topFinding.recommendedActions.map(agentAction) : [],
1272
+ nextCommands: [nextCommand(briefing.nextCommand)]
1273
+ };
1274
+ }
1275
+ function askAgentOptions(result) {
1276
+ const errors = result.degraded ? [{
1277
+ code: "cloud_unavailable",
1278
+ message: result.fallbackReason ?? "Moda Cloud intelligence was unavailable; answer synthesized from local Data API evidence.",
1279
+ blocking: false,
1280
+ suggested_commands: ["moda doctor --online"]
1281
+ }] : [];
1282
+ return {
1283
+ summary: { text: result.answer, confidence: result.confidence },
1284
+ findings: result.findings.map(agentFinding),
1285
+ evidenceRefs: result.evidenceRefs.map(agentEvidence),
1286
+ recommendedActions: result.findings.flatMap((finding) => finding.recommendedActions.map(agentAction)),
1287
+ nextCommands: result.nextCommands.map(nextCommand),
1288
+ warnings: result.warnings,
1289
+ errors
1290
+ };
1291
+ }
1292
+ function agentFinding(finding) {
1293
+ return {
1294
+ id: finding.id,
1295
+ title: finding.title,
1296
+ severity: finding.severity,
1297
+ description: `${finding.summary} ${finding.rankReason}`,
1298
+ evidence_ref_ids: finding.evidenceRefIds
1299
+ };
1300
+ }
1301
+ function agentEvidence(ref) {
1302
+ return {
1303
+ id: ref.id,
1304
+ kind: ref.kind,
1305
+ label: ref.label,
1306
+ path: ref.path,
1307
+ line: ref.line,
1308
+ href: ref.href
1309
+ };
1310
+ }
1311
+ function agentAction(action) {
1312
+ return {
1313
+ id: action.id,
1314
+ title: action.title,
1315
+ rationale: action.rationale,
1316
+ command: action.command,
1317
+ mutability: action.mutability,
1318
+ requires_approval: action.requiresApproval
1319
+ };
1320
+ }
1321
+ function nextCommand(command) {
1322
+ return {
1323
+ command,
1324
+ purpose: purposeForCommand(command),
1325
+ mutability: command.includes(" sync") ? "sync" : "read",
1326
+ requires_approval: command.includes(" sync") || command.includes(" --yes")
1327
+ };
1328
+ }
1329
+ function renderOverviewBriefingHuman(briefing) {
1330
+ const lines = [];
1331
+ lines.push("Moda overview");
1332
+ lines.push("Production health briefing");
1333
+ lines.push("");
1334
+ lines.push(`Status ${briefing.status}`);
1335
+ lines.push(`Data flow ${briefing.dataFlow.status} (${briefing.dataFlow.conversations} trace(s))`);
1336
+ if (briefing.dataFlow.lastEvent) {
1337
+ lines.push(`Last event ${briefing.dataFlow.lastEvent.summary}${briefing.dataFlow.lastEvent.timestamp ? ` - ${briefing.dataFlow.lastEvent.timestamp}` : ""}`);
1338
+ }
1339
+ lines.push("");
1340
+ lines.push("Sync");
1341
+ lines.push(` Harness ${briefing.harnessSync.status} - ${briefing.harnessSync.summary}`);
1342
+ lines.push(` Prompts ${briefing.promptSync.status} - ${briefing.promptSync.summary}`);
1343
+ lines.push(` Skills ${briefing.skillSync.status} - ${briefing.skillSync.summary}`);
1344
+ lines.push("");
1345
+ lines.push("Signals");
1346
+ for (const [label, value] of overviewSignalPairs(briefing.metrics)) {
1347
+ lines.push(` ${label.padEnd(18)} ${String(value)}`);
1348
+ }
1349
+ lines.push("");
1350
+ lines.push("Top signal");
1351
+ if (briefing.topFinding) {
1352
+ lines.push(` ${briefing.topFinding.severity.toUpperCase()} ${briefing.topFinding.title}`);
1353
+ lines.push(` ${briefing.topFinding.summary}`);
1354
+ lines.push(` Why ranked: ${briefing.topFinding.rankReason}`);
1355
+ } else {
1356
+ lines.push(" No urgent production issue found in the selected window.");
1357
+ }
1358
+ lines.push("");
1359
+ lines.push("Next best action");
1360
+ lines.push(` ${briefing.nextCommand}`);
1361
+ return lines.join(`
1362
+ `);
1363
+ }
1364
+ function renderInvestigationHuman(investigation) {
1365
+ const lines = [];
1366
+ lines.push("Moda investigation");
1367
+ lines.push(`Window: last ${investigation.daysBack} day(s)`);
1368
+ lines.push("");
1369
+ lines.push(investigation.summary.text);
1370
+ for (const finding of investigation.findings) {
1371
+ lines.push("");
1372
+ lines.push(`${finding.severity.toUpperCase()} ${finding.title}`);
1373
+ lines.push(` Confidence: ${finding.confidence}`);
1374
+ lines.push(` Impact: ${finding.impactScore}`);
1375
+ lines.push(` ${finding.summary}`);
1376
+ lines.push(` Why ranked: ${finding.rankReason}`);
1377
+ if (finding.likelyLocations.length > 0) {
1378
+ lines.push(" Likely location:");
1379
+ for (const location of finding.likelyLocations.slice(0, 3)) {
1380
+ lines.push(` - ${location.path ?? location.label} (${location.confidence})`);
1381
+ }
1382
+ }
1383
+ if (finding.recommendedActions.length > 0) {
1384
+ lines.push(" Next:");
1385
+ for (const action of finding.recommendedActions.slice(0, 2)) {
1386
+ lines.push(` ${action.command}`);
1387
+ }
1388
+ }
1389
+ }
1390
+ lines.push("");
1391
+ lines.push("Next commands");
1392
+ for (const command of investigation.nextCommands.slice(0, 6))
1393
+ lines.push(` ${command}`);
1394
+ if (investigation.warnings.length > 0) {
1395
+ lines.push("");
1396
+ lines.push("Warnings");
1397
+ for (const warning of investigation.warnings.slice(0, 5))
1398
+ lines.push(` - ${warning}`);
1399
+ }
1400
+ return lines.join(`
1401
+ `);
1402
+ }
1403
+ function renderAskHuman(result, options = {}) {
1404
+ const color = options.color ?? shouldColorTerminalOutput();
1405
+ const style = terminalStyles(color);
1406
+ const lines = [];
1407
+ lines.push(style.heading("Moda ask"));
1408
+ lines.push(`${style.dim("Question:")} ${result.question}`);
1409
+ if (result.degraded) {
1410
+ lines.push("");
1411
+ lines.push("[degraded] Moda Cloud was unavailable — this answer is synthesized from local Data API evidence (lower trust).");
1412
+ }
1413
+ lines.push("");
1414
+ lines.push(renderMarkdownForTerminal(result.answer, { color }));
1415
+ lines.push("");
1416
+ lines.push(`Confidence: ${style.bold(result.confidence)}`);
1417
+ lines.push(`Source: ${result.source}${result.degraded ? " (degraded)" : ""}`);
1418
+ if (result.evidenceRefs.length > 0) {
1419
+ lines.push("");
1420
+ lines.push(style.bold("Evidence"));
1421
+ const grounding = new Map(result.intelligence.evidence_refs.map((ref) => [ref.id, ref.grounding]));
1422
+ const dashboardBase = resolveModaBaseUrl();
1423
+ for (const ref of result.evidenceRefs.slice(0, 8)) {
1424
+ const marker = grounding.get(ref.id) === "inferred" ? " (inferred)" : "";
1425
+ const label = stripAnsi(ref.label);
1426
+ const path = ref.path ? ` (${stripAnsi(ref.path)})` : "";
1427
+ const link = ref.href ? `
1428
+ ${style.dim(`${dashboardBase}${stripAnsi(ref.href)}`)}` : "";
1429
+ lines.push(` - ${label}${path}${marker}${link}`);
1430
+ }
1431
+ }
1432
+ const unverifiedClaims = result.intelligence.answer_claims.filter((claim) => claim.status !== "grounded").length;
1433
+ if (unverifiedClaims > 0) {
1434
+ lines.push("");
1435
+ lines.push(`Note: ${unverifiedClaims} claim(s) could not be grounded against tool data.`);
1436
+ }
1437
+ if (result.nextCommands.length > 0) {
1438
+ lines.push("");
1439
+ lines.push(style.bold("Next commands"));
1440
+ for (const command of result.nextCommands.slice(0, 5))
1441
+ lines.push(` ${stripAnsi(command)}`);
1442
+ }
1443
+ if (result.warnings.length > 0) {
1444
+ lines.push("");
1445
+ lines.push(style.bold("Warnings"));
1446
+ for (const warning of result.warnings.slice(0, 3))
1447
+ lines.push(` - ${stripAnsi(warning)}`);
1448
+ }
1449
+ return lines.join(`
1450
+ `);
1451
+ }
1452
+ function addOverviewEvidence(data, endpoint, refs) {
1453
+ refs.push({
1454
+ id: "overview:data",
1455
+ kind: "data_api",
1456
+ label: "Production overview aggregate",
1457
+ endpoint,
1458
+ value: overviewMetrics(data)
1459
+ });
1460
+ }
1461
+ function toolRows(data, scopedTool) {
1462
+ const record = asRecord(data);
1463
+ const directToolName = asString(record?.tool_name) ?? scopedTool;
1464
+ const examples = Array.isArray(record?.examples) ? record.examples : [];
1465
+ if (directToolName) {
1466
+ const subtypes = (Array.isArray(record?.subtypes) ? record.subtypes : []).map((item) => asRecord(item)).filter((item) => item !== null && item !== undefined);
1467
+ const subtypeTotal = subtypes.reduce((sum, item) => sum + numberValue(item.count), 0);
1468
+ const top = [...subtypes].sort((left, right) => numberValue(right.count) - numberValue(left.count))[0];
1469
+ return [{
1470
+ name: directToolName,
1471
+ count: numberValue(record?.failure_count) || numberValue(record?.total_failures) || numberValue(asRecord(record?.pagination)?.total) || subtypeTotal || examples.length,
1472
+ conversations: numberValue(record?.failed_conversations) || numberValue(record?.conversation_count) || Math.max(0, ...subtypes.map((item) => numberValue(item.conversation_count))) || examples.length,
1473
+ subtype: asString(record?.top_error_subtype) ?? asString(record?.error_subtype) ?? asString(top?.subtype) ?? undefined,
1474
+ raw: data
1475
+ }];
1476
+ }
1477
+ const tools = Array.isArray(record?.tools) ? record.tools : Array.isArray(record?.tool_failures) ? record.tool_failures : Array.isArray(record?.failures) ? record.failures : [];
1478
+ return tools.flatMap((item) => {
1479
+ const row = asRecord(item);
1480
+ const name = asString(row?.tool_name) ?? asString(row?.name);
1481
+ if (!name)
1482
+ return [];
1483
+ return [{
1484
+ name,
1485
+ count: numberValue(row?.failure_count) || numberValue(row?.total_failures) || numberValue(row?.count),
1486
+ conversations: numberValue(row?.failed_conversations) || numberValue(row?.conversation_count),
1487
+ subtype: asString(row?.top_error_subtype) ?? asString(row?.error_subtype) ?? undefined,
1488
+ raw: row
1489
+ }];
1490
+ });
1491
+ }
1492
+ function firstQuote(row) {
1493
+ const quotes = Array.isArray(row?.user_quotes) ? row.user_quotes : [];
1494
+ const first = asRecord(quotes[0]);
1495
+ return asString(first?.quote) ?? undefined;
1496
+ }
1497
+ function likelyToolLocation(toolName, hints) {
1498
+ const normalized = normalizeKey(toolName);
1499
+ for (const [key, path] of hints.toolPaths.entries()) {
1500
+ if (key.includes(normalized) || normalized.includes(key)) {
1501
+ return {
1502
+ kind: "tool",
1503
+ path,
1504
+ label: `Tool definition for ${toolName}`,
1505
+ confidence: "medium",
1506
+ reason: "Matched tool name against the local harness graph."
1507
+ };
1508
+ }
1509
+ }
1510
+ return null;
1511
+ }
1512
+ function defaultLikelyLocations(hints, kind) {
1513
+ const source = kind === "tool" ? hints.toolPaths : kind === "prompt" ? hints.promptPaths : hints.skillPaths;
1514
+ const first = [...source.values()][0];
1515
+ if (first) {
1516
+ return [{
1517
+ kind,
1518
+ path: first,
1519
+ label: `${kind} artifact`,
1520
+ confidence: "low",
1521
+ reason: "The overview is aggregate-only; investigation will map specific production evidence."
1522
+ }];
1523
+ }
1524
+ if (hints.reportPath) {
1525
+ return [{
1526
+ kind: "harness",
1527
+ path: hints.reportPath,
1528
+ label: "Local harness manifest",
1529
+ confidence: "low",
1530
+ reason: "Use the local harness graph to map production signals to code."
1531
+ }];
1532
+ }
1533
+ return [];
1534
+ }
1535
+ function unknownLocation(kind, reason) {
1536
+ return {
1537
+ kind,
1538
+ label: "Unknown local path",
1539
+ confidence: "unknown",
1540
+ reason
1541
+ };
1542
+ }
1543
+ function loadHarnessHints(cwd) {
1544
+ const hints = {
1545
+ toolPaths: new Map,
1546
+ promptPaths: new Map,
1547
+ skillPaths: new Map,
1548
+ entrypointPaths: []
1549
+ };
1550
+ const harnessPath = join(cwd, ".moda", "harness.json");
1551
+ if (!existsSync(harnessPath))
1552
+ return hints;
1553
+ hints.reportPath = ".moda/harness.json";
1554
+ try {
1555
+ const parsed = JSON.parse(readFileSync(harnessPath, "utf8"));
1556
+ const graph = asRecord(parsed);
1557
+ const artifacts = Array.isArray(graph?.artifacts) ? graph.artifacts : [];
1558
+ for (const item of artifacts) {
1559
+ const artifact = asRecord(item);
1560
+ if (!artifact)
1561
+ continue;
1562
+ const name = asString(artifact.name) ?? asString(artifact.id) ?? "";
1563
+ const path = asString(artifact.sourcePath) ?? asString(artifact.path);
1564
+ const type = asString(artifact.type);
1565
+ if (!path || !name)
1566
+ continue;
1567
+ const key = normalizeKey(name);
1568
+ if (type === "tool")
1569
+ hints.toolPaths.set(key, path);
1570
+ if (type === "prompt")
1571
+ hints.promptPaths.set(key, path);
1572
+ if (type === "skill")
1573
+ hints.skillPaths.set(key, path);
1574
+ if (type === "entrypoint")
1575
+ hints.entrypointPaths.push(path);
1576
+ }
1577
+ } catch {
1578
+ return hints;
1579
+ }
1580
+ return hints;
1581
+ }
1582
+ function localHarnessSummary(cwd) {
1583
+ const path = join(cwd, ".moda", "harness.json");
1584
+ if (!existsSync(path))
1585
+ return null;
1586
+ try {
1587
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
1588
+ return {
1589
+ harnessId: parsed.harnessId,
1590
+ graphHash: parsed.graphHash,
1591
+ runtimeAgentCount: Array.isArray(parsed.runtimeAgents) ? parsed.runtimeAgents.length : undefined
1592
+ };
1593
+ } catch {
1594
+ return null;
1595
+ }
1596
+ }
1597
+ function localSyncBrief(cwd, relativePath, label) {
1598
+ const path = join(cwd, relativePath);
1599
+ if (!existsSync(path)) {
1600
+ return { status: "missing", summary: `${label} state not found`, path: relativePath };
1601
+ }
1602
+ return { status: "present", summary: `${label} state exists locally`, path: relativePath };
1603
+ }
1604
+ function localSkillBrief(cwd) {
1605
+ const claudeSkill = join(cwd, ".claude", "skills");
1606
+ const cursorRules = join(cwd, ".cursor", "rules");
1607
+ if (existsSync(claudeSkill) || existsSync(cursorRules)) {
1608
+ return { status: "present", summary: "Local agent skill/rule directory exists" };
1609
+ }
1610
+ return { status: "missing", summary: "No local agent skill/rule directory found" };
1611
+ }
1612
+ function summarizeInvestigation(findings) {
1613
+ const top = findings[0];
1614
+ if (!top) {
1615
+ return { text: "No production findings were generated.", confidence: "unknown" };
1616
+ }
1617
+ if (top.kind === "insufficient_data") {
1618
+ return { text: top.title, confidence: top.confidence };
1619
+ }
1620
+ return {
1621
+ text: `Top production finding: ${top.title}. ${top.summary}`,
1622
+ confidence: top.confidence
1623
+ };
1624
+ }
1625
+ function nextCommandsForFindings(findings, daysBack, includeProblems = false) {
1626
+ const limit = 8;
1627
+ const primary = [...new Set(findings.map((finding) => finding.recommendedActions[0]?.command).filter(Boolean))];
1628
+ const generic = [
1629
+ ...includeProblems ? [`moda problems --days-back=${daysBack}`] : [],
1630
+ `moda overview --days-back=${daysBack}`,
1631
+ "moda doctor --online"
1632
+ ];
1633
+ const secondary = findings.flatMap((finding) => finding.recommendedActions.slice(1).map((action) => action.command)).filter(Boolean);
1634
+ const keep = new Set(primary);
1635
+ for (const command of [...generic, ...secondary]) {
1636
+ if (keep.size >= Math.max(limit, primary.length))
1637
+ break;
1638
+ keep.add(command);
1639
+ }
1640
+ const ordered = [
1641
+ ...findings.flatMap((finding) => finding.recommendedActions.map((action) => action.command)).filter(Boolean),
1642
+ ...generic
1643
+ ];
1644
+ return [...new Set(ordered)].filter((command) => keep.has(command));
1645
+ }
1646
+ function capFindings(sorted, limit, problemSlots) {
1647
+ const reserved = [
1648
+ ...sorted.filter((finding) => finding.kind === "problem").slice(0, problemSlots),
1649
+ ...sorted.filter((finding) => finding.kind === "frustration").slice(0, 1)
1650
+ ];
1651
+ const others = sorted.filter((finding) => !reserved.includes(finding)).slice(0, limit - reserved.length);
1652
+ const kept = new Set([...reserved, ...others]);
1653
+ return sorted.filter((finding) => kept.has(finding));
1654
+ }
1655
+ function compareFindings(a, b) {
1656
+ const severityDelta = severityWeight(b.severity) - severityWeight(a.severity);
1657
+ if (severityDelta !== 0)
1658
+ return severityDelta;
1659
+ return b.impactScore - a.impactScore;
1660
+ }
1661
+ function severityWeight(severity) {
1662
+ return { critical: 5, high: 4, medium: 3, low: 2, info: 1 }[severity];
1663
+ }
1664
+ function severityForCount(count, high, medium) {
1665
+ if (count >= high)
1666
+ return "critical";
1667
+ if (count >= medium)
1668
+ return "high";
1669
+ return "medium";
1670
+ }
1671
+ function uniqueEvidenceRefs(refs) {
1672
+ const seen = new Set;
1673
+ const out = [];
1674
+ for (const ref of refs) {
1675
+ if (seen.has(ref.id))
1676
+ continue;
1677
+ seen.add(ref.id);
1678
+ out.push(ref);
1679
+ }
1680
+ return out;
1681
+ }
1682
+ function overviewMetrics(data) {
1683
+ return {
1684
+ conversations: valueAt(data, ["conversations", "total"]) ?? 0,
1685
+ conversationTrendPct: valueAt(data, ["conversations", "trend_pct"]) ?? 0,
1686
+ toolFailures: valueAt(data, ["tool_failures", "total"]) ?? 0,
1687
+ failedToolConversations: valueAt(data, ["tool_failures", "conversations"]) ?? 0,
1688
+ impactedTools: valueAt(data, ["tool_failures", "tools"]) ?? 0,
1689
+ frustrated: valueAt(data, ["frustrations", "frustrated"]) ?? 0,
1690
+ atRisk: valueAt(data, ["frustrations", "at_risk"]) ?? 0,
1691
+ frustrationRatePct: valueAt(data, ["frustrations", "rate_pct"]) ?? 0,
1692
+ recentActivity: recentActivity(data).length
1693
+ };
1694
+ }
1695
+ function overviewSignalPairs(metrics) {
1696
+ return [
1697
+ ["Traces", metrics.conversations],
1698
+ ["Tool failures", metrics.toolFailures],
1699
+ ["Failed traces", metrics.failedToolConversations],
1700
+ ["Impacted tools", metrics.impactedTools],
1701
+ ["Frustrated", metrics.frustrated],
1702
+ ["At risk", metrics.atRisk],
1703
+ ["Frustration %", metrics.frustrationRatePct],
1704
+ ["Recent events", metrics.recentActivity]
1705
+ ];
1706
+ }
1707
+ function recentActivity(data) {
1708
+ const activity = valueAt(data, ["recent_activity"]);
1709
+ if (!Array.isArray(activity))
1710
+ return [];
1711
+ return activity.flatMap((item) => {
1712
+ const row = asRecord(item);
1713
+ const summary = asString(row?.summary) ?? asString(row?.title) ?? asString(row?.event);
1714
+ if (!summary)
1715
+ return [];
1716
+ return [{
1717
+ summary,
1718
+ timestamp: asString(row?.timestamp) ?? asString(row?.created_at) ?? undefined,
1719
+ conversationId: asString(row?.conversation_id) ?? undefined
1720
+ }];
1721
+ });
1722
+ }
1723
+ function valueAt(value, path) {
1724
+ let cursor = value;
1725
+ for (const key of path) {
1726
+ const record = asRecord(cursor);
1727
+ if (!record)
1728
+ return;
1729
+ cursor = record[key];
1730
+ }
1731
+ return cursor;
1732
+ }
1733
+ function numberAt(value, path) {
1734
+ return numberValue(valueAt(value, path));
1735
+ }
1736
+ function numberValue(value) {
1737
+ const direct = asNumber(value);
1738
+ if (direct !== null)
1739
+ return direct;
1740
+ if (typeof value === "string") {
1741
+ const parsed = Number(value);
1742
+ return Number.isFinite(parsed) ? parsed : 0;
1743
+ }
1744
+ return 0;
1745
+ }
1746
+ function daysBackFlag(flags, fallback = 7) {
1747
+ const raw = flags["days-back"];
1748
+ if (raw === undefined)
1749
+ return fallback;
1750
+ const parsed = Number(raw);
1751
+ if (raw.trim() === "" || raw === "true" || !Number.isInteger(parsed) || parsed < 1 || parsed > 90) {
1752
+ throw new CliInputError(`--days-back must be a whole number from 1 to 90; got '${raw}'.`, "Nothing was run. Example: --days-back=7 (the default).");
1753
+ }
1754
+ return parsed;
1755
+ }
1756
+ function normalizeConfidence(value) {
1757
+ if (value === "high" || value === "medium" || value === "low" || value === "unknown")
1758
+ return value;
1759
+ return "medium";
1760
+ }
1761
+ function capFallbackConfidence(confidence) {
1762
+ return confidence === "high" ? "medium" : confidence;
1763
+ }
1764
+ function evidenceKind(value) {
1765
+ if (value === "data_api" || value === "tool_failure" || value === "frustration" || value === "problem" || value === "harness" || value === "ask")
1766
+ return value;
1767
+ return "ask";
1768
+ }
1769
+ function normalizeKey(value) {
1770
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
1771
+ }
1772
+ function slug(value) {
1773
+ const clean = value.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
1774
+ return clean || "unknown";
1775
+ }
1776
+ function purposeForCommand(command) {
1777
+ if (command.startsWith("moda investigate"))
1778
+ return "Open the ranked production investigation.";
1779
+ if (command.startsWith("moda tool-failure-detail"))
1780
+ return "Inspect tool failure examples and trace anchors.";
1781
+ if (command.startsWith("moda problems"))
1782
+ return "List the ranked Problems.";
1783
+ if (command.startsWith("moda problem "))
1784
+ return "Open the Problem dossier.";
1785
+ if (command.startsWith("moda context"))
1786
+ return "Read the relevant trace window.";
1787
+ if (command.startsWith("moda doctor"))
1788
+ return "Validate Moda setup and data flow.";
1789
+ if (command.startsWith("moda overview"))
1790
+ return "Return to the harness health briefing.";
1791
+ return "Suggested next Moda command.";
1792
+ }
1793
+ export {
1794
+ shouldStreamAsk,
1795
+ runProductionCommand,
1796
+ renderAskHuman,
1797
+ loadOverviewBriefing,
1798
+ investigateProduction,
1799
+ daysBackFlag,
1800
+ askResultFromStreamOutcome,
1801
+ askModa
1802
+ };