@sanity/ailf 4.1.0 → 4.3.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 (126) hide show
  1. package/config/package-surface.ts +37 -0
  2. package/config/preflight-scoring.ts +26 -0
  3. package/dist/_vendor/ailf-core/artifact-registry.d.ts +1 -1
  4. package/dist/_vendor/ailf-core/artifact-registry.js +47 -0
  5. package/dist/_vendor/ailf-core/config-helpers.d.ts +35 -0
  6. package/dist/_vendor/ailf-core/config-helpers.js +67 -0
  7. package/dist/_vendor/ailf-core/index.d.ts +1 -1
  8. package/dist/_vendor/ailf-core/index.js +1 -1
  9. package/dist/_vendor/ailf-core/ports/context.d.ts +18 -0
  10. package/dist/_vendor/ailf-core/ports/doc-fetcher.d.ts +30 -0
  11. package/dist/_vendor/ailf-core/ports/index.d.ts +3 -1
  12. package/dist/_vendor/ailf-core/ports/index.js +1 -0
  13. package/dist/_vendor/ailf-core/ports/mode-handler.d.ts +23 -0
  14. package/dist/_vendor/ailf-core/ports/package-surface-resolver.d.ts +71 -0
  15. package/dist/_vendor/ailf-core/ports/package-surface-resolver.js +36 -0
  16. package/dist/_vendor/ailf-core/schemas/eval-config.d.ts +6 -0
  17. package/dist/_vendor/ailf-core/schemas/eval-config.js +14 -0
  18. package/dist/_vendor/ailf-core/schemas/index.d.ts +1 -0
  19. package/dist/_vendor/ailf-core/schemas/index.js +1 -0
  20. package/dist/_vendor/ailf-core/schemas/symbol-preflight-report.d.ts +51 -0
  21. package/dist/_vendor/ailf-core/schemas/symbol-preflight-report.js +57 -0
  22. package/dist/_vendor/ailf-core/types/generalized-task.d.ts +20 -3
  23. package/dist/_vendor/ailf-core/types/index.d.ts +13 -1
  24. package/dist/_vendor/ailf-core/types/index.js +1 -0
  25. package/dist/_vendor/ailf-core/types/package-surface.d.ts +36 -0
  26. package/dist/_vendor/ailf-core/types/package-surface.js +13 -0
  27. package/dist/_vendor/ailf-core/types/preflight-scoring.d.ts +52 -0
  28. package/dist/_vendor/ailf-core/types/preflight-scoring.js +18 -0
  29. package/dist/_vendor/ailf-core/types/repo-config.d.ts +14 -0
  30. package/dist/_vendor/ailf-core/types/symbol-preflight-report.d.ts +66 -0
  31. package/dist/_vendor/ailf-core/types/symbol-preflight-report.js +25 -0
  32. package/dist/adapters/config-sources/file-config-adapter.js +1 -0
  33. package/dist/adapters/doc-fetchers/sanity-doc-fetcher.d.ts +25 -5
  34. package/dist/adapters/doc-fetchers/sanity-doc-fetcher.js +276 -95
  35. package/dist/adapters/index.d.ts +1 -0
  36. package/dist/adapters/index.js +1 -0
  37. package/dist/adapters/package-surface/dts-package-surface.d.ts +46 -0
  38. package/dist/adapters/package-surface/dts-package-surface.js +173 -0
  39. package/dist/adapters/package-surface/in-memory-package-surface.d.ts +15 -0
  40. package/dist/adapters/package-surface/in-memory-package-surface.js +28 -0
  41. package/dist/adapters/package-surface/index.d.ts +9 -0
  42. package/dist/adapters/package-surface/index.js +8 -0
  43. package/dist/adapters/package-surface/parse-dts-exports.d.ts +31 -0
  44. package/dist/adapters/package-surface/parse-dts-exports.js +54 -0
  45. package/dist/adapters/task-sources/repo-schemas.d.ts +22 -0
  46. package/dist/adapters/task-sources/repo-schemas.js +93 -1
  47. package/dist/adapters/task-sources/repo-task-source.js +11 -2
  48. package/dist/commands/pipeline-action.d.ts +2 -0
  49. package/dist/commands/pipeline-action.js +12 -0
  50. package/dist/commands/remote-pipeline.js +9 -2
  51. package/dist/commands/remote-results.d.ts +12 -1
  52. package/dist/commands/remote-results.js +25 -5
  53. package/dist/commands/validate-tasks.js +8 -2
  54. package/dist/composition-root.js +9 -0
  55. package/dist/config/package-surface.ts +37 -0
  56. package/dist/config/preflight-scoring.ts +26 -0
  57. package/dist/index.d.ts +2 -2
  58. package/dist/index.js +1 -1
  59. package/dist/orchestration/build-app-context.js +1 -0
  60. package/dist/orchestration/pipeline-orchestrator.d.ts +19 -1
  61. package/dist/orchestration/pipeline-orchestrator.js +38 -0
  62. package/dist/orchestration/steps/calculate-scores-step.js +11 -0
  63. package/dist/orchestration/steps/generate-configs-step.js +16 -1
  64. package/dist/orchestration/steps/run-eval-step.js +27 -0
  65. package/dist/pipeline/calculate-scores.d.ts +66 -5
  66. package/dist/pipeline/calculate-scores.js +141 -27
  67. package/dist/pipeline/compiler/index.d.ts +1 -1
  68. package/dist/pipeline/compiler/index.js +1 -1
  69. package/dist/pipeline/compiler/literacy-bridge.d.ts +9 -0
  70. package/dist/pipeline/compiler/literacy-bridge.js +2 -0
  71. package/dist/pipeline/compiler/mode-handlers/__fixtures__/agent-harness-example-tasks.js +0 -12
  72. package/dist/pipeline/compiler/mode-handlers/__fixtures__/knowledge-probe-example-tasks.js +0 -12
  73. package/dist/pipeline/compiler/mode-handlers/literacy/assertions.d.ts +1 -1
  74. package/dist/pipeline/compiler/mode-handlers/literacy/assertions.js +31 -4
  75. package/dist/pipeline/compiler/mode-handlers/literacy/compiler.js +190 -6
  76. package/dist/pipeline/compiler/mode-handlers/literacy/index.js +2 -0
  77. package/dist/pipeline/compiler/mode-handlers/literacy/types.d.ts +17 -2
  78. package/dist/pipeline/compiler/rubric-resolution.d.ts +17 -1
  79. package/dist/pipeline/compiler/rubric-resolution.js +78 -2
  80. package/dist/pipeline/compiler/scoring-bridge.d.ts +49 -2
  81. package/dist/pipeline/compiler/scoring-bridge.js +104 -10
  82. package/dist/pipeline/eval-fingerprint.d.ts +9 -0
  83. package/dist/pipeline/eval-fingerprint.js +7 -1
  84. package/dist/pipeline/preflight/compute-preflight.d.ts +67 -0
  85. package/dist/pipeline/preflight/compute-preflight.js +118 -0
  86. package/dist/pipeline/preflight/emit-symbol-preflight.d.ts +51 -0
  87. package/dist/pipeline/preflight/emit-symbol-preflight.js +102 -0
  88. package/dist/pipeline/preflight/load-package-surface.d.ts +14 -0
  89. package/dist/pipeline/preflight/load-package-surface.js +19 -0
  90. package/dist/pipeline/preflight/load-preflight-context.d.ts +13 -0
  91. package/dist/pipeline/preflight/load-preflight-context.js +25 -0
  92. package/dist/pipeline/preflight/load-preflight-scoring.d.ts +12 -0
  93. package/dist/pipeline/preflight/load-preflight-scoring.js +17 -0
  94. package/dist/pipeline/preflight/parse-imports.d.ts +62 -0
  95. package/dist/pipeline/preflight/parse-imports.js +125 -0
  96. package/dist/report-store.d.ts +8 -0
  97. package/dist/report-store.js +55 -6
  98. package/dist/sanity/document-renderers.d.ts +106 -0
  99. package/dist/sanity/document-renderers.js +307 -0
  100. package/dist/sanity/queries.d.ts +32 -11
  101. package/dist/sanity/queries.js +78 -0
  102. package/dist/sanity/symbol-index.d.ts +98 -0
  103. package/dist/sanity/symbol-index.js +615 -0
  104. package/dist/tasks/knowledge-probe/define-type-api.task.ts +2 -6
  105. package/dist/tasks/knowledge-probe/groq-projections.task.ts +0 -5
  106. package/dist/tasks/literacy/content-lake.task.ts +4 -10
  107. package/dist/tasks/literacy/frameworks.task.ts +2 -8
  108. package/dist/tasks/literacy/functions.task.ts +1 -4
  109. package/dist/tasks/literacy/groq.task.ts +3 -12
  110. package/dist/tasks/literacy/image-handling.task.ts +1 -4
  111. package/dist/tasks/literacy/nextjs-live.task.ts +1 -4
  112. package/dist/tasks/literacy/portable-text.task.ts +2 -8
  113. package/dist/tasks/literacy/studio-setup.task.ts +2 -8
  114. package/dist/tasks/literacy/visual-editing.task.ts +2 -8
  115. package/package.json +2 -1
  116. package/tasks/knowledge-probe/define-type-api.task.ts +2 -6
  117. package/tasks/knowledge-probe/groq-projections.task.ts +0 -5
  118. package/tasks/literacy/content-lake.task.ts +4 -10
  119. package/tasks/literacy/frameworks.task.ts +2 -8
  120. package/tasks/literacy/functions.task.ts +1 -4
  121. package/tasks/literacy/groq.task.ts +3 -12
  122. package/tasks/literacy/image-handling.task.ts +1 -4
  123. package/tasks/literacy/nextjs-live.task.ts +1 -4
  124. package/tasks/literacy/portable-text.task.ts +2 -8
  125. package/tasks/literacy/studio-setup.task.ts +2 -8
  126. package/tasks/literacy/visual-editing.task.ts +2 -8
