@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.
- package/config/package-surface.ts +37 -0
- package/config/preflight-scoring.ts +26 -0
- package/dist/_vendor/ailf-core/artifact-registry.d.ts +1 -1
- package/dist/_vendor/ailf-core/artifact-registry.js +47 -0
- package/dist/_vendor/ailf-core/config-helpers.d.ts +35 -0
- package/dist/_vendor/ailf-core/config-helpers.js +67 -0
- package/dist/_vendor/ailf-core/index.d.ts +1 -1
- package/dist/_vendor/ailf-core/index.js +1 -1
- package/dist/_vendor/ailf-core/ports/context.d.ts +18 -0
- package/dist/_vendor/ailf-core/ports/doc-fetcher.d.ts +30 -0
- package/dist/_vendor/ailf-core/ports/index.d.ts +3 -1
- package/dist/_vendor/ailf-core/ports/index.js +1 -0
- package/dist/_vendor/ailf-core/ports/mode-handler.d.ts +23 -0
- package/dist/_vendor/ailf-core/ports/package-surface-resolver.d.ts +71 -0
- package/dist/_vendor/ailf-core/ports/package-surface-resolver.js +36 -0
- package/dist/_vendor/ailf-core/schemas/eval-config.d.ts +6 -0
- package/dist/_vendor/ailf-core/schemas/eval-config.js +14 -0
- package/dist/_vendor/ailf-core/schemas/index.d.ts +1 -0
- package/dist/_vendor/ailf-core/schemas/index.js +1 -0
- package/dist/_vendor/ailf-core/schemas/symbol-preflight-report.d.ts +51 -0
- package/dist/_vendor/ailf-core/schemas/symbol-preflight-report.js +57 -0
- package/dist/_vendor/ailf-core/types/generalized-task.d.ts +20 -3
- package/dist/_vendor/ailf-core/types/index.d.ts +13 -1
- package/dist/_vendor/ailf-core/types/index.js +1 -0
- package/dist/_vendor/ailf-core/types/package-surface.d.ts +36 -0
- package/dist/_vendor/ailf-core/types/package-surface.js +13 -0
- package/dist/_vendor/ailf-core/types/preflight-scoring.d.ts +52 -0
- package/dist/_vendor/ailf-core/types/preflight-scoring.js +18 -0
- package/dist/_vendor/ailf-core/types/repo-config.d.ts +14 -0
- package/dist/_vendor/ailf-core/types/symbol-preflight-report.d.ts +66 -0
- package/dist/_vendor/ailf-core/types/symbol-preflight-report.js +25 -0
- package/dist/adapters/config-sources/file-config-adapter.js +1 -0
- package/dist/adapters/doc-fetchers/sanity-doc-fetcher.d.ts +25 -5
- package/dist/adapters/doc-fetchers/sanity-doc-fetcher.js +276 -95
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/package-surface/dts-package-surface.d.ts +46 -0
- package/dist/adapters/package-surface/dts-package-surface.js +173 -0
- package/dist/adapters/package-surface/in-memory-package-surface.d.ts +15 -0
- package/dist/adapters/package-surface/in-memory-package-surface.js +28 -0
- package/dist/adapters/package-surface/index.d.ts +9 -0
- package/dist/adapters/package-surface/index.js +8 -0
- package/dist/adapters/package-surface/parse-dts-exports.d.ts +31 -0
- package/dist/adapters/package-surface/parse-dts-exports.js +54 -0
- package/dist/adapters/task-sources/repo-schemas.d.ts +22 -0
- package/dist/adapters/task-sources/repo-schemas.js +93 -1
- package/dist/adapters/task-sources/repo-task-source.js +11 -2
- package/dist/commands/pipeline-action.d.ts +2 -0
- package/dist/commands/pipeline-action.js +12 -0
- package/dist/commands/remote-pipeline.js +9 -2
- package/dist/commands/remote-results.d.ts +12 -1
- package/dist/commands/remote-results.js +25 -5
- package/dist/commands/validate-tasks.js +8 -2
- package/dist/composition-root.js +9 -0
- package/dist/config/package-surface.ts +37 -0
- package/dist/config/preflight-scoring.ts +26 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/orchestration/build-app-context.js +1 -0
- package/dist/orchestration/pipeline-orchestrator.d.ts +19 -1
- package/dist/orchestration/pipeline-orchestrator.js +38 -0
- package/dist/orchestration/steps/calculate-scores-step.js +11 -0
- package/dist/orchestration/steps/generate-configs-step.js +16 -1
- package/dist/orchestration/steps/run-eval-step.js +27 -0
- package/dist/pipeline/calculate-scores.d.ts +66 -5
- package/dist/pipeline/calculate-scores.js +141 -27
- package/dist/pipeline/compiler/index.d.ts +1 -1
- package/dist/pipeline/compiler/index.js +1 -1
- package/dist/pipeline/compiler/literacy-bridge.d.ts +9 -0
- package/dist/pipeline/compiler/literacy-bridge.js +2 -0
- package/dist/pipeline/compiler/mode-handlers/__fixtures__/agent-harness-example-tasks.js +0 -12
- package/dist/pipeline/compiler/mode-handlers/__fixtures__/knowledge-probe-example-tasks.js +0 -12
- package/dist/pipeline/compiler/mode-handlers/literacy/assertions.d.ts +1 -1
- package/dist/pipeline/compiler/mode-handlers/literacy/assertions.js +31 -4
- package/dist/pipeline/compiler/mode-handlers/literacy/compiler.js +190 -6
- package/dist/pipeline/compiler/mode-handlers/literacy/index.js +2 -0
- package/dist/pipeline/compiler/mode-handlers/literacy/types.d.ts +17 -2
- package/dist/pipeline/compiler/rubric-resolution.d.ts +17 -1
- package/dist/pipeline/compiler/rubric-resolution.js +78 -2
- package/dist/pipeline/compiler/scoring-bridge.d.ts +49 -2
- package/dist/pipeline/compiler/scoring-bridge.js +104 -10
- package/dist/pipeline/eval-fingerprint.d.ts +9 -0
- package/dist/pipeline/eval-fingerprint.js +7 -1
- package/dist/pipeline/preflight/compute-preflight.d.ts +67 -0
- package/dist/pipeline/preflight/compute-preflight.js +118 -0
- package/dist/pipeline/preflight/emit-symbol-preflight.d.ts +51 -0
- package/dist/pipeline/preflight/emit-symbol-preflight.js +102 -0
- package/dist/pipeline/preflight/load-package-surface.d.ts +14 -0
- package/dist/pipeline/preflight/load-package-surface.js +19 -0
- package/dist/pipeline/preflight/load-preflight-context.d.ts +13 -0
- package/dist/pipeline/preflight/load-preflight-context.js +25 -0
- package/dist/pipeline/preflight/load-preflight-scoring.d.ts +12 -0
- package/dist/pipeline/preflight/load-preflight-scoring.js +17 -0
- package/dist/pipeline/preflight/parse-imports.d.ts +62 -0
- package/dist/pipeline/preflight/parse-imports.js +125 -0
- package/dist/report-store.d.ts +8 -0
- package/dist/report-store.js +55 -6
- package/dist/sanity/document-renderers.d.ts +106 -0
- package/dist/sanity/document-renderers.js +307 -0
- package/dist/sanity/queries.d.ts +32 -11
- package/dist/sanity/queries.js +78 -0
- package/dist/sanity/symbol-index.d.ts +98 -0
- package/dist/sanity/symbol-index.js +615 -0
- package/dist/tasks/knowledge-probe/define-type-api.task.ts +2 -6
- package/dist/tasks/knowledge-probe/groq-projections.task.ts +0 -5
- package/dist/tasks/literacy/content-lake.task.ts +4 -10
- package/dist/tasks/literacy/frameworks.task.ts +2 -8
- package/dist/tasks/literacy/functions.task.ts +1 -4
- package/dist/tasks/literacy/groq.task.ts +3 -12
- package/dist/tasks/literacy/image-handling.task.ts +1 -4
- package/dist/tasks/literacy/nextjs-live.task.ts +1 -4
- package/dist/tasks/literacy/portable-text.task.ts +2 -8
- package/dist/tasks/literacy/studio-setup.task.ts +2 -8
- package/dist/tasks/literacy/visual-editing.task.ts +2 -8
- package/package.json +2 -1
- package/tasks/knowledge-probe/define-type-api.task.ts +2 -6
- package/tasks/knowledge-probe/groq-projections.task.ts +0 -5
- package/tasks/literacy/content-lake.task.ts +4 -10
- package/tasks/literacy/frameworks.task.ts +2 -8
- package/tasks/literacy/functions.task.ts +1 -4
- package/tasks/literacy/groq.task.ts +3 -12
- package/tasks/literacy/image-handling.task.ts +1 -4
- package/tasks/literacy/nextjs-live.task.ts +1 -4
- package/tasks/literacy/portable-text.task.ts +2 -8
- package/tasks/literacy/studio-setup.task.ts +2 -8
- package/tasks/literacy/visual-editing.task.ts +2 -8
|
@@ -18,8 +18,9 @@ import { join } from "path";
|
|
|
18
18
|
import { canonicalDocRefLabel, isIdRef, isPathRef, isPerspectiveRef, isSlugRef, } from "../../_vendor/ailf-core/index.js";
|
|
19
19
|
import { fetchUrlContent, } from "../../pipeline/fetch-url-content.js";
|
|
20
20
|
import { createPerspectiveClient, createPublishedClient, getSanityClient, } from "../../sanity/client.js";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
21
|
+
import { extractSymbolsForDoc, renderDocument, } from "../../sanity/document-renderers.js";
|
|
22
|
+
import { mergeSymbolIndexes, renderSymbolIndex, symbolIndexTierBreakdown, } from "../../sanity/symbol-index.js";
|
|
23
|
+
import { ALL_ARTICLES_QUERY, ALL_FEATURE_AREAS, ARTICLE_BY_ID_QUERY, ARTICLE_BY_SLUG_QUERY, ARTICLE_BY_SLUG_WITH_PERSPECTIVE_QUERY, ARTICLE_SLUG_BY_PATH_QUERY, ARTICLE_SLUG_BY_SECTION_PATH_QUERY, ARTICLES_IN_RELEASE_QUERY, ARTICLES_METADATA_BY_SLUGS_QUERY, DOCS_BY_IDS_QUERY, FEATURE_AREA_QUERIES, } from "../../sanity/queries.js";
|
|
23
24
|
// ---------------------------------------------------------------------------
|
|
24
25
|
// Helpers
|
|
25
26
|
// ---------------------------------------------------------------------------
|
|
@@ -34,11 +35,14 @@ function escapeNunjucks(text) {
|
|
|
34
35
|
function estimateTokens(text) {
|
|
35
36
|
return Math.ceil(text.length / 4);
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Read a `DocumentForRender` field that the GROQ projection set as a
|
|
40
|
+
* scalar string, gracefully returning `undefined` when the projection
|
|
41
|
+
* shape is empty/unexpected (defensive for partial overlay docs).
|
|
42
|
+
*/
|
|
43
|
+
function readString(doc, key) {
|
|
44
|
+
const value = doc[key];
|
|
45
|
+
return typeof value === "string" ? value : undefined;
|
|
42
46
|
}
|
|
43
47
|
/**
|
|
44
48
|
* Bridge DocSourceConfig (domain type) to getSanityClient overrides.
|
|
@@ -99,9 +103,11 @@ export class SanityDocFetcher {
|
|
|
99
103
|
}
|
|
100
104
|
}
|
|
101
105
|
}
|
|
102
|
-
// Resolve ID refs
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
// Resolve ID refs. Articles get added to allSlugs (legacy slug-flow takes
|
|
107
|
+
// over for fetch + render). Non-articles render directly via the renderer
|
|
108
|
+
// registry and bypass the slug-keyed content map.
|
|
109
|
+
const idResolution = await this.resolveIdRefs(idRefs, source);
|
|
110
|
+
for (const slug of idResolution.idToSlug.values()) {
|
|
105
111
|
allSlugs.add(slug);
|
|
106
112
|
}
|
|
107
113
|
// Resolve path refs → slugs
|
|
@@ -118,8 +124,14 @@ export class SanityDocFetcher {
|
|
|
118
124
|
}
|
|
119
125
|
}
|
|
120
126
|
const metadata = {};
|
|
121
|
-
// 2. Fetch document manifest (traceability metadata)
|
|
127
|
+
// 2. Fetch document manifest (traceability metadata). Articles flow
|
|
128
|
+
// through fetchManifest; non-article id-refs contribute their own
|
|
129
|
+
// entries from the resolution we already did above.
|
|
122
130
|
const manifest = await this.fetchManifest(allSlugs, source);
|
|
131
|
+
for (const extra of idResolution.extraManifest) {
|
|
132
|
+
manifest.push(extra);
|
|
133
|
+
}
|
|
134
|
+
manifest.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
123
135
|
if (manifest.length > 0) {
|
|
124
136
|
metadata.manifest = manifest;
|
|
125
137
|
}
|
|
@@ -145,12 +157,12 @@ export class SanityDocFetcher {
|
|
|
145
157
|
console.log(` Resolving ${source.documentIds.length} document ID(s) against canonical set...`);
|
|
146
158
|
documentOverlay = await this.resolveDocumentOverlay(source.documentIds, allSlugs, source);
|
|
147
159
|
const summary = {
|
|
148
|
-
appendedCount: documentOverlay.
|
|
160
|
+
appendedCount: documentOverlay.appendedDocs.length,
|
|
149
161
|
documentIds: source.documentIds,
|
|
150
162
|
replacedSlugs: [...documentOverlay.replacements.keys()],
|
|
151
163
|
};
|
|
152
164
|
metadata.documentOverlay = summary;
|
|
153
|
-
console.log(` Document overlay: ${documentOverlay.replacements.size} replacement(s), ${documentOverlay.
|
|
165
|
+
console.log(` Document overlay: ${documentOverlay.replacements.size} replacement(s), ${documentOverlay.appendedDocs.length} appended`);
|
|
154
166
|
}
|
|
155
167
|
// 5. URL content fetch — fetch direct URLs
|
|
156
168
|
const urlContent = [];
|
|
@@ -210,66 +222,145 @@ export class SanityDocFetcher {
|
|
|
210
222
|
}
|
|
211
223
|
const affectedSlugs = new Set(slugPerspective.keys());
|
|
212
224
|
const removedSlugs = new Set(releaseImpact?.removed ?? []);
|
|
213
|
-
const
|
|
214
|
-
// 7. Assemble per-task context
|
|
225
|
+
const docsBySlug = await this.fetchCanonicalDocs(allSlugs, affectedSlugs, removedSlugs, documentOverlay, source, slugPerspective);
|
|
226
|
+
// 7. Assemble per-task context + symbol index. Both surfaces dispatch
|
|
227
|
+
// through the renderer registry uniformly — the slug-flow, id-ref flow,
|
|
228
|
+
// and overlay flow all produce `DocumentForRender` objects here, and the
|
|
229
|
+
// registry decides how to render Markdown and extract symbols per
|
|
230
|
+
// `_type`. Per-task assembly aggregates symbols across all references
|
|
231
|
+
// attached to the task, writes:
|
|
232
|
+
// - `contexts/canonical/<task.id>.md` — existing rendered-doc
|
|
233
|
+
// artifact, consumed by Promptfoo `vars.docs`.
|
|
234
|
+
// - `contexts/canonical-symbols/<task.id>.md` — W0197 symbol-index
|
|
235
|
+
// artifact, written only when the task produced ≥1 symbol.
|
|
236
|
+
// - `contexts/canonical-symbols/manifest.json` — per-task manifest
|
|
237
|
+
// listing which tasks produced indexes (with counts + tier
|
|
238
|
+
// breakdowns). The grader-context consumer reads this manifest
|
|
239
|
+
// to decide between symbol-index and full-doc injection.
|
|
215
240
|
const canonicalDir = join(this.rootDir, "contexts", "canonical");
|
|
241
|
+
const symbolsDir = join(this.rootDir, "contexts", "canonical-symbols");
|
|
216
242
|
mkdirSync(canonicalDir, { recursive: true });
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
243
|
+
mkdirSync(symbolsDir, { recursive: true });
|
|
244
|
+
const renderCtx = { fetchUrl: this.fetchAttachmentBody };
|
|
245
|
+
const renderAndExtract = async (doc) => {
|
|
246
|
+
const rendered = await renderDocument(doc, renderCtx);
|
|
247
|
+
const symbols = await extractSymbolsForDoc(doc, renderCtx);
|
|
248
|
+
return { markdown: rendered.content, symbols, slug: rendered.slug };
|
|
249
|
+
};
|
|
250
|
+
const consumeDoc = async (doc, fallbackSlug) => {
|
|
251
|
+
const { markdown, symbols, slug } = await renderAndExtract(doc);
|
|
252
|
+
if (!markdown)
|
|
253
|
+
return null;
|
|
254
|
+
return { markdown, slug: fallbackSlug ?? slug, symbols };
|
|
255
|
+
};
|
|
256
|
+
const symbolIndexManifest = [];
|
|
257
|
+
const contexts = await Promise.all(tasks.map(async (task) => {
|
|
258
|
+
// Resolve each ref to a "consumed" record (or null). Refs that
|
|
259
|
+
// hit the slug-flow / overlay path render+extract via the
|
|
260
|
+
// registry; non-article id-refs were already rendered+extracted
|
|
261
|
+
// in resolveIdRefs and live in contentById.
|
|
262
|
+
const refResolutions = await Promise.all((task.context?.docs ?? []).map(async (ref) => {
|
|
222
263
|
if (isPerspectiveRef(ref)) {
|
|
223
264
|
const expanded = perspectiveToSlugs.get(ref.perspective) ?? [];
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
265
|
+
const docs = expanded
|
|
266
|
+
.filter((slug) => !removedSlugs.has(slug))
|
|
267
|
+
.map((slug) => ({ slug, doc: docsBySlug.get(slug) }))
|
|
268
|
+
.filter((e) => e.doc !== undefined);
|
|
269
|
+
const consumed = await Promise.all(docs.map((e) => consumeDoc(e.doc, e.slug)));
|
|
270
|
+
return consumed.filter((c) => c !== null);
|
|
271
|
+
}
|
|
272
|
+
if (isIdRef(ref) && idResolution.contentById.has(ref.id)) {
|
|
273
|
+
const entry = idResolution.contentById.get(ref.id);
|
|
274
|
+
return [
|
|
275
|
+
{
|
|
276
|
+
markdown: entry.content,
|
|
277
|
+
slug: entry.slug,
|
|
278
|
+
symbols: entry.symbolIndex,
|
|
279
|
+
},
|
|
280
|
+
];
|
|
234
281
|
}
|
|
235
282
|
let slug;
|
|
236
283
|
if (isSlugRef(ref)) {
|
|
237
284
|
slug = ref.slug;
|
|
238
285
|
}
|
|
239
286
|
else if (isIdRef(ref)) {
|
|
240
|
-
slug = idToSlug.get(ref.id);
|
|
287
|
+
slug = idResolution.idToSlug.get(ref.id);
|
|
241
288
|
}
|
|
242
289
|
else if (isPathRef(ref)) {
|
|
243
290
|
slug = pathToSlug.get(ref.path);
|
|
244
291
|
}
|
|
245
292
|
if (!slug)
|
|
246
|
-
|
|
293
|
+
return null;
|
|
247
294
|
if (removedSlugs.has(slug))
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
if (
|
|
251
|
-
|
|
252
|
-
|
|
295
|
+
return null;
|
|
296
|
+
const doc = docsBySlug.get(slug);
|
|
297
|
+
if (!doc)
|
|
298
|
+
return null;
|
|
299
|
+
const consumed = await consumeDoc(doc, slug);
|
|
300
|
+
return consumed ? [consumed] : null;
|
|
301
|
+
}));
|
|
302
|
+
// Overlay docs (appended) flow through the same registry dispatch.
|
|
303
|
+
const overlayConsumed = [];
|
|
304
|
+
if (documentOverlay && documentOverlay.appendedDocs.length > 0) {
|
|
305
|
+
const consumed = await Promise.all(documentOverlay.appendedDocs.map((doc) => consumeDoc(doc)));
|
|
306
|
+
for (const c of consumed) {
|
|
307
|
+
if (c)
|
|
308
|
+
overlayConsumed.push(c);
|
|
253
309
|
}
|
|
254
310
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
311
|
+
const parts = [];
|
|
312
|
+
const slugs = [];
|
|
313
|
+
const taskIndexes = [];
|
|
314
|
+
const consumed = [
|
|
315
|
+
...refResolutions.flat().filter((c) => c !== null),
|
|
316
|
+
...overlayConsumed,
|
|
317
|
+
];
|
|
318
|
+
for (const c of consumed) {
|
|
319
|
+
parts.push(c.markdown);
|
|
320
|
+
slugs.push(c.slug);
|
|
321
|
+
if (c.symbols.symbols.length > 0)
|
|
322
|
+
taskIndexes.push(c.symbols);
|
|
258
323
|
}
|
|
259
|
-
// Append URL-fetched content
|
|
324
|
+
// Append URL-fetched content. URLs are raw markdown blobs from
|
|
325
|
+
// external sources (no Sanity doc shape), so they don't contribute
|
|
326
|
+
// to the per-task symbol index.
|
|
260
327
|
if (urlContent.length > 0) {
|
|
261
328
|
parts.push(...urlContent);
|
|
262
329
|
}
|
|
263
330
|
const combined = escapeNunjucks(parts.join("\n\n---\n\n"));
|
|
264
331
|
const contextPath = join(canonicalDir, `${task.id}.md`);
|
|
265
332
|
writeFileSync(contextPath, combined, "utf-8");
|
|
333
|
+
// Aggregate per-task symbol index. Always emit a manifest entry —
|
|
334
|
+
// entry presence is the consumer's signal that extraction ran.
|
|
335
|
+
// The per-task .md file is only written when the index is
|
|
336
|
+
// non-empty; a missing file under an entry-present manifest is
|
|
337
|
+
// permitted (consumer treats it the same as count zero).
|
|
338
|
+
const merged = mergeSymbolIndexes(taskIndexes);
|
|
339
|
+
const tierBreakdown = symbolIndexTierBreakdown(merged);
|
|
340
|
+
const symbolsRelativePath = `contexts/canonical-symbols/${task.id}.md`;
|
|
341
|
+
if (merged.symbols.length > 0) {
|
|
342
|
+
const indexMarkdown = renderSymbolIndex(merged, task.title);
|
|
343
|
+
writeFileSync(join(symbolsDir, `${task.id}.md`), indexMarkdown, "utf-8");
|
|
344
|
+
}
|
|
345
|
+
symbolIndexManifest.push({
|
|
346
|
+
taskId: task.id,
|
|
347
|
+
path: symbolsRelativePath,
|
|
348
|
+
symbolCount: merged.symbols.length,
|
|
349
|
+
tierBreakdown,
|
|
350
|
+
});
|
|
266
351
|
return {
|
|
267
352
|
taskId: task.id,
|
|
268
353
|
content: combined,
|
|
269
354
|
slugs,
|
|
270
355
|
tokenCount: estimateTokens(combined),
|
|
271
356
|
};
|
|
272
|
-
});
|
|
357
|
+
}));
|
|
358
|
+
// Persist the symbol-index manifest so the literacy compiler can read
|
|
359
|
+
// it to decide between symbol-index and full-doc grader-context
|
|
360
|
+
// injection per task. Sorted for stable diffs across runs.
|
|
361
|
+
symbolIndexManifest.sort((a, b) => a.taskId.localeCompare(b.taskId));
|
|
362
|
+
writeFileSync(join(symbolsDir, "manifest.json"), JSON.stringify({ entries: symbolIndexManifest }, null, 2), "utf-8");
|
|
363
|
+
metadata.symbolIndexes = symbolIndexManifest;
|
|
273
364
|
const hasMetadata = Object.keys(metadata).length > 0;
|
|
274
365
|
return {
|
|
275
366
|
contexts,
|
|
@@ -289,40 +380,130 @@ export class SanityDocFetcher {
|
|
|
289
380
|
.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
290
381
|
}
|
|
291
382
|
// -----------------------------------------------------------------------
|
|
292
|
-
// Private: Resolve ID refs
|
|
383
|
+
// Private: Resolve ID refs (type-agnostic)
|
|
293
384
|
// -----------------------------------------------------------------------
|
|
294
385
|
/**
|
|
295
|
-
* Batch-resolve document ID refs
|
|
386
|
+
* Batch-resolve document ID refs without filtering on `_type`.
|
|
387
|
+
*
|
|
388
|
+
* Articles are routed back into the slug-flow (so manifest, perspective
|
|
389
|
+
* diffing, and overlay handling continue to work unchanged). Non-articles
|
|
390
|
+
* are rendered eagerly via the renderer registry — `typesReference` gets
|
|
391
|
+
* a high-fidelity formatter; anything else falls through to the default
|
|
392
|
+
* walker so authors can pin marketing pages, glossary entries, etc.
|
|
393
|
+
* without an AILF code change.
|
|
296
394
|
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
395
|
+
* Three log shapes:
|
|
396
|
+
* - `info` — doc rendered with the default formatter (suggests a
|
|
397
|
+
* dedicated renderer would improve fidelity)
|
|
398
|
+
* - `warn` — registered renderer produced empty content (W0195 AC#3)
|
|
399
|
+
* - `warn` — id had no matching document (existed/wrong tenant/typo)
|
|
300
400
|
*/
|
|
301
|
-
async
|
|
302
|
-
const
|
|
401
|
+
async resolveIdRefs(idRefs, source) {
|
|
402
|
+
const idToSlug = new Map();
|
|
403
|
+
const contentById = new Map();
|
|
404
|
+
const extraManifest = [];
|
|
303
405
|
if (idRefs.length === 0)
|
|
304
|
-
return
|
|
406
|
+
return { idToSlug, contentById, extraManifest };
|
|
305
407
|
const uniqueIds = [...new Set(idRefs.map((r) => r.id))];
|
|
408
|
+
const taskByRef = new Map();
|
|
409
|
+
for (const { id, taskId } of idRefs) {
|
|
410
|
+
const existing = taskByRef.get(id) ?? [];
|
|
411
|
+
existing.push(taskId);
|
|
412
|
+
taskByRef.set(id, existing);
|
|
413
|
+
}
|
|
306
414
|
const client = source?.perspective
|
|
307
415
|
? createPerspectiveClient(source.perspective, source)
|
|
308
416
|
: getSanityClient(toSanityOverrides(source));
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
417
|
+
const docs = await client.fetch(DOCS_BY_IDS_QUERY, {
|
|
418
|
+
ids: uniqueIds,
|
|
419
|
+
});
|
|
420
|
+
const docsById = new Map(docs.map((d) => [d._id, d]));
|
|
421
|
+
let articleCount = 0;
|
|
422
|
+
let highFidelity = 0;
|
|
423
|
+
let defaultFidelity = 0;
|
|
424
|
+
for (const id of uniqueIds) {
|
|
425
|
+
const taskIds = taskByRef.get(id) ?? [];
|
|
426
|
+
const doc = docsById.get(id);
|
|
427
|
+
if (!doc) {
|
|
428
|
+
console.warn(` [warn] doc "${id}" not found (referenced by task(s): ${taskIds.join(", ")})`);
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
// Article id-refs flow back into the slug-keyed pipeline so manifest,
|
|
432
|
+
// perspective diff, and document overlay continue to work. The slug
|
|
433
|
+
// projection in DOCS_BY_IDS_QUERY mirrors ARTICLE_PROJECTION.
|
|
434
|
+
if (doc._type === "article") {
|
|
435
|
+
const slug = doc.slug;
|
|
436
|
+
if (typeof slug === "string" && slug.length > 0) {
|
|
437
|
+
idToSlug.set(id, slug);
|
|
438
|
+
articleCount += 1;
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
console.warn(` [warn] article "${id}" has no slug (referenced by task(s): ${taskIds.join(", ")})`);
|
|
442
|
+
}
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
const renderCtx = { fetchUrl: this.fetchAttachmentBody };
|
|
446
|
+
const rendered = await renderDocument(doc, renderCtx);
|
|
447
|
+
if (!rendered.content) {
|
|
448
|
+
console.warn(` [warn] doc "${id}" resolved as "${doc._type}" but produced empty context (referenced by task(s): ${taskIds.join(", ")})`);
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
const symbolIndex = await extractSymbolsForDoc(doc, renderCtx);
|
|
452
|
+
contentById.set(id, {
|
|
453
|
+
content: rendered.content,
|
|
454
|
+
slug: rendered.slug,
|
|
455
|
+
type: doc._type,
|
|
456
|
+
symbolIndex,
|
|
457
|
+
});
|
|
458
|
+
const _rev = doc._rev;
|
|
459
|
+
const title = doc.title;
|
|
460
|
+
extraManifest.push({
|
|
461
|
+
_id: doc._id,
|
|
462
|
+
_rev: typeof _rev === "string" ? _rev : "",
|
|
463
|
+
slug: rendered.slug,
|
|
464
|
+
title: typeof title === "string" ? title : `(${doc._type})`,
|
|
465
|
+
});
|
|
466
|
+
if (rendered.fidelity === "default") {
|
|
467
|
+
console.log(` [info] doc "${id}" rendered with default formatter — a dedicated renderer for "${doc._type}" would likely improve grader fidelity`);
|
|
468
|
+
defaultFidelity += 1;
|
|
316
469
|
}
|
|
317
470
|
else {
|
|
318
|
-
|
|
471
|
+
highFidelity += 1;
|
|
319
472
|
}
|
|
320
473
|
}
|
|
321
|
-
if (
|
|
322
|
-
console.log(` Resolved ${
|
|
474
|
+
if (articleCount > 0) {
|
|
475
|
+
console.log(` Resolved ${articleCount} article id ref(s) to slugs`);
|
|
323
476
|
}
|
|
324
|
-
|
|
477
|
+
if (highFidelity + defaultFidelity > 0) {
|
|
478
|
+
console.log(` Resolved ${highFidelity + defaultFidelity} non-article id ref(s) (${highFidelity} high-fidelity, ${defaultFidelity} default)`);
|
|
479
|
+
}
|
|
480
|
+
return { idToSlug, contentById, extraManifest };
|
|
325
481
|
}
|
|
482
|
+
/**
|
|
483
|
+
* Fetch the raw body of a Sanity file asset URL, used by the
|
|
484
|
+
* `typesReference` renderer to inline typedoc JSON. Returns `null`
|
|
485
|
+
* on any HTTP/network failure rather than throwing — the renderer
|
|
486
|
+
* surfaces a placeholder so the rest of the context still renders.
|
|
487
|
+
*
|
|
488
|
+
* Wrapped in a 30s `AbortSignal.timeout` so a slow CDN can't hang the
|
|
489
|
+
* eval pipeline indefinitely. Timeouts surface as a single `null`
|
|
490
|
+
* return like any other fetch failure.
|
|
491
|
+
*/
|
|
492
|
+
fetchAttachmentBody = async (url) => {
|
|
493
|
+
try {
|
|
494
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(30_000) });
|
|
495
|
+
if (!response.ok) {
|
|
496
|
+
console.warn(` [warn] attachment fetch failed for ${url}: HTTP ${response.status}`);
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
return await response.text();
|
|
500
|
+
}
|
|
501
|
+
catch (err) {
|
|
502
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
503
|
+
console.warn(` [warn] attachment fetch failed for ${url}: ${msg}`);
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
};
|
|
326
507
|
// -----------------------------------------------------------------------
|
|
327
508
|
// Private: Resolve path refs to slugs
|
|
328
509
|
// -----------------------------------------------------------------------
|
|
@@ -470,7 +651,7 @@ export class SanityDocFetcher {
|
|
|
470
651
|
// -----------------------------------------------------------------------
|
|
471
652
|
async resolveDocumentOverlay(documentIds, canonicalSlugs, source) {
|
|
472
653
|
const overlay = {
|
|
473
|
-
|
|
654
|
+
appendedDocs: [],
|
|
474
655
|
replacements: new Map(),
|
|
475
656
|
};
|
|
476
657
|
if (documentIds.length === 0)
|
|
@@ -484,16 +665,14 @@ export class SanityDocFetcher {
|
|
|
484
665
|
console.warn(` [warn] No article found for document ID "${id}"`);
|
|
485
666
|
continue;
|
|
486
667
|
}
|
|
487
|
-
const
|
|
488
|
-
if (
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
overlay.replacements.set(doc.slug, content);
|
|
492
|
-
console.log(` 📄 Document ${id} → replaces canonical doc "${doc.slug}"`);
|
|
668
|
+
const slug = readString(doc, "slug");
|
|
669
|
+
if (slug && canonicalSlugs.has(slug)) {
|
|
670
|
+
overlay.replacements.set(slug, doc);
|
|
671
|
+
console.log(` 📄 Document ${id} → replaces canonical doc "${slug}"`);
|
|
493
672
|
}
|
|
494
673
|
else {
|
|
495
|
-
overlay.
|
|
496
|
-
const slugInfo =
|
|
674
|
+
overlay.appendedDocs.push(doc);
|
|
675
|
+
const slugInfo = slug ? ` (slug: "${slug}")` : "";
|
|
497
676
|
console.log(` 📄 Document ${id} → appended as additional context${slugInfo}`);
|
|
498
677
|
}
|
|
499
678
|
}
|
|
@@ -513,9 +692,9 @@ export class SanityDocFetcher {
|
|
|
513
692
|
const doc = await client.fetch(ARTICLE_BY_SLUG_QUERY, { slug });
|
|
514
693
|
if (!doc) {
|
|
515
694
|
console.warn(` [warn] No article found for slug "${slug}"`);
|
|
516
|
-
return
|
|
695
|
+
return null;
|
|
517
696
|
}
|
|
518
|
-
return
|
|
697
|
+
return doc;
|
|
519
698
|
}
|
|
520
699
|
async fetchArticleBySlugWithPerspective(slug, source) {
|
|
521
700
|
if (!source.perspective) {
|
|
@@ -525,24 +704,26 @@ export class SanityDocFetcher {
|
|
|
525
704
|
const doc = await client.fetch(ARTICLE_BY_SLUG_WITH_PERSPECTIVE_QUERY, { slug });
|
|
526
705
|
if (!doc) {
|
|
527
706
|
console.warn(` [warn] No article found for slug "${slug}" in perspective "${source.perspective}"`);
|
|
528
|
-
return
|
|
707
|
+
return null;
|
|
529
708
|
}
|
|
530
|
-
return
|
|
709
|
+
return doc;
|
|
531
710
|
}
|
|
532
711
|
// -----------------------------------------------------------------------
|
|
533
712
|
// Private: Fetch all canonical docs with perspective/overlay awareness
|
|
534
713
|
// -----------------------------------------------------------------------
|
|
535
714
|
async fetchCanonicalDocs(allSlugs, affectedSlugs, removedSlugs, overlay, source, slugPerspective) {
|
|
536
|
-
const
|
|
715
|
+
const docsBySlug = new Map();
|
|
537
716
|
for (const slug of allSlugs) {
|
|
538
717
|
if (removedSlugs.has(slug))
|
|
539
718
|
continue;
|
|
540
719
|
// Check if this slug has a document overlay replacement
|
|
541
720
|
if (overlay?.replacements.has(slug)) {
|
|
542
721
|
console.log(` Fetching: ${slug} (from document overlay)`);
|
|
543
|
-
|
|
722
|
+
docsBySlug.set(slug, overlay.replacements.get(slug));
|
|
723
|
+
continue;
|
|
544
724
|
}
|
|
545
|
-
|
|
725
|
+
let doc;
|
|
726
|
+
if (affectedSlugs.has(slug)) {
|
|
546
727
|
// Use the per-slug perspective when available; fall back to
|
|
547
728
|
// source-level perspective. This ensures perspective-ref-expanded
|
|
548
729
|
// slugs are fetched from their specific release even when no
|
|
@@ -551,23 +732,22 @@ export class SanityDocFetcher {
|
|
|
551
732
|
if (perspective && source) {
|
|
552
733
|
const perspectiveSource = { ...source, perspective };
|
|
553
734
|
console.log(` Fetching: ${slug} (from perspective: ${perspective})`);
|
|
554
|
-
|
|
555
|
-
contentBySlug.set(slug, content);
|
|
735
|
+
doc = await this.fetchArticleBySlugWithPerspective(slug, perspectiveSource);
|
|
556
736
|
}
|
|
557
737
|
else {
|
|
558
738
|
// No perspective available — fetch from published
|
|
559
739
|
console.log(` Fetching: ${slug}`);
|
|
560
|
-
|
|
561
|
-
contentBySlug.set(slug, content);
|
|
740
|
+
doc = await this.fetchArticleBySlug(slug);
|
|
562
741
|
}
|
|
563
742
|
}
|
|
564
743
|
else {
|
|
565
744
|
console.log(` Fetching: ${slug}`);
|
|
566
|
-
|
|
567
|
-
contentBySlug.set(slug, content);
|
|
745
|
+
doc = await this.fetchArticleBySlug(slug);
|
|
568
746
|
}
|
|
747
|
+
if (doc)
|
|
748
|
+
docsBySlug.set(slug, doc);
|
|
569
749
|
}
|
|
570
|
-
return
|
|
750
|
+
return docsBySlug;
|
|
571
751
|
}
|
|
572
752
|
// -----------------------------------------------------------------------
|
|
573
753
|
// Public non-port methods — opt-in CLI features
|
|
@@ -590,7 +770,8 @@ export class SanityDocFetcher {
|
|
|
590
770
|
console.warn(` [warn] No articles found for "${feature}"`);
|
|
591
771
|
continue;
|
|
592
772
|
}
|
|
593
|
-
const
|
|
773
|
+
const rendered = await Promise.all(docs.map((d) => renderDocument(d, { fetchUrl: this.fetchAttachmentBody })));
|
|
774
|
+
const combined = rendered.map((r) => r.content).join("\n\n---\n\n");
|
|
594
775
|
const outPath = join(contextsDir, `${feature}.md`);
|
|
595
776
|
writeFileSync(outPath, escapeNunjucks(combined), "utf-8");
|
|
596
777
|
console.log(` ${feature}: ${docs.length} articles, ~${estimateTokens(combined)} tokens`);
|
|
@@ -607,16 +788,16 @@ export class SanityDocFetcher {
|
|
|
607
788
|
const baseUrl = source?.baseUrl ?? "https://www.sanity.io/docs";
|
|
608
789
|
console.log("\nGenerating full corpus...");
|
|
609
790
|
const docs = await client.fetch(ALL_ARTICLES_QUERY);
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
})
|
|
619
|
-
|
|
791
|
+
const rendered = await Promise.all(docs.map(async (d) => {
|
|
792
|
+
const result = await renderDocument(d, {
|
|
793
|
+
fetchUrl: this.fetchAttachmentBody,
|
|
794
|
+
});
|
|
795
|
+
// Corpus output prepends the article URL line so the dataset can
|
|
796
|
+
// be consumed by RAG retrievers that key on canonical URL.
|
|
797
|
+
const slug = readString(d, "slug") ?? result.slug;
|
|
798
|
+
return `URL: ${baseUrl}/${slug}\n\n${result.content}`;
|
|
799
|
+
}));
|
|
800
|
+
const corpus = rendered.join("\n\n---\n\n");
|
|
620
801
|
const outDir = join(this.rootDir, "contexts");
|
|
621
802
|
mkdirSync(outDir, { recursive: true });
|
|
622
803
|
writeFileSync(join(outDir, "full-corpus.md"), escapeNunjucks(corpus), "utf-8");
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -9,3 +9,4 @@ export { SanityDocFetcher } from "./doc-fetchers/index.js";
|
|
|
9
9
|
export { PromptfooEvalAdapter } from "./eval-runners/index.js";
|
|
10
10
|
export { ConsoleLogger, type ConsoleLoggerOptions, JsonLogger, QuietLogger, } from "./loggers/index.js";
|
|
11
11
|
export { CliConfigAdapter, FileConfigAdapter } from "./config-sources/index.js";
|
|
12
|
+
export { DtsPackageSurface, InMemoryPackageSurface, type DtsPackageSurfaceOptions, type PackageRootResolver, parseDtsExports, type ParsedDtsExports, } from "./package-surface/index.js";
|
package/dist/adapters/index.js
CHANGED
|
@@ -9,3 +9,4 @@ export { SanityDocFetcher } from "./doc-fetchers/index.js";
|
|
|
9
9
|
export { PromptfooEvalAdapter } from "./eval-runners/index.js";
|
|
10
10
|
export { ConsoleLogger, JsonLogger, QuietLogger, } from "./loggers/index.js";
|
|
11
11
|
export { CliConfigAdapter, FileConfigAdapter } from "./config-sources/index.js";
|
|
12
|
+
export { DtsPackageSurface, InMemoryPackageSurface, parseDtsExports, } from "./package-surface/index.js";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DtsPackageSurface — `PackageSurfaceResolver` adapter that reads installed
|
|
3
|
+
* package `.d.ts` files from `node_modules`.
|
|
4
|
+
*
|
|
5
|
+
* Resolution flow:
|
|
6
|
+
* 1. Find the package's root directory via the configured resolver
|
|
7
|
+
* (default uses `createRequire` against the working directory).
|
|
8
|
+
* 2. Read its `package.json` to capture the resolved `version` and the
|
|
9
|
+
* `.d.ts` entry path (`types`, `typings`, or `exports["."].types`).
|
|
10
|
+
* 3. Parse the entry `.d.ts` for top-level export declarations.
|
|
11
|
+
* 4. Follow ONE hop of `export * from "./relative"` re-exports — direct
|
|
12
|
+
* siblings only, no transitive walking.
|
|
13
|
+
* 5. Cache the result by package name for the resolver's lifetime.
|
|
14
|
+
*
|
|
15
|
+
* Throws `PackageSurfaceResolverError` with a typed `reason` when the
|
|
16
|
+
* package isn't installed or its types entry can't be resolved. Callers
|
|
17
|
+
* (the W0198 preflight) catch the error and convert it into per-binding
|
|
18
|
+
* `unresolved` findings rather than `missing` deductions.
|
|
19
|
+
*/
|
|
20
|
+
import { type PackageSurface, type PackageSurfaceResolver } from "../../_vendor/ailf-core/index.d.ts";
|
|
21
|
+
/**
|
|
22
|
+
* Resolves a package's installed root directory (the directory whose
|
|
23
|
+
* `package.json` describes it). Returns `null` when the package isn't
|
|
24
|
+
* installed in the configured lookup path.
|
|
25
|
+
*/
|
|
26
|
+
export type PackageRootResolver = (pkg: string) => string | null;
|
|
27
|
+
export interface DtsPackageSurfaceOptions {
|
|
28
|
+
/**
|
|
29
|
+
* How to find a package's root directory. Defaults to a `createRequire`
|
|
30
|
+
* walk from `resolveFromDir` (or the current working directory).
|
|
31
|
+
* Tests pass a fixture-aware resolver so they don't depend on real
|
|
32
|
+
* `node_modules` contents.
|
|
33
|
+
*/
|
|
34
|
+
resolvePackageRoot?: PackageRootResolver;
|
|
35
|
+
/**
|
|
36
|
+
* Directory whose `node_modules` chain is searched by the default
|
|
37
|
+
* resolver. Ignored when `resolvePackageRoot` is supplied.
|
|
38
|
+
*/
|
|
39
|
+
resolveFromDir?: string;
|
|
40
|
+
}
|
|
41
|
+
export declare class DtsPackageSurface implements PackageSurfaceResolver {
|
|
42
|
+
private readonly resolveRoot;
|
|
43
|
+
private readonly cache;
|
|
44
|
+
constructor(opts?: DtsPackageSurfaceOptions);
|
|
45
|
+
resolveExports(pkg: string): Promise<PackageSurface>;
|
|
46
|
+
}
|