@gmickel/gno 1.18.0 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/README.md +14 -7
  2. package/assets/skill/SKILL.md +54 -12
  3. package/assets/skill/mcp-reference.md +7 -2
  4. package/assets/skill/recipes/citation-and-provenance.md +32 -9
  5. package/package.json +2 -1
  6. package/spec/AGENTS.md +83 -0
  7. package/spec/CLAUDE.md +83 -0
  8. package/spec/bench-fixture.schema.json +137 -0
  9. package/spec/cli.md +2919 -0
  10. package/spec/db/schema.sql +442 -0
  11. package/spec/evals-agentic.md +592 -0
  12. package/spec/evals.md +1106 -0
  13. package/spec/mcp.md +2279 -0
  14. package/spec/output-schemas/activation-verification.schema.json +515 -0
  15. package/spec/output-schemas/ask.schema.json +564 -0
  16. package/spec/output-schemas/backlinks.schema.json +131 -0
  17. package/spec/output-schemas/bench-result.schema.json +120 -0
  18. package/spec/output-schemas/capture-receipt.schema.json +143 -0
  19. package/spec/output-schemas/claim-verification.schema.json +291 -0
  20. package/spec/output-schemas/collection-list.schema.json +45 -0
  21. package/spec/output-schemas/context-capsule-v1.schema.json +726 -0
  22. package/spec/output-schemas/context-capsule-verification.schema.json +1338 -0
  23. package/spec/output-schemas/context-list.schema.json +21 -0
  24. package/spec/output-schemas/doctor.schema.json +313 -0
  25. package/spec/output-schemas/error.schema.json +30 -0
  26. package/spec/output-schemas/expansion.schema.json +37 -0
  27. package/spec/output-schemas/get.schema.json +140 -0
  28. package/spec/output-schemas/graph-query.schema.json +99 -0
  29. package/spec/output-schemas/graph.schema.json +371 -0
  30. package/spec/output-schemas/links-list.schema.json +186 -0
  31. package/spec/output-schemas/mcp-add-collection-result.schema.json +23 -0
  32. package/spec/output-schemas/mcp-capture-result.schema.json +152 -0
  33. package/spec/output-schemas/mcp-http-error.schema.json +30 -0
  34. package/spec/output-schemas/mcp-job-list.schema.json +58 -0
  35. package/spec/output-schemas/mcp-job-status.schema.json +224 -0
  36. package/spec/output-schemas/mcp-remove-result.schema.json +39 -0
  37. package/spec/output-schemas/mcp-sync-result.schema.json +41 -0
  38. package/spec/output-schemas/mcp-tag-result.schema.json +33 -0
  39. package/spec/output-schemas/models-list.schema.json +93 -0
  40. package/spec/output-schemas/multi-get.schema.json +103 -0
  41. package/spec/output-schemas/process-status.schema.json +119 -0
  42. package/spec/output-schemas/query-diagnose.schema.json +123 -0
  43. package/spec/output-schemas/resident-status.schema.json +154 -0
  44. package/spec/output-schemas/retrieval-trace-common.schema.json +492 -0
  45. package/spec/output-schemas/retrieval-trace-delete.schema.json +16 -0
  46. package/spec/output-schemas/retrieval-trace-export.schema.json +61 -0
  47. package/spec/output-schemas/retrieval-trace-filters.schema.json +139 -0
  48. package/spec/output-schemas/retrieval-trace-judgment.schema.json +15 -0
  49. package/spec/output-schemas/retrieval-trace-list.schema.json +18 -0
  50. package/spec/output-schemas/retrieval-trace-payloads.schema.json +178 -0
  51. package/spec/output-schemas/retrieval-trace-purge.schema.json +31 -0
  52. package/spec/output-schemas/retrieval-trace-qrels.schema.json +303 -0
  53. package/spec/output-schemas/retrieval-trace-replay.schema.json +286 -0
  54. package/spec/output-schemas/retrieval-trace-show.schema.json +69 -0
  55. package/spec/output-schemas/retrieval-trace-summary.schema.json +65 -0
  56. package/spec/output-schemas/search-result.schema.json +154 -0
  57. package/spec/output-schemas/search-results.schema.json +338 -0
  58. package/spec/output-schemas/similar.schema.json +84 -0
  59. package/spec/output-schemas/status.schema.json +676 -0
  60. package/spec/output-schemas/tags-list.schema.json +48 -0
  61. package/src/app/context-runtime-contract.ts +10 -5
  62. package/src/app/context-runtime-input.ts +29 -1
  63. package/src/app/context-runtime-types.ts +7 -0
  64. package/src/app/context-runtime.ts +20 -2
  65. package/src/app/context-surface.ts +4 -0
  66. package/src/app/verified-ask.ts +291 -0
  67. package/src/cli/commands/ask-format.ts +255 -0
  68. package/src/cli/commands/ask.ts +144 -183
  69. package/src/cli/commands/context-build.ts +56 -9
  70. package/src/cli/commands/get.ts +64 -3
  71. package/src/cli/commands/query.ts +62 -23
  72. package/src/cli/commands/replay.ts +140 -0
  73. package/src/cli/commands/search.ts +48 -3
  74. package/src/cli/commands/shared.ts +3 -1
  75. package/src/cli/commands/trace.ts +200 -0
  76. package/src/cli/commands/vsearch.ts +75 -53
  77. package/src/cli/program.ts +287 -1
  78. package/src/config/index.ts +9 -0
  79. package/src/config/retrieval-traces.ts +56 -0
  80. package/src/config/types.ts +4 -0
  81. package/src/core/context-budget.ts +6 -0
  82. package/src/core/context-capsule-retrieval-schema.ts +4 -0
  83. package/src/core/context-capsule-schema.ts +17 -0
  84. package/src/core/context-capsule-validation.ts +3 -2
  85. package/src/core/context-capsule.ts +18 -0
  86. package/src/core/context-compiler.ts +44 -25
  87. package/src/core/context-evidence.ts +6 -0
  88. package/src/core/retrieval-qrels.ts +405 -0
  89. package/src/core/retrieval-replay-candidate.ts +368 -0
  90. package/src/core/retrieval-replay-types.ts +109 -0
  91. package/src/core/retrieval-replay-validation.ts +89 -0
  92. package/src/core/retrieval-replay.ts +441 -0
  93. package/src/core/retrieval-trace-evidence-origin.ts +178 -0
  94. package/src/core/retrieval-trace-export.ts +113 -0
  95. package/src/core/retrieval-trace-filter-normalization.ts +27 -0
  96. package/src/core/retrieval-trace-filters.ts +19 -0
  97. package/src/core/retrieval-trace-management-helpers.ts +247 -0
  98. package/src/core/retrieval-trace-management-types.ts +132 -0
  99. package/src/core/retrieval-trace-management.ts +422 -0
  100. package/src/core/retrieval-trace-request.ts +141 -0
  101. package/src/core/retrieval-trace-session.ts +507 -0
  102. package/src/core/retrieval-trace.ts +472 -0
  103. package/src/llm/errors.ts +10 -1
  104. package/src/llm/httpGeneration.ts +11 -1
  105. package/src/llm/nodeLlamaCpp/generation.ts +54 -10
  106. package/src/llm/types.ts +6 -0
  107. package/src/mcp/tools/ask.ts +228 -0
  108. package/src/mcp/tools/context.ts +87 -15
  109. package/src/mcp/tools/get.ts +35 -1
  110. package/src/mcp/tools/index.ts +83 -0
  111. package/src/mcp/tools/query.ts +95 -64
  112. package/src/mcp/tools/search.ts +36 -13
  113. package/src/mcp/tools/trace.ts +143 -0
  114. package/src/mcp/tools/vsearch.ts +71 -38
  115. package/src/pipeline/answer.ts +167 -26
  116. package/src/pipeline/claim-verification-schema.ts +235 -0
  117. package/src/pipeline/claim-verification.ts +487 -0
  118. package/src/pipeline/claim-verifier.ts +474 -0
  119. package/src/pipeline/graph-retrieval.ts +15 -1
  120. package/src/pipeline/hybrid.ts +151 -43
  121. package/src/pipeline/search.ts +36 -3
  122. package/src/pipeline/trace-metadata.ts +47 -0
  123. package/src/pipeline/types.ts +68 -0
  124. package/src/pipeline/vsearch.ts +101 -38
  125. package/src/sdk/client.ts +415 -73
  126. package/src/sdk/documents.ts +48 -1
  127. package/src/sdk/index.ts +17 -0
  128. package/src/sdk/types.ts +28 -0
  129. package/src/serve/context-capsule.ts +67 -8
  130. package/src/serve/public/app.tsx +12 -1
  131. package/src/serve/public/components/AskVerificationPanel.tsx +189 -0
  132. package/src/serve/public/globals.built.css +1 -1
  133. package/src/serve/public/lib/workspace-tabs.ts +2 -0
  134. package/src/serve/public/pages/Ask.tsx +42 -4
  135. package/src/serve/public/pages/Dashboard.tsx +10 -0
  136. package/src/serve/public/pages/TraceHistory.tsx +478 -0
  137. package/src/serve/public/pages/trace-history-detail.tsx +224 -0
  138. package/src/serve/retrieval-trace.ts +28 -0
  139. package/src/serve/routes/api.ts +508 -68
  140. package/src/serve/routes/traces.ts +156 -0
  141. package/src/serve/server.ts +87 -2
  142. package/src/store/index.ts +31 -0
  143. package/src/store/migrations/014-retrieval-traces.ts +303 -0
  144. package/src/store/migrations/index.ts +2 -0
  145. package/src/store/retrieval-trace-codec.ts +384 -0
  146. package/src/store/sqlite/adapter.ts +153 -1
  147. package/src/store/sqlite/retrieval-trace-management-store.ts +341 -0
  148. package/src/store/sqlite/retrieval-trace-retention.ts +349 -0
  149. package/src/store/sqlite/retrieval-trace-rows.ts +267 -0
  150. package/src/store/sqlite/retrieval-trace-store.ts +515 -0
  151. package/src/store/types.ts +297 -0
  152. package/src/store/vector/sqlite-vec.ts +76 -1
  153. package/src/store/vector/types.ts +1 -1