@@ -0,0 +1,307 @@
1
+ /**
2
+ * document-renderers.ts
3
+ *
4
+ * Renderer registry that turns a Sanity document into both:
5
+ *
6
+ * - Markdown content for inclusion in a literacy task's grader/candidate
7
+ * context (existing surface, used by the doc fetcher).
8
+ * - A symbol-reference index for the W0197 grader-context pathway —
9
+ * a flat list of identifiers the doc legitimizes, with provenance.
10
+ * The grader prefers this over the rendered markdown when available
11
+ * (smaller, deterministic, harder for the grader's prior to override).
12
+ *
13
+ * Both surfaces dispatch through the same registry. Articles and
14
+ * typesReference docs have hand-written renderers; everything else falls
15
+ * through to the default walker. This keeps "what to do with a document
16
+ * of type X" a single decision point regardless of whether the doc was
17
+ * looked up by slug, path, perspective, or id.
18
+ *
19
+ * Two tiers of fidelity (rendered output):
20
+ *
21
+ * 1. Registered renderers (high fidelity) — `article`, `typesReference`.
22
+ * Hand-written for the document shapes we care about most.
23
+ * 2. Default renderer (best effort) — walks the doc, flattens portable-text
24
+ * fields, surfaces top-level scalars, follows references one level deep,
25
+ * skips framework-internal fields. Lets pinning a `marketingPage`,
26
+ * `glossaryEntry`, etc. work without AILF code changes.
27
+ *
28
+ * Adding a new high-fidelity renderer: implement a `DocumentRenderer`
29
+ * (both `render` and `extractSymbols`) and register it in
30
+ * `BUILT_IN_RENDERERS` keyed by `_type`.
31
+ */
32
+ import { toMarkdown } from "./portable-text.js";
33
+ import { extractSymbolIndex, extractSymbolsFromTypedoc, mergeSymbolIndexes, } from "./symbol-index.js";
34
+ // ---------------------------------------------------------------------------
35
+ // Helpers
36
+ // ---------------------------------------------------------------------------
37
+ const DEFAULT_MAX_BYTES = 30_000;
38
+ const SKIP_FIELDS = new Set([
39
+ "_createdAt",
40
+ "_id",
41
+ "_rev",
42
+ "_system",
43
+ "_type",
44
+ "_updatedAt",
45
+ "_key",
46
+ ]);
47
+ function isPortableTextArray(value) {
48
+ return (Array.isArray(value) &&
49
+ value.length > 0 &&
50
+ typeof value[0] === "object" &&
51
+ value[0] !== null &&
52
+ "_type" in value[0]);
53
+ }
54
+ function truncate(content, max) {
55
+ if (content.length <= max)
56
+ return content;
57
+ return (content.slice(0, max) +
58
+ `\n\n*[content truncated — ${content.length - max} more bytes omitted]*`);
59
+ }
60
+ function slugForDoc(doc) {
61
+ const slugField = doc.slug;
62
+ if (typeof slugField === "object" &&
63
+ slugField !== null &&
64
+ "current" in slugField &&
65
+ typeof slugField.current === "string") {
66
+ return slugField.current;
67
+ }
68
+ if (typeof slugField === "string")
69
+ return slugField;
70
+ return `${doc._type}:${doc._id}`;
71
+ }
72
+ function renderArticle(doc) {
73
+ const title = doc.title ?? "(untitled)";
74
+ const description = doc.description;
75
+ const section = doc.section;
76
+ const content = doc.content;
77
+ const sectionLabel = section?.title ? `Section: ${section.title}\n` : "";
78
+ const desc = description ? `${description}\n\n` : "";
79
+ const markdown = toMarkdown(content ?? []);
80
+ return {
81
+ content: `## ${title}\n\n${sectionLabel}${desc}${markdown}`,
82
+ fidelity: "high",
83
+ slug: slugForDoc(doc),
84
+ };
85
+ }
86
+ const articleRenderer = {
87
+ render: renderArticle,
88
+ extractSymbols(doc) {
89
+ const content = doc.content;
90
+ if (!Array.isArray(content))
91
+ return { symbols: [] };
92
+ return extractSymbolIndex(content);
93
+ },
94
+ };
95
+ async function renderTypesReference(doc, ctx) {
96
+ const title = doc.title ?? "(untitled)";
97
+ const slug = slugForDoc(doc);
98
+ const library = doc.library;
99
+ const latestVersion = doc.latestVersion;
100
+ const asset = latestVersion?.attachment?.asset;
101
+ const max = ctx.maxBytes ?? DEFAULT_MAX_BYTES;
102
+ const lines = [];
103
+ lines.push(`## ${title}`, "");
104
+ lines.push(`Slug: \`${slug}\``);
105
+ if (library?.npmName)
106
+ lines.push(`Package: \`${library.npmName}\``);
107
+ if (latestVersion?.semver)
108
+ lines.push(`Version: \`${latestVersion.semver}\``);
109
+ if (latestVersion?.date)
110
+ lines.push(`Released: ${latestVersion.date}`);
111
+ lines.push("");
112
+ if (!asset?.url) {
113
+ lines.push("*[no attached type definitions found — resolver could not locate `latestVersion.attachment.asset.url`]*");
114
+ return { content: lines.join("\n"), fidelity: "high", slug };
115
+ }
116
+ if (!ctx.fetchUrl) {
117
+ lines.push(`*[type definitions live at ${asset.url} — fetcher not configured to retrieve attachments]*`);
118
+ return { content: lines.join("\n"), fidelity: "high", slug };
119
+ }
120
+ const body = await ctx.fetchUrl(asset.url);
121
+ if (body === null) {
122
+ lines.push(`*[failed to fetch type definitions from ${asset.url}]*`);
123
+ return { content: lines.join("\n"), fidelity: "high", slug };
124
+ }
125
+ const filename = asset.originalFilename ?? "types.json";
126
+ const lang = filename.endsWith(".json") ? "json" : "";
127
+ lines.push(`### Type definitions (${filename})`, "");
128
+ lines.push("```" + lang);
129
+ // Reserve ~200 bytes for the fence + trailing notice
130
+ lines.push(truncate(body, Math.max(0, max - 200)));
131
+ lines.push("```");
132
+ return { content: lines.join("\n"), fidelity: "high", slug };
133
+ }
134
+ async function extractTypesReferenceSymbols(doc, ctx) {
135
+ const library = doc.library;
136
+ const latestVersion = doc.latestVersion;
137
+ const asset = latestVersion?.attachment?.asset;
138
+ // Re-fetches the same URL the renderer would; bounded to ≤ a handful of
139
+ // typesReference docs per eval run, so the duplicate I/O is acceptable.
140
+ // Fold into a single registry method later if profiling shows it bites.
141
+ if (!asset?.url || !ctx.fetchUrl)
142
+ return { symbols: [] };
143
+ const body = await ctx.fetchUrl(asset.url);
144
+ if (body === null)
145
+ return { symbols: [] };
146
+ return extractSymbolsFromTypedoc(body, library?.npmName);
147
+ }
148
+ const typesReferenceRenderer = {
149
+ render: renderTypesReference,
150
+ extractSymbols: extractTypesReferenceSymbols,
151
+ };
152
+ // ---------------------------------------------------------------------------
153
+ // formatDefault — generic walker for any unknown `_type`.
154
+ //
155
+ // Walks top-level fields, renders portable-text arrays as Markdown, surfaces
156
+ // scalars as labeled key/value lines, and follows resolved references one
157
+ // level deep. The output is "best effort" — not as tight as a hand-written
158
+ // renderer, but feeds the LLM grader real content for any pinned doc type.
159
+ // ---------------------------------------------------------------------------
160
+ function renderScalar(value) {
161
+ if (typeof value === "string")
162
+ return value;
163
+ if (typeof value === "number" || typeof value === "boolean") {
164
+ return String(value);
165
+ }
166
+ return null;
167
+ }
168
+ function renderField(key, value, depth = 0) {
169
+ if (value === null || value === undefined)
170
+ return null;
171
+ if (SKIP_FIELDS.has(key))
172
+ return null;
173
+ const indent = " ".repeat(depth);
174
+ if (isPortableTextArray(value)) {
175
+ const md = toMarkdown(value);
176
+ if (!md.trim())
177
+ return null;
178
+ return `${indent}**${key}:**\n\n${md}`;
179
+ }
180
+ // Sanity slug field: { _type: "slug", current: "…" }
181
+ if (typeof value === "object" &&
182
+ value !== null &&
183
+ !Array.isArray(value) &&
184
+ value._type === "slug") {
185
+ const current = value.current;
186
+ if (typeof current === "string")
187
+ return `${indent}**${key}:** \`${current}\``;
188
+ return null;
189
+ }
190
+ const scalar = renderScalar(value);
191
+ if (scalar !== null)
192
+ return `${indent}**${key}:** ${scalar}`;
193
+ // Single-level deref: a previously-resolved reference shows up as an
194
+ // object with its own `_id`/`_type` (the projection joined it in). Walk
195
+ // its fields one level deep.
196
+ if (typeof value === "object" &&
197
+ !Array.isArray(value) &&
198
+ depth === 0 &&
199
+ value._id !== undefined) {
200
+ const inner = Object.entries(value)
201
+ .map(([k, v]) => renderField(k, v, depth + 1))
202
+ .filter((line) => line !== null);
203
+ if (inner.length === 0)
204
+ return null;
205
+ return `${indent}**${key}:**\n${inner.join("\n")}`;
206
+ }
207
+ if (Array.isArray(value)) {
208
+ const items = value
209
+ .map((item, i) => renderField(String(i), item, depth + 1))
210
+ .filter((line) => line !== null);
211
+ if (items.length === 0)
212
+ return null;
213
+ return `${indent}**${key}:**\n${items.join("\n")}`;
214
+ }
215
+ return null;
216
+ }
217
+ function renderDefault(doc) {
218
+ const title = doc.title ?? `(${doc._type})`;
219
+ const slug = slugForDoc(doc);
220
+ const lines = [`## ${title}`, "", `Type: \`${doc._type}\``];
221
+ if (slug !== `${doc._type}:${doc._id}`)
222
+ lines.push(`Slug: \`${slug}\``);
223
+ lines.push("");
224
+ const fieldLines = [];
225
+ for (const [key, value] of Object.entries(doc)) {
226
+ if (key === "title")
227
+ continue;
228
+ if (key === "slug" && slug !== `${doc._type}:${doc._id}`)
229
+ continue;
230
+ const rendered = renderField(key, value);
231
+ if (rendered !== null)
232
+ fieldLines.push(rendered);
233
+ }
234
+ if (fieldLines.length > 0) {
235
+ lines.push(...fieldLines);
236
+ }
237
+ else {
238
+ lines.push("*[no renderable content — all fields were null, framework metadata, or unrecognized shapes]*");
239
+ }
240
+ return { content: lines.join("\n"), fidelity: "default", slug };
241
+ }
242
+ function extractDefaultSymbols(doc) {
243
+ // For unknown types we don't know the doc's intent — but if it has any
244
+ // Portable Text fields, those probably contain prose-with-inline-code
245
+ // that names symbols. Walk top-level fields, run the PT extractor on
246
+ // each PT array, and tag each extracted entry with the originating
247
+ // field name so reviewers can trace which field a symbol came from.
248
+ // Returns empty for shapes with no PT content (purely scalar docs like
249
+ // `marketingPage`); caller falls back to the rendered markdown.
250
+ const indexes = [];
251
+ for (const [key, value] of Object.entries(doc)) {
252
+ if (SKIP_FIELDS.has(key))
253
+ continue;
254
+ if (isPortableTextArray(value)) {
255
+ indexes.push(tagWithFieldName(extractSymbolIndex(value), key));
256
+ }
257
+ }
258
+ return mergeSymbolIndexes(indexes);
259
+ }
260
+ function tagWithFieldName(index, fieldName) {
261
+ return {
262
+ symbols: index.symbols.map((entry) => ({
263
+ symbol: entry.symbol,
264
+ provenance: {
265
+ ...entry.provenance,
266
+ snippet: `[${fieldName}] ${entry.provenance.snippet}`,
267
+ },
268
+ })),
269
+ };
270
+ }
271
+ const defaultRenderer = {
272
+ render: renderDefault,
273
+ extractSymbols: extractDefaultSymbols,
274
+ };
275
+ // ---------------------------------------------------------------------------
276
+ // Registry
277
+ // ---------------------------------------------------------------------------
278
+ const BUILT_IN_RENDERERS = {
279
+ article: articleRenderer,
280
+ typesReference: typesReferenceRenderer,
281
+ };
282
+ function rendererFor(type) {
283
+ return BUILT_IN_RENDERERS[type] ?? defaultRenderer;
284
+ }
285
+ /**
286
+ * Render a document using the registered renderer for its `_type`, falling
287
+ * back to the default walker. The returned `fidelity` flag tells callers
288
+ * whether to emit the "info: dedicated renderer would help" log.
289
+ */
290
+ export async function renderDocument(doc, ctx = {}) {
291
+ return rendererFor(doc._type).render(doc, ctx);
292
+ }
293
+ /**
294
+ * Extract a symbol-reference index for a document using its registered
295
+ * renderer (or the default walker for unknown types). Used by the
296
+ * grader-context pathway (W0197) to feed the LLM judge a compact
297
+ * deterministic recognition reference instead of the full rendered doc.
298
+ *
299
+ * Returns an empty `SymbolIndex` (`{ symbols: [] }`) when extraction
300
+ * yields nothing — callers interpret this as the signal to fall back to
301
+ * the rendered markdown.
302
+ */
303
+ export async function extractSymbolsForDoc(doc, ctx = {}) {
304
+ return rendererFor(doc._type).extractSymbols(doc, ctx);
305
+ }
306
+ /** Exported for tests and consumers that want the registered set. */
307
+ export const REGISTERED_RENDERER_TYPES = Object.keys(BUILT_IN_RENDERERS).sort();
@@ -20,34 +20,34 @@ export declare const FEATURE_AREA_QUERIES: {
20
20
  /**
21
21
  * Other Frameworks — Nuxt, React Router/Remix, Astro
22
22
  */
23
- readonly frameworks: "\n *[_type == \"article\"\n && !(_id in path(\"drafts.**\"))\n && (\n primarySection._ref in [\n \"b2c937d0-34b3-435e-818b-d0c8520ea9b8\",\n \"e223241c-e207-41e0-8e12-f80e78203cf1\",\n \"73bebb61-e508-42b7-b44b-c4703ae42d01\"\n ]\n || (primarySection._ref == \"ea6b80fa-d0d2-4daa-8220-eb8abdcc9deb\"\n && (title match \"Remix*\" || title match \"Nuxt*\"\n || title match \"Astro*\" || title match \"SvelteKit*\"))\n )\n ] {\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n ";
23
+ readonly frameworks: "\n *[_type == \"article\"\n && !(_id in path(\"drafts.**\"))\n && (\n primarySection._ref in [\n \"b2c937d0-34b3-435e-818b-d0c8520ea9b8\",\n \"e223241c-e207-41e0-8e12-f80e78203cf1\",\n \"73bebb61-e508-42b7-b44b-c4703ae42d01\"\n ]\n || (primarySection._ref == \"ea6b80fa-d0d2-4daa-8220-eb8abdcc9deb\"\n && (title match \"Remix*\" || title match \"Nuxt*\"\n || title match \"Astro*\" || title match \"SvelteKit*\"))\n )\n ] {\n _id,\n _type,\n _rev,\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n ";
24
24
  /**
25
25
  * Functions / Compute / AI — serverless functions, webhooks
26
26
  * Section: "Compute and AI" (12 articles)
27
27
  */
28
- readonly functions: "\n *[_type == \"article\"\n && primarySection._ref == \"3024ce79-c196-49ee-a237-a327d6a6348f\"\n && !(_id in path(\"drafts.**\"))\n ] {\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n ";
28
+ readonly functions: "\n *[_type == \"article\"\n && primarySection._ref == \"3024ce79-c196-49ee-a237-a327d6a6348f\"\n && !(_id in path(\"drafts.**\"))\n ] {\n _id,\n _type,\n _rev,\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n ";
29
29
  /**
30
30
  * GROQ Query Language — introduction, syntax, joins, filtering, projections
31
31
  * Combines: Content Lake section GROQ articles + Developer Guides articles
32
32
  * about querying + title-matched GROQ articles across all sections.
33
33
  */
34
- readonly groq: "\n *[_type == \"article\"\n && !(_id in path(\"drafts.**\"))\n && (\n primarySection._ref == \"60a6bcb5-9706-4cab-9153-57725e28f4d0\"\n || primarySection._ref == \"ea6b80fa-d0d2-4daa-8220-eb8abdcc9deb\"\n )\n && (\n title match \"GROQ*\"\n || title match \"*GROQ*\"\n || title match \"Query*\"\n || title match \"How Queries*\"\n || slug.current match \"groq-*\"\n || slug.current match \"query-*\"\n || slug.current match \"how-queries-*\"\n || slug.current match \"paginating-with-groq\"\n || slug.current match \"high-performance-groq\"\n )\n ] {\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n ";
34
+ readonly groq: "\n *[_type == \"article\"\n && !(_id in path(\"drafts.**\"))\n && (\n primarySection._ref == \"60a6bcb5-9706-4cab-9153-57725e28f4d0\"\n || primarySection._ref == \"ea6b80fa-d0d2-4daa-8220-eb8abdcc9deb\"\n )\n && (\n title match \"GROQ*\"\n || title match \"*GROQ*\"\n || title match \"Query*\"\n || title match \"How Queries*\"\n || slug.current match \"groq-*\"\n || slug.current match \"query-*\"\n || slug.current match \"how-queries-*\"\n || slug.current match \"paginating-with-groq\"\n || slug.current match \"high-performance-groq\"\n )\n ] {\n _id,\n _type,\n _rev,\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n ";
35
35
  /**
36
36
  * Next.js integration + Live Content API
37
37
  * Combines: Next.js quickstart section + developer-guide articles
38
38
  * mentioning Next.js or Live Content.
39
39
  */
40
- readonly "nextjs-live": "\n *[_type == \"article\"\n && !(_id in path(\"drafts.**\"))\n && (\n primarySection._ref == \"6208dff1-bba7-487a-a6bb-a0ffccb5846c\"\n || (primarySection._ref == \"ea6b80fa-d0d2-4daa-8220-eb8abdcc9deb\"\n && (title match \"Next*\" || title match \"*Live*\"))\n )\n ] {\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n ";
40
+ readonly "nextjs-live": "\n *[_type == \"article\"\n && !(_id in path(\"drafts.**\"))\n && (\n primarySection._ref == \"6208dff1-bba7-487a-a6bb-a0ffccb5846c\"\n || (primarySection._ref == \"ea6b80fa-d0d2-4daa-8220-eb8abdcc9deb\"\n && (title match \"Next*\" || title match \"*Live*\"))\n )\n ] {\n _id,\n _type,\n _rev,\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n ";
41
41
  /**
42
42
  * Studio Setup & Customization
43
43
  * Section: "Studio" (116 articles)
44
44
  */
45
- readonly "studio-setup": "\n *[_type == \"article\"\n && primarySection._ref == \"d67e3879-0342-4a80-8a2d-e35908df35cc\"\n && !(_id in path(\"drafts.**\"))\n ] {\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n ";
45
+ readonly "studio-setup": "\n *[_type == \"article\"\n && primarySection._ref == \"d67e3879-0342-4a80-8a2d-e35908df35cc\"\n && !(_id in path(\"drafts.**\"))\n ] {\n _id,\n _type,\n _rev,\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n ";
46
46
  /**
47
47
  * Visual Editing — Presentation tool, overlays, live preview
48
48
  * Section: "Visual Editing" (24 articles)
49
49
  */
50
- readonly "visual-editing": "\n *[_type == \"article\"\n && primarySection._ref == \"4e0ef463-01e2-48db-9a31-38b3912ececd\"\n && !(_id in path(\"drafts.**\"))\n ] {\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n ";
50
+ readonly "visual-editing": "\n *[_type == \"article\"\n && primarySection._ref == \"4e0ef463-01e2-48db-9a31-38b3912ececd\"\n && !(_id in path(\"drafts.**\"))\n ] {\n _id,\n _type,\n _rev,\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n ";
51
51
  };