@@ -24,6 +24,11 @@ import type {
24
24
 
25
25
  import { getDocumentCapabilities } from "../core/document-capabilities";
26
26
  import { isGlobPattern, parseRef, splitRefs } from "../core/ref-parser";
27
+ import {
28
+ attachRetrievalTraceMetadata,
29
+ evidenceFromExactDocument,
30
+ RetrievalTraceSession,
31
+ } from "../core/retrieval-trace-session";
27
32
  import { sdkError } from "./errors";
28
33
 
29
34
  const URI_PREFIX_PATTERN = /^gno:\/\/[^/]+\//;
@@ -72,7 +77,7 @@ function buildConversionMeta(
72
77
  };
73
78
  }
74
79
 
75
- export async function getDocumentByRef(
80
+ async function getDocumentByRefUntraced(
76
81
  store: StorePort,
77
82
  config: Config,
78
83
  ref: string,
@@ -159,6 +164,48 @@ export async function getDocumentByRef(
159
164
  };
160
165
  }
161
166
 
167
+ export async function getDocumentByRef(
168
+ store: StorePort,
169
+ config: Config,
170
+ ref: string,
171
+ options: GnoGetOptions = {}
172
+ ): Promise<GetResponse> {
173
+ const response = await getDocumentByRefUntraced(store, config, ref, options);
174
+ if (!options.traceId) return response;
175
+ const resumed = await RetrievalTraceSession.resume({
176
+ store,
177
+ config: config.retrievalTraces,
178
+ traceId: options.traceId,
179
+ });
180
+ if (!resumed.ok) {
181
+ throw sdkError("STORE", resumed.error.message, {
182
+ cause: resumed.error.cause,
183
+ });
184
+ }
185
+ const traceSession = resumed.value;
186
+ if (!traceSession) return response;
187
+ const lines = response.returnedLines ?? {
188
+ start: 1,
189
+ end: response.totalLines,
190
+ };
191
+ const evidence = evidenceFromExactDocument({
192
+ docid: response.docid,
193
+ uri: response.uri,
194
+ sourceHash: response.source.sourceHash,
195
+ mirrorHash: response.conversion?.mirrorHash,
196
+ content: response.content,
197
+ startLine: lines.start,
198
+ endLine: lines.end,
199
+ });
200
+ if (evidence) {
201
+ const got = await traceSession.recordEvidence("get", [evidence]);
202
+ if (!got.ok) throw sdkError("STORE", got.error.message);
203
+ const opened = await traceSession.recordEvidence("open", [evidence]);
204
+ if (!opened.ok) throw sdkError("STORE", opened.error.message);
205
+ }
206
+ return attachRetrievalTraceMetadata(response, traceSession);
207
+ }
208
+
162
209
  function truncateContent(
163
210
  content: string,
164
211
  maxBytes: number
package/src/sdk/index.ts CHANGED
@@ -72,3 +72,20 @@ export {
72
72
  ContextRuntimeError,
73
73
  type ContextRuntimeErrorCode,
74
74
  } from "../app/context-runtime";
75
+ export {
76
+ getRetrievalTraceMetadata,
77
+ RETRIEVAL_TRACE_METADATA,
78
+ type RetrievalTraceSurfaceMetadata,
79
+ } from "../core/retrieval-trace-session";
80
+ export type {
81
+ RetrievalTraceDeleteResult,
82
+ RetrievalTraceDetail,
83
+ RetrievalTraceExportRequest,
84
+ RetrievalTraceExportResult,
85
+ RetrievalTraceLabelRequest,
86
+ RetrievalTraceLabelResult,
87
+ RetrievalTraceListRequest,
88
+ RetrievalTraceListResult,
89
+ RetrievalTracePurgeResult,
90
+ RetrievalTraceSummary,
91
+ } from "../core/retrieval-trace-management";
package/src/sdk/types.ts CHANGED
@@ -19,6 +19,17 @@ import type { ContextEvidenceErrorCode } from "../core/context-evidence";
19
19
  import type { ContextVerifierErrorCode } from "../core/context-verifier";
20
20
  import type { NoteCollisionPolicy } from "../core/note-creation";
21
21
  import type { NotePresetId } from "../core/note-presets";
22
+ import type {
23
+ RetrievalTraceDeleteResult,
24
+ RetrievalTraceDetail,
25
+ RetrievalTraceExportRequest,
26
+ RetrievalTraceExportResult,
27
+ RetrievalTraceLabelRequest,
28
+ RetrievalTraceLabelResult,
29
+ RetrievalTraceListRequest,
30
+ RetrievalTraceListResult,
31
+ RetrievalTracePurgeResult as RetrievalTraceManagementPurgeResult,
32
+ } from "../core/retrieval-trace-management";
22
33
  import type { DocumentSection } from "../core/sections";
23
34
  import type { SyncResult } from "../ingestion";
24
35
  import type { DownloadPolicy } from "../llm/policy";
@@ -88,6 +99,8 @@ export type GnoContextErrorCode =
88
99
  export interface GnoGetOptions {
89
100
  from?: number;
90
101
  limit?: number;
102
+ /** Continue an open retrieval trace returned by search/query. */
103
+ traceId?: string;
91
104
  }
92
105
 
93
106
  export interface GnoMultiGetOptions {
@@ -221,6 +234,21 @@ export interface GnoClient {
221
234
  options?: GnoListOptions
222
235
  ): Promise<import("../cli/commands/ls").LsResponse>;
223
236
  status(): Promise<IndexStatus>;
237
+ listRetrievalTraces(
238
+ options?: RetrievalTraceListRequest
239
+ ): Promise<RetrievalTraceListResult>;
240
+ getRetrievalTrace(
241
+ traceId: string,
242
+ options?: { detailLimit?: number }
243
+ ): Promise<RetrievalTraceDetail>;
244
+ labelRetrievalTrace(
245
+ input: RetrievalTraceLabelRequest
246
+ ): Promise<RetrievalTraceLabelResult>;
247
+ exportRetrievalTraces(
248
+ input: RetrievalTraceExportRequest
249
+ ): Promise<RetrievalTraceExportResult>;
250
+ deleteRetrievalTrace(traceId: string): Promise<RetrievalTraceDeleteResult>;
251
+ purgeRetrievalTraces(): Promise<RetrievalTraceManagementPurgeResult>;
224
252
  update(options?: GnoUpdateOptions): Promise<SyncResult>;
225
253
  embed(options?: GnoEmbedOptions): Promise<GnoEmbedResult>;
226
254
  index(options?: GnoIndexOptions): Promise<GnoIndexResult>;
@@ -1,5 +1,6 @@
1
1
  /** REST adapters for the shared Context Capsule application boundary. */
2
2
 
3
+ import type { RetrievalTraceSession } from "../core/retrieval-trace-session";
3
4
  import type { GnoContextErrorCode } from "../sdk/types";
4
5
  import type { ServerContext } from "./context";
5
6
 
@@ -20,6 +21,8 @@ import {
20
21
  parseContextVerifySurfaceInput,
21
22
  } from "../app/context-surface";
22
23
  import { ContextCapsuleContractError } from "../core/context-capsule";
24
+ import { startRetrievalTraceRequest } from "../core/retrieval-trace-request";
25
+ import { withRetrievalTraceHeader } from "./retrieval-trace";
23
26
 
24
27
  const JSON_HEADERS = { "content-type": "application/json; charset=utf-8" };
25
28
  const MARKDOWN_HEADERS = {
@@ -80,6 +83,7 @@ export const handleContextBuild = async (
80
83
  context: ServerContext,
81
84
  request: Request
82
85
  ): Promise<Response> => {
86
+ let traceSession: RetrievalTraceSession | null = null;
83
87
  try {
84
88
  const { input, format } = parseContextBuildSurfaceInput(
85
89
  await parseJsonBody(request),
@@ -90,6 +94,42 @@ export const handleContextBuild = async (
90
94
  context.indexName,
91
95
  context.config.collections.map((collection) => collection.name)
92
96
  );
97
+ const started = await startRetrievalTraceRequest({
98
+ store: context.store,
99
+ config: context.config,
100
+ query: input.query ?? input.goal,
101
+ goal: input.goal,
102
+ filters: {
103
+ limit: input.limit,
104
+ collection:
105
+ input.collections?.length === 1 ? input.collections[0] : undefined,
106
+ collections: [...(input.collections ?? [])].sort(),
107
+ lang: input.lang,
108
+ tagsAll: input.tagsAll,
109
+ tagsAny: input.tagsAny,
110
+ since: input.since,
111
+ until: input.until,
112
+ categories: input.categories,
113
+ author: input.author,
114
+ graph: input.graph,
115
+ candidateLimit: input.candidateLimit,
116
+ queryModes: input.queryModes,
117
+ uriPrefix: input.uriPrefix ?? undefined,
118
+ },
119
+ pipeline: "context",
120
+ indexName: context.indexName,
121
+ modelUris: [
122
+ context.embedPort?.modelUri,
123
+ context.rerankPort?.modelUri,
124
+ ].filter((value): value is string => Boolean(value)),
125
+ });
126
+ if (!started.ok) {
127
+ throw Object.assign(
128
+ new Error(`Retrieval trace start failed: ${started.error.message}`),
129
+ { code: "retrieval_failed" }
130
+ );
131
+ }
132
+ traceSession = started.value;
93
133
  const capsule = await buildContextCapsule(input, {
94
134
  store: context.store,
95
135
  config: context.config,
@@ -97,16 +137,35 @@ export const handleContextBuild = async (
97
137
  vectorIndex: context.vectorIndex,
98
138
  embedPort: context.embedPort,
99
139
  rerankPort: context.rerankPort,
140
+ traceSession: traceSession ?? undefined,
100
141
  });
101
- return format === "md"
102
- ? new Response(formatContextCapsuleMarkdown(capsule), {
103
- headers: MARKDOWN_HEADERS,
104
- })
105
- : new Response(canonicalBuiltContextCapsuleJson(capsule), {
106
- headers: JSON_HEADERS,
107
- });
142
+ const finished = await traceSession?.finish(
143
+ request.signal.aborted ? "cancelled" : "completed"
144
+ );
145
+ if (finished && !finished.ok) {
146
+ throw Object.assign(
147
+ new Error(
148
+ `Retrieval trace finalization failed: ${finished.error.message}`
149
+ ),
150
+ { code: "retrieval_failed" }
151
+ );
152
+ }
153
+ const response =
154
+ format === "md"
155
+ ? new Response(formatContextCapsuleMarkdown(capsule), {
156
+ headers: MARKDOWN_HEADERS,
157
+ })
158
+ : new Response(canonicalBuiltContextCapsuleJson(capsule), {
159
+ headers: JSON_HEADERS,
160
+ });
161
+ return withRetrievalTraceHeader(response, traceSession);
108
162
  } catch (error) {
109
- return errorResponse(error);
163
+ if (traceSession) {
164
+ await traceSession.finish(
165
+ request.signal.aborted ? "cancelled" : "failed"
166
+ );
167
+ }
168
+ return withRetrievalTraceHeader(errorResponse(error), traceSession);
110
169
  }
111
170
  };
112
171
 
@@ -29,6 +29,7 @@ import DocumentEditor from "./pages/DocumentEditor";
29
29
  import DocView from "./pages/DocView";
30
30
  import GraphView from "./pages/GraphView";
31
31
  import Search from "./pages/Search";
32
+ import TraceHistory from "./pages/TraceHistory";
32
33
 
33
34
  type Route =
34
35
  | "/"
@@ -39,7 +40,8 @@ type Route =
39
40
  | "/edit"
40
41
  | "/collections"
41
42
  | "/graph"
42
- | "/connectors";
43
+ | "/connectors"
44
+ | "/traces";
43
45
  type Navigate = (to: string | number) => void;
44
46
 
45
47
  interface RoutePageProps {
@@ -57,6 +59,7 @@ const routes: Record<Route, React.ComponentType<RoutePageProps>> = {
57
59
  "/connectors": Connectors,
58
60
  "/ask": Ask,
59
61
  "/graph": GraphView,
62
+ "/traces": TraceHistory,
60
63
  };
61
64
 
62
65
  interface AppContentProps {
@@ -165,6 +168,14 @@ function AppContent({
165
168
  Collections
166
169
  </button>
167
170
  <span className="text-border/30">—</span>
171
+ <button
172
+ className="transition-colors duration-300 hover:text-primary"
173
+ onClick={() => navigate("/traces")}
174
+ type="button"
175
+ >
176
+ Trace history
177
+ </button>
178
+ <span className="text-border/30">—</span>
168
179
  <a
169
180
  className="transition-colors duration-300 hover:text-primary"
170
181
  href="https://github.com/gmickel/gno"
@@ -0,0 +1,189 @@
1
+ import { AlertTriangle, CheckCircle2, CircleHelp, ShieldX } from "lucide-react";
2
+
3
+ import type { AskResult } from "../../../pipeline/types";
4
+
5
+ import { cn } from "../lib/utils";
6
+ import { Badge } from "./ui/badge";
7
+
8
+ export type AskVerification = NonNullable<AskResult["verification"]>;
9
+
10
+ interface AskVerificationPanelProps {
11
+ navigate: (to: string) => void;
12
+ verification: AskVerification;
13
+ }
14
+
15
+ const STATUS_PRESENTATION = {
16
+ supported: {
17
+ label: "supported",
18
+ icon: CheckCircle2,
19
+ className: "border-emerald-500/30 bg-emerald-500/10 text-emerald-300",
20
+ },
21
+ contradicted: {
22
+ label: "contradicted",
23
+ icon: ShieldX,
24
+ className: "border-destructive/40 bg-destructive/10 text-destructive",
25
+ },
26
+ insufficient: {
27
+ label: "insufficient",
28
+ icon: AlertTriangle,
29
+ className: "border-amber-500/30 bg-amber-500/10 text-amber-300",
30
+ },
31
+ uncertain: {
32
+ label: "uncertain",
33
+ icon: CircleHelp,
34
+ className: "border-sky-500/30 bg-sky-500/10 text-sky-300",
35
+ },
36
+ } as const;
37
+
38
+ const evidenceLabel = (uri: string, startLine: number, endLine: number) =>
39
+ `${uri}:L${startLine}${startLine === endLine ? "" : `-L${endLine}`}`;
40
+
41
+ export function AskVerificationPanel({
42
+ navigate,
43
+ verification,
44
+ }: AskVerificationPanelProps) {
45
+ const { claims, capsule, semantic } = verification;
46
+ const degradedCapabilities = Object.entries(
47
+ capsule.retrieval.capabilityStates
48
+ ).filter(([, state]) => state.requested && state.outcome !== "used");
49
+ const gaps = capsule.coverage.gaps;
50
+
51
+ return (
52
+ <details className="group rounded-lg border border-border/50 bg-card/55">
53
+ <summary className="flex cursor-pointer list-none items-center justify-between gap-3 px-4 py-3">
54
+ <div className="min-w-0">
55
+ <div className="flex flex-wrap items-center gap-2">
56
+ <span className="font-medium text-sm">
57
+ {claims.answerStatus === "verified"
58
+ ? "All claims verified"
59
+ : "Answer withheld"}
60
+ </span>
61
+ <Badge
62
+ className="font-mono text-[9px]"
63
+ variant={
64
+ claims.answerStatus === "verified" ? "default" : "outline"
65
+ }
66
+ >
67
+ {claims.coverage.supportedClaims}/{claims.coverage.totalClaims}{" "}
68
+ supported
69
+ </Badge>
70
+ </div>
71
+ <p className="mt-1 text-muted-foreground text-xs">
72
+ {claims.abstentionReason ??
73
+ `${claims.coverage.supportedRatio * 100}% support coverage`}
74
+ </p>
75
+ </div>
76
+ <span className="shrink-0 font-mono text-[10px] text-muted-foreground group-open:hidden">
77
+ details
78
+ </span>
79
+ </summary>
80
+
81
+ <div className="space-y-4 border-border/40 border-t px-4 py-4">
82
+ <div className="flex flex-wrap gap-2">
83
+ <Badge className="font-mono text-[9px]" variant="outline">
84
+ semantic: {semantic.status}/{semantic.reason}
85
+ </Badge>
86
+ {degradedCapabilities.map(([name, state]) => (
87
+ <Badge
88
+ className="font-mono text-[9px]"
89
+ key={name}
90
+ variant="outline"
91
+ >
92
+ {name}: {state.outcome}
93
+ {state.fallbackReasons.length > 0
94
+ ? ` (${state.fallbackReasons.join(", ")})`
95
+ : ""}
96
+ </Badge>
97
+ ))}
98
+ </div>
99
+
100
+ <div className="grid grid-cols-2 gap-2 font-mono text-[10px] text-muted-foreground sm:grid-cols-4">
101
+ <span>{claims.coverage.supportedClaims} supported</span>
102
+ <span>{claims.coverage.contradictedClaims} contradicted</span>
103
+ <span>{claims.coverage.insufficientClaims} insufficient</span>
104
+ <span>{claims.coverage.uncertainClaims} uncertain</span>
105
+ </div>
106
+
107
+ <div className="space-y-2">
108
+ {claims.claims.map((claim) => {
109
+ const presentation = STATUS_PRESENTATION[claim.status];
110
+ const StatusIcon = presentation.icon;
111
+ return (
112
+ <details
113
+ className="rounded-md border border-border/35 bg-background/20"
114
+ key={claim.claimId}
115
+ >
116
+ <summary className="flex cursor-pointer list-none items-start gap-2 px-3 py-2">
117
+ <StatusIcon className="mt-0.5 size-3.5 shrink-0" />
118
+ <span className="min-w-0 flex-1 text-xs">{claim.text}</span>
119
+ <Badge
120
+ className={cn(
121
+ "shrink-0 font-mono text-[9px]",
122
+ presentation.className
123
+ )}
124
+ variant="outline"
125
+ >
126
+ {presentation.label}
127
+ </Badge>
128
+ </summary>
129
+ <div className="space-y-2 border-border/30 border-t px-3 py-2">
130
+ {claim.evidence.length > 0 ? (
131
+ claim.evidence.map((evidence) => (
132
+ <button
133
+ className="block w-full truncate text-left font-mono text-[10px] text-primary hover:underline"
134
+ key={evidence.evidenceId}
135
+ onClick={() =>
136
+ navigate(
137
+ `/doc?uri=${encodeURIComponent(evidence.uri)}`
138
+ )
139
+ }
140
+ title={evidenceLabel(
141
+ evidence.uri,
142
+ evidence.startLine,
143
+ evidence.endLine
144
+ )}
145
+ type="button"
146
+ >
147
+ {evidenceLabel(
148
+ evidence.uri,
149
+ evidence.startLine,
150
+ evidence.endLine
151
+ )}
152
+ </button>
153
+ ))
154
+ ) : (
155
+ <p className="text-muted-foreground text-xs">
156
+ No retained evidence span.
157
+ </p>
158
+ )}
159
+ <p className="font-mono text-[10px] text-muted-foreground">
160
+ {claim.rationaleCode}
161
+ {claim.confidence === null
162
+ ? ""
163
+ : ` · confidence ${claim.confidence.toFixed(2)}`}
164
+ </p>
165
+ </div>
166
+ </details>
167
+ );
168
+ })}
169
+ </div>
170
+
171
+ {(capsule.coverage.unresolvedFacets.length > 0 || gaps.length > 0) && (
172
+ <div className="rounded-md border border-amber-500/25 bg-amber-500/5 px-3 py-2">
173
+ <p className="font-medium text-amber-200 text-xs">Evidence gaps</p>
174
+ <ul className="mt-1 space-y-1 font-mono text-[10px] text-muted-foreground">
175
+ {capsule.coverage.unresolvedFacets.map((facet) => (
176
+ <li key={`facet:${facet}`}>unresolved facet: {facet}</li>
177
+ ))}
178
+ {gaps.map((gap) => (
179
+ <li key={`${gap.facet}:${gap.code}`}>
180
+ {gap.facet}: {gap.code}
181
+ </li>
182
+ ))}
183
+ </ul>
184
+ </div>
185
+ )}
186
+ </div>
187
+ </details>
188
+ );
189
+ }