52
52
  export type FeatureArea = keyof typeof FEATURE_AREA_QUERIES;
53
53
  export declare const ALL_FEATURE_AREAS: FeatureArea[];
@@ -55,11 +55,11 @@ export declare const ALL_FEATURE_AREAS: FeatureArea[];
55
55
  * Fetch a single article by its slug.
56
56
  * Returns the same projection shape as the feature-area queries.
57
57
  */
58
- export declare const ARTICLE_BY_SLUG_QUERY = "\n *[_type == \"article\"\n && slug.current == $slug\n && !(_id in path(\"drafts.**\"))\n ][0] {\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n";
58
+ export declare const ARTICLE_BY_SLUG_QUERY = "\n *[_type == \"article\"\n && slug.current == $slug\n && !(_id in path(\"drafts.**\"))\n ][0] {\n _id,\n _type,\n _rev,\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n";
59
59
  /**
60
60
  * Fetch all published articles (for full-corpus generation).
61
61
  */
62
- export declare const ALL_ARTICLES_QUERY = "\n *[_type == \"article\"\n && !(_id in path(\"drafts.**\"))\n ] | order(primarySection->slug.current, title) {\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n";
62
+ export declare const ALL_ARTICLES_QUERY = "\n *[_type == \"article\"\n && !(_id in path(\"drafts.**\"))\n ] | order(primarySection->slug.current, title) {\n _id,\n _type,\n _rev,\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n";
63
63
  /**
64
64
  * Fetch article metadata for a list of slugs.
65
65
  *
@@ -76,7 +76,7 @@ export declare const ARTICLES_METADATA_BY_SLUGS_QUERY = "\n *[_type == \"articl
76
76
  *
77
77
  * Returns the full article projection for content comparison.
78
78
  */
79
- export declare const ARTICLE_BY_SLUG_WITH_PERSPECTIVE_QUERY = "\n *[_type == \"article\"\n && slug.current == $slug\n ][0] {\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n";
79
+ export declare const ARTICLE_BY_SLUG_WITH_PERSPECTIVE_QUERY = "\n *[_type == \"article\"\n && slug.current == $slug\n ][0] {\n _id,\n _type,\n _rev,\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n";
80
80
  /**
81
81
  * Fetch articles by their document IDs.
82
82
  *
@@ -86,7 +86,28 @@ export declare const ARTICLE_BY_SLUG_WITH_PERSPECTIVE_QUERY = "\n *[_type == \"
86
86
  *
87
87
  * @param $ids — array of document ID strings
88
88
  */
89
- export declare const ARTICLES_BY_IDS_QUERY = "\n *[_type == \"article\"\n && _id in $ids\n ] {\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n";
89
+ export declare const ARTICLES_BY_IDS_QUERY = "\n *[_type == \"article\"\n && _id in $ids\n ] {\n _id,\n _type,\n _rev,\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n";
90
+ /**
91
+ * Fetch arbitrary documents by their `_id` — no `_type` filter.
92
+ *
93
+ * Used by id-ref resolution so authors can pin any document type
94
+ * (`article`, `typesReference`, `marketingPage`, `glossaryEntry`, …),
95
+ * not just articles. The projection branches on `_type`:
96
+ *
97
+ * - `article` → reuses ARTICLE_PROJECTION's flatten + card-deref shape.
98
+ * - `typesReference` → derefs `library` and `latestVersion`, including
99
+ * the resolved file asset URL so the renderer can fetch typedoc JSON
100
+ * without a follow-up round-trip.
101
+ * - default `{...}` → returns the raw document fields, which the
102
+ * default walker in `document-renderers.ts` flattens to Markdown.
103
+ *
104
+ * Does NOT include the `!(_id in path("drafts.**"))` filter — id-ref
105
+ * resolution is intentional, drafts are queryable when perspective is
106
+ * draft-enabled, and authors are expected to pin published `_id`s.
107
+ *
108
+ * @param $ids — array of document ID strings
109
+ */
110
+ export declare const DOCS_BY_IDS_QUERY = "\n *[_id in $ids] {\n _id,\n _type,\n _system,\n _createdAt,\n _updatedAt,\n _rev,\n _type == \"article\" => {\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n _type != \"docsCardCollection\" => { ... },\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n },\n _type == \"typesReference\" => {\n title,\n autoPublish,\n \"slug\": slug,\n \"library\": library->{ _id, _type, title, npmName },\n \"latestVersion\": latestVersion->{\n _id,\n _type,\n semver,\n date,\n \"attachment\": {\n \"asset\": attachment.asset->{\n _id,\n _type,\n url,\n originalFilename,\n mimeType,\n size,\n extension\n }\n }\n }\n },\n !(_type in [\"article\", \"typesReference\"]) => { ... }\n }\n";
90
111
  /**
91
112
  * Fetch a single article by its document ID.
92
113
  *
@@ -96,7 +117,7 @@ export declare const ARTICLES_BY_IDS_QUERY = "\n *[_type == \"article\"\n &&
96
117
  *
97
118
  * @param $id — document ID string
98
119
  */
99
- export declare const ARTICLE_BY_ID_QUERY = "\n *[_type == \"article\"\n && _id == $id\n ][0] {\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n";
120
+ export declare const ARTICLE_BY_ID_QUERY = "\n *[_type == \"article\"\n && _id == $id\n ][0] {\n _id,\n _type,\n _rev,\n title,\n description,\n \"slug\": slug.current,\n \"section\": primarySection->{ \"slug\": slug.current, title },\n \"content\": content[] {\n // Pass through all standard blocks unchanged\n _type != \"docsCardCollection\" => { ... },\n // Resolve references inside card collections so we get titles/slugs\n _type == \"docsCardCollection\" => {\n ...,\n cards[] {\n ...,\n \"resolvedTitle\": reference->title,\n \"resolvedSlug\": reference->slug.current\n }\n }\n }\n}\n";
100
121
  /**
101
122
  * Resolve an article slug from a URL path segment.
102
123
  *
@@ -43,8 +43,15 @@ const SECTION_IDS = {
43
43
  * Returns the raw Portable Text `content` array so that the Node-side
44
44
  * converter (@portabletext/markdown) can produce well-structured Markdown
45
45
  * with headings, code fences, tables, callouts, etc.
46
+ *
47
+ * `_id`, `_type`, and `_rev` are projected so a result satisfies the
48
+ * `DocumentForRender` shape and can dispatch through the renderer
49
+ * registry uniformly with id-ref-resolved docs (W0197).
46
50
  */
47
51
  const ARTICLE_PROJECTION = `{
52
+ _id,
53
+ _type,
54
+ _rev,
48
55
  title,
49
56
  description,
50
57
  "slug": slug.current,
@@ -229,6 +236,77 @@ export const ARTICLES_BY_IDS_QUERY = `
229
236
  && _id in $ids
230
237
  ] ${ARTICLE_PROJECTION}
231
238
  `;
239
+ /**
240
+ * Fetch arbitrary documents by their `_id` — no `_type` filter.
241
+ *
242
+ * Used by id-ref resolution so authors can pin any document type
243
+ * (`article`, `typesReference`, `marketingPage`, `glossaryEntry`, …),
244
+ * not just articles. The projection branches on `_type`:
245
+ *
246
+ * - `article` → reuses ARTICLE_PROJECTION's flatten + card-deref shape.
247
+ * - `typesReference` → derefs `library` and `latestVersion`, including
248
+ * the resolved file asset URL so the renderer can fetch typedoc JSON
249
+ * without a follow-up round-trip.
250
+ * - default `{...}` → returns the raw document fields, which the
251
+ * default walker in `document-renderers.ts` flattens to Markdown.
252
+ *
253
+ * Does NOT include the `!(_id in path("drafts.**"))` filter — id-ref
254
+ * resolution is intentional, drafts are queryable when perspective is
255
+ * draft-enabled, and authors are expected to pin published `_id`s.
256
+ *
257
+ * @param $ids — array of document ID strings
258
+ */
259
+ export const DOCS_BY_IDS_QUERY = `
260
+ *[_id in $ids] {
261
+ _id,
262
+ _type,
263
+ _system,
264
+ _createdAt,
265
+ _updatedAt,
266
+ _rev,
267
+ _type == "article" => {
268
+ title,
269
+ description,
270
+ "slug": slug.current,
271
+ "section": primarySection->{ "slug": slug.current, title },
272
+ "content": content[] {
273
+ _type != "docsCardCollection" => { ... },
274
+ _type == "docsCardCollection" => {
275
+ ...,
276
+ cards[] {
277
+ ...,
278
+ "resolvedTitle": reference->title,
279
+ "resolvedSlug": reference->slug.current
280
+ }
281
+ }
282
+ }
283
+ },
284
+ _type == "typesReference" => {
285
+ title,
286
+ autoPublish,
287
+ "slug": slug,
288
+ "library": library->{ _id, _type, title, npmName },
289
+ "latestVersion": latestVersion->{
290
+ _id,
291
+ _type,
292
+ semver,
293
+ date,
294
+ "attachment": {
295
+ "asset": attachment.asset->{
296
+ _id,
297
+ _type,
298
+ url,
299
+ originalFilename,
300
+ mimeType,
301
+ size,
302
+ extension
303
+ }
304
+ }
305
+ }
306
+ },
307
+ !(_type in ["article", "typesReference"]) => { ... }
308
+ }
309
+ `;
232
310
  /**
233
311
  * Fetch a single article by its document ID.
234
312
  *
@@ -0,0 +1,98 @@
1
+ /**
2
+ * symbol-index — Programmatic extractor that produces a flat list of
3
+ * identifiers the canonical reference legitimizes, each with a one-line
4
+ * provenance snippet.
5
+ *
6
+ * Used by the grader-context pathway (W0196 / W0197) so the LLM judge sees
7
+ * a compact, deterministic recognition reference instead of the full
8
+ * narrative doc — addresses the DOC-2117 prior-collision failure mode
9
+ * where a grader claimed `useEditDocument` did not exist.
10
+ *
11
+ * Two upstream sources, both fully programmatic (no LLM in the extractor):
12
+ *
13
+ * - `extractSymbolIndex(blocks)` — walks Sanity Portable Text content
14
+ * (article docs).
15
+ * - `extractSymbolsFromTypedoc(json, packageName)` — parses typedoc
16
+ * JSON (typesReference docs).
17
+ *
18
+ * `mergeSymbolIndexes(indexes)` combines indexes from multiple references
19
+ * into a single deduped index for a task.
20
+ *
21
+ * Source precedence (higher wins on dedup):
22
+ * 1. type-def — typedoc declarations: literal authoritative type
23
+ * surface, no editorial layer between extracted
24
+ * symbol and the package's actual exports.
25
+ * 2. heading — Section headings (`block` style h1..h4), including
26
+ * inline `code` marks within heading spans.
27
+ * 3. inline-code — Inline `code` marks within non-heading `block` spans.
28
+ * 4. code-block — Identifiers from `import` statements in `codeBlock`
29
+ * bodies. Body identifiers (`const foo = ...`) are
30
+ * intentionally not extracted — those are usage demos.
31
+ */
32
+ export type SymbolProvenanceKind = "type-def" | "heading" | "inline-code" | "code-block";
33
+ /** Declaration kinds we surface from typedoc JSON for `type-def` provenance. */
34
+ export type TypeDefDeclarationKind = "function" | "class" | "interface" | "type" | "enum" | "variable" | "namespace";
35
+ export interface SymbolProvenance {
36
+ kind: SymbolProvenanceKind;
37
+ /** Human-readable line that locates the symbol in the source doc. */
38
+ snippet: string;
39
+ /** Heading style when kind === "heading". */
40
+ style?: "h1" | "h2" | "h3" | "h4";
41
+ /** Source filename when kind === "code-block" and one is set on the block. */
42
+ filename?: string;
43
+ /** Code block language tag when kind === "code-block". */
44
+ language?: string;
45
+ /** Declaration kind when kind === "type-def". */
46
+ declarationKind?: TypeDefDeclarationKind;
47
+ /** Source package (e.g. `@sanity/sdk-react`) when kind === "type-def". */
48
+ package?: string;
49
+ }
50
+ export interface SymbolEntry {
51
+ symbol: string;
52
+ provenance: SymbolProvenance;
53
+ }
54
+ export interface SymbolIndex {
55
+ symbols: SymbolEntry[];
56
+ }
57
+ export declare function extractSymbolIndex(blocks: unknown): SymbolIndex;
58
+ /**
59
+ * Extract a symbol index from a typedoc JSON document (schema 2.x — the
60
+ * shape produced by `typedoc --json`). Each top-level export becomes a
61
+ * `type-def` provenance entry with the symbol name, declaration kind
62
+ * (function / interface / type / etc.), and the JSDoc summary as snippet.
63
+ *
64
+ * Type-def is the highest-precedence source: typedoc declarations are
65
+ * literal authoritative type surface, no editorial layer between them
66
+ * and the package's actual exports. If a symbol also appears in narrative
67
+ * docs (heading, inline code, code-block import) the type-def entry wins
68
+ * on dedup.
69
+ *
70
+ * `body` is the raw JSON string fetched from the typesReference's
71
+ * attachment URL. Returns an empty index for any unparseable input — this
72
+ * is best-effort recognition material; callers fall back to full-doc
73
+ * injection when extraction yields nothing.
74
+ */
75
+ export declare function extractSymbolsFromTypedoc(body: string, packageName?: string): SymbolIndex;
76
+ /**
77
+ * Compute per-tier counts for a SymbolIndex. Used by the fetcher to
78
+ * populate the per-task manifest entry's `tierBreakdown` field.
79
+ */
80
+ export declare function symbolIndexTierBreakdown(index: SymbolIndex): {
81
+ typeDef: number;
82
+ heading: number;
83
+ inlineCode: number;
84
+ codeBlock: number;
85
+ };
86
+ /**
87
+ * Combine multiple `SymbolIndex` instances (typically from different
88
+ * canonical references for the same task) into a single deduped index
89
+ * preserving precedence.
90
+ */
91
+ export declare function mergeSymbolIndexes(indexes: readonly SymbolIndex[]): SymbolIndex;
92
+ /**
93
+ * Render a SymbolIndex as a compact markdown reference suitable for
94
+ * injection into a grader's `rubricPrompt` as ground-truth recognition
95
+ * material. Layout intentionally puts headings first so the most
96
+ * authoritative symbols anchor the top of the list.
97
+ */
98
+ export declare function renderSymbolIndex(index: SymbolIndex, title?: string): string;