@sanity/ailf 4.2.0 → 4.3.1
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/pipeline-request.d.ts +4 -0
- package/dist/_vendor/ailf-core/schemas/pipeline-request.js +7 -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/index.d.ts +12 -0
- 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/pipeline-request.d.ts +1 -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/api-client/build-request.d.ts +1 -0
- package/dist/adapters/api-client/build-request.js +3 -0
- package/dist/adapters/config-sources/file-config-adapter.js +1 -0
- package/dist/adapters/doc-fetchers/sanity-doc-fetcher.d.ts +4 -0
- package/dist/adapters/doc-fetchers/sanity-doc-fetcher.js +159 -82
- 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 +6 -0
- package/dist/adapters/task-sources/repo-schemas.js +15 -0
- package/dist/commands/pipeline-action.d.ts +2 -0
- package/dist/commands/pipeline-action.js +12 -0
- package/dist/commands/remote-pipeline.js +10 -2
- package/dist/commands/remote-results.d.ts +12 -1
- package/dist/commands/remote-results.js +25 -5
- 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/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 +146 -1
- 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/map-request-to-config.js +1 -0
- 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 +45 -7
- package/dist/sanity/document-renderers.js +99 -13
- package/dist/sanity/queries.d.ts +11 -11
- package/dist/sanity/queries.js +7 -0
- package/dist/sanity/symbol-index.d.ts +98 -0
- package/dist/sanity/symbol-index.js +615 -0
- package/package.json +2 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* load-preflight-scoring — read the W0198 preflight scoring config
|
|
3
|
+
* (`config/preflight-scoring.ts`) authored via `definePreflightScoring()`.
|
|
4
|
+
*
|
|
5
|
+
* Returns `undefined` when the file is absent so callers fall back to
|
|
6
|
+
* `DEFAULT_PREFLIGHT_CODE_CORRECTNESS_WEIGHT`. The eval package itself
|
|
7
|
+
* ships a config so the live pipeline always finds one; the optional
|
|
8
|
+
* return path exists for downstream / external callers that may not
|
|
9
|
+
* have authored one yet.
|
|
10
|
+
*/
|
|
11
|
+
import type { PreflightScoringConfig } from "../../_vendor/ailf-core/index.d.ts";
|
|
12
|
+
export declare function loadPreflightScoring(rootDir: string): Promise<PreflightScoringConfig | undefined>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* load-preflight-scoring — read the W0198 preflight scoring config
|
|
3
|
+
* (`config/preflight-scoring.ts`) authored via `definePreflightScoring()`.
|
|
4
|
+
*
|
|
5
|
+
* Returns `undefined` when the file is absent so callers fall back to
|
|
6
|
+
* `DEFAULT_PREFLIGHT_CODE_CORRECTNESS_WEIGHT`. The eval package itself
|
|
7
|
+
* ships a config so the live pipeline always finds one; the optional
|
|
8
|
+
* return path exists for downstream / external callers that may not
|
|
9
|
+
* have authored one yet.
|
|
10
|
+
*/
|
|
11
|
+
import { tryLoadConfigFile } from "../compiler/config-loader.js";
|
|
12
|
+
export async function loadPreflightScoring(rootDir) {
|
|
13
|
+
const result = tryLoadConfigFile("preflight-scoring", rootDir);
|
|
14
|
+
if (!result)
|
|
15
|
+
return undefined;
|
|
16
|
+
return result.data;
|
|
17
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* parse-imports — pure function that extracts every `import` declaration
|
|
3
|
+
* from a candidate code block as a flat list of per-binding entries.
|
|
4
|
+
*
|
|
5
|
+
* Output shape is intentionally flat: the W0198 preflight checks each
|
|
6
|
+
* `(source, imported)` pair against the resolved package surface, so a
|
|
7
|
+
* flat list is the natural input. Multi-binding declarations
|
|
8
|
+
* (`import { a, b as c } from "pkg"`) produce one entry per binding;
|
|
9
|
+
* default + named combos (`import def, { a } from "pkg"`) likewise.
|
|
10
|
+
*
|
|
11
|
+
* Implementation: delegates to `oxc-parser`'s `staticImports` view, which
|
|
12
|
+
* already decomposes each import statement into entries with explicit
|
|
13
|
+
* `importName` / `localName` / `isType` fields. The TS-aware grammar
|
|
14
|
+
* means dynamic `import(...)`, `import.meta`, and malformed statements
|
|
15
|
+
* are all handled by the parser itself — we just translate the entries
|
|
16
|
+
* into our `CandidateImportBinding` shape.
|
|
17
|
+
*
|
|
18
|
+
* Recognized grammar (handled by oxc-parser):
|
|
19
|
+
* - `import { a, b as c } from "pkg"`
|
|
20
|
+
* - `import def from "pkg"`
|
|
21
|
+
* - `import * as ns from "pkg"`
|
|
22
|
+
* - `import def, { a } from "pkg"` and `import def, * as ns from "pkg"`
|
|
23
|
+
* - `import type { ... } from "pkg"` and `import { type a, b } from "pkg"`
|
|
24
|
+
* - `import "pkg"` (side-effect; surfaced as a single `side-effect` entry)
|
|
25
|
+
* - Multi-line variants of all of the above.
|
|
26
|
+
*
|
|
27
|
+
* Out of scope (intentionally — both regex and oxc-parser ignore these):
|
|
28
|
+
* - Dynamic `import("pkg")` — runtime, not statically resolvable here.
|
|
29
|
+
* - `export { a } from "pkg"` re-exports from candidate code.
|
|
30
|
+
* - TypeScript `import = require()` and `import("...").Type` ambient
|
|
31
|
+
* references — neither pattern shows up in the App SDK / Studio
|
|
32
|
+
* candidate corpus the preflight grades against.
|
|
33
|
+
*/
|
|
34
|
+
export type CandidateImportKind = "named" | "default" | "namespace" | "side-effect";
|
|
35
|
+
export interface CandidateImportBinding {
|
|
36
|
+
/** Source specifier as written by the candidate (e.g. `"@sanity/sdk-react"`). */
|
|
37
|
+
source: string;
|
|
38
|
+
/** Which import-clause shape this binding came from. */
|
|
39
|
+
kind: CandidateImportKind;
|
|
40
|
+
/**
|
|
41
|
+
* Name to look up against the package surface:
|
|
42
|
+
* - `kind: "named"` — the imported identifier.
|
|
43
|
+
* - `kind: "default"` — the literal string `"default"`.
|
|
44
|
+
* - `kind: "namespace"` — the literal string `"*"`.
|
|
45
|
+
* - `kind: "side-effect"` — empty string (no binding).
|
|
46
|
+
*/
|
|
47
|
+
imported: string;
|
|
48
|
+
/** Local alias used in the candidate's body. Same as `imported` when no alias. */
|
|
49
|
+
local: string;
|
|
50
|
+
/**
|
|
51
|
+
* Whether this binding is type-only — either the whole declaration was
|
|
52
|
+
* `import type`, or this specifier was prefixed with `type`
|
|
53
|
+
* (`import { type X, Y } from "pkg"`).
|
|
54
|
+
*/
|
|
55
|
+
isType: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* 1-based line number where the import declaration starts in the
|
|
58
|
+
* source. Useful for surfacing findings back to a reviewer.
|
|
59
|
+
*/
|
|
60
|
+
line: number;
|
|
61
|
+
}
|
|
62
|
+
export declare function parseImports(src: string): CandidateImportBinding[];
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* parse-imports — pure function that extracts every `import` declaration
|
|
3
|
+
* from a candidate code block as a flat list of per-binding entries.
|
|
4
|
+
*
|
|
5
|
+
* Output shape is intentionally flat: the W0198 preflight checks each
|
|
6
|
+
* `(source, imported)` pair against the resolved package surface, so a
|
|
7
|
+
* flat list is the natural input. Multi-binding declarations
|
|
8
|
+
* (`import { a, b as c } from "pkg"`) produce one entry per binding;
|
|
9
|
+
* default + named combos (`import def, { a } from "pkg"`) likewise.
|
|
10
|
+
*
|
|
11
|
+
* Implementation: delegates to `oxc-parser`'s `staticImports` view, which
|
|
12
|
+
* already decomposes each import statement into entries with explicit
|
|
13
|
+
* `importName` / `localName` / `isType` fields. The TS-aware grammar
|
|
14
|
+
* means dynamic `import(...)`, `import.meta`, and malformed statements
|
|
15
|
+
* are all handled by the parser itself — we just translate the entries
|
|
16
|
+
* into our `CandidateImportBinding` shape.
|
|
17
|
+
*
|
|
18
|
+
* Recognized grammar (handled by oxc-parser):
|
|
19
|
+
* - `import { a, b as c } from "pkg"`
|
|
20
|
+
* - `import def from "pkg"`
|
|
21
|
+
* - `import * as ns from "pkg"`
|
|
22
|
+
* - `import def, { a } from "pkg"` and `import def, * as ns from "pkg"`
|
|
23
|
+
* - `import type { ... } from "pkg"` and `import { type a, b } from "pkg"`
|
|
24
|
+
* - `import "pkg"` (side-effect; surfaced as a single `side-effect` entry)
|
|
25
|
+
* - Multi-line variants of all of the above.
|
|
26
|
+
*
|
|
27
|
+
* Out of scope (intentionally — both regex and oxc-parser ignore these):
|
|
28
|
+
* - Dynamic `import("pkg")` — runtime, not statically resolvable here.
|
|
29
|
+
* - `export { a } from "pkg"` re-exports from candidate code.
|
|
30
|
+
* - TypeScript `import = require()` and `import("...").Type` ambient
|
|
31
|
+
* references — neither pattern shows up in the App SDK / Studio
|
|
32
|
+
* candidate corpus the preflight grades against.
|
|
33
|
+
*/
|
|
34
|
+
import { parseSync } from "oxc-parser";
|
|
35
|
+
export function parseImports(src) {
|
|
36
|
+
// Use the `.tsx` filename hint so the parser tolerates JSX in candidate
|
|
37
|
+
// code (App SDK literacy tasks frequently include JSX in their answers).
|
|
38
|
+
const result = parseSync("input.tsx", src, { lang: "tsx" });
|
|
39
|
+
const lineStarts = computeLineStarts(src);
|
|
40
|
+
const offsetToLine = (offset) => binarySearchLine(lineStarts, offset) + 1;
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const imp of result.module.staticImports) {
|
|
43
|
+
const source = imp.moduleRequest.value;
|
|
44
|
+
// The parser recovers from malformed specifiers (e.g. backtick-quoted
|
|
45
|
+
// module requests) by emitting an empty source; the W0198 preflight
|
|
46
|
+
// can't do anything useful with that, so drop those entries.
|
|
47
|
+
if (!source)
|
|
48
|
+
continue;
|
|
49
|
+
const line = offsetToLine(imp.start);
|
|
50
|
+
if (imp.entries.length === 0) {
|
|
51
|
+
// Side-effect form: `import "pkg"`.
|
|
52
|
+
out.push({
|
|
53
|
+
source,
|
|
54
|
+
kind: "side-effect",
|
|
55
|
+
imported: "",
|
|
56
|
+
local: "",
|
|
57
|
+
isType: false,
|
|
58
|
+
line,
|
|
59
|
+
});
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
for (const entry of imp.entries) {
|
|
63
|
+
const local = entry.localName.value;
|
|
64
|
+
const isType = entry.isType;
|
|
65
|
+
switch (entry.importName.kind) {
|
|
66
|
+
case "Name":
|
|
67
|
+
out.push({
|
|
68
|
+
source,
|
|
69
|
+
kind: "named",
|
|
70
|
+
imported: entry.importName.name ?? local,
|
|
71
|
+
local,
|
|
72
|
+
isType,
|
|
73
|
+
line,
|
|
74
|
+
});
|
|
75
|
+
break;
|
|
76
|
+
case "Default":
|
|
77
|
+
out.push({
|
|
78
|
+
source,
|
|
79
|
+
kind: "default",
|
|
80
|
+
imported: "default",
|
|
81
|
+
local,
|
|
82
|
+
isType,
|
|
83
|
+
line,
|
|
84
|
+
});
|
|
85
|
+
break;
|
|
86
|
+
case "NamespaceObject":
|
|
87
|
+
out.push({
|
|
88
|
+
source,
|
|
89
|
+
kind: "namespace",
|
|
90
|
+
imported: "*",
|
|
91
|
+
local,
|
|
92
|
+
isType,
|
|
93
|
+
line,
|
|
94
|
+
});
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Line-number helpers — oxc-parser returns byte offsets; the W0198
|
|
103
|
+
// preflight surfaces 1-based line numbers in findings so reviewers can
|
|
104
|
+
// jump straight to the citing line.
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
function computeLineStarts(src) {
|
|
107
|
+
const offsets = [0];
|
|
108
|
+
for (let i = 0; i < src.length; i++) {
|
|
109
|
+
if (src[i] === "\n")
|
|
110
|
+
offsets.push(i + 1);
|
|
111
|
+
}
|
|
112
|
+
return offsets;
|
|
113
|
+
}
|
|
114
|
+
function binarySearchLine(offsets, target) {
|
|
115
|
+
let lo = 0;
|
|
116
|
+
let hi = offsets.length - 1;
|
|
117
|
+
while (lo < hi) {
|
|
118
|
+
const mid = (lo + hi + 1) >>> 1;
|
|
119
|
+
if (offsets[mid] <= target)
|
|
120
|
+
lo = mid;
|
|
121
|
+
else
|
|
122
|
+
hi = mid - 1;
|
|
123
|
+
}
|
|
124
|
+
return lo;
|
|
125
|
+
}
|
package/dist/report-store.d.ts
CHANGED
|
@@ -65,6 +65,10 @@ export declare class ReportStore {
|
|
|
65
65
|
* matching `evalFingerprint`. Used by the pipeline to skip the expensive
|
|
66
66
|
* eval step when identical inputs have already been evaluated.
|
|
67
67
|
*
|
|
68
|
+
* Advisory lookup: a `ReportSchemaValidationError` from a corrupt prior
|
|
69
|
+
* doc is logged + counted, then null is returned so the current eval
|
|
70
|
+
* proceeds. Use `read(id)` when callers ask for a specific document by id.
|
|
71
|
+
*
|
|
68
72
|
* @returns The cached report, or null if no match or on error
|
|
69
73
|
* @see docs/design-docs/content-lake-eval-caching.md
|
|
70
74
|
*/
|
|
@@ -78,6 +82,10 @@ export declare class ReportStore {
|
|
|
78
82
|
* "Comparable" means: same evaluation mode + same source name.
|
|
79
83
|
* More granular matching (areas, models) can be added as needed.
|
|
80
84
|
*
|
|
85
|
+
* Advisory lookup: see `findByFingerprint` — a malformed prior baseline
|
|
86
|
+
* is logged + counted and null is returned so the current run still
|
|
87
|
+
* publishes its report.
|
|
88
|
+
*
|
|
81
89
|
* @see docs/design-docs/report-store/architecture.md — Auto-comparison
|
|
82
90
|
*/
|
|
83
91
|
findComparableBaseline(query: LineageQuery): Promise<null | Report>;
|
package/dist/report-store.js
CHANGED
|
@@ -119,6 +119,10 @@ export class ReportStore {
|
|
|
119
119
|
* matching `evalFingerprint`. Used by the pipeline to skip the expensive
|
|
120
120
|
* eval step when identical inputs have already been evaluated.
|
|
121
121
|
*
|
|
122
|
+
* Advisory lookup: a `ReportSchemaValidationError` from a corrupt prior
|
|
123
|
+
* doc is logged + counted, then null is returned so the current eval
|
|
124
|
+
* proceeds. Use `read(id)` when callers ask for a specific document by id.
|
|
125
|
+
*
|
|
122
126
|
* @returns The cached report, or null if no match or on error
|
|
123
127
|
* @see docs/design-docs/content-lake-eval-caching.md
|
|
124
128
|
*/
|
|
@@ -131,9 +135,19 @@ export class ReportStore {
|
|
|
131
135
|
return doc ? toReport(doc) : null;
|
|
132
136
|
}
|
|
133
137
|
catch (error) {
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
138
|
+
// Advisory lookup — a single corrupt prior doc must not break the
|
|
139
|
+
// current eval. Log loudly + emit a counter so ops can alert, and
|
|
140
|
+
// return null so the caller treats it as "no comparable cache hit".
|
|
141
|
+
// Direct read(id) keeps the rethrow behavior because the caller asked
|
|
142
|
+
// for that specific document and silent-null would mask the bug.
|
|
143
|
+
if (error instanceof ReportSchemaValidationError) {
|
|
144
|
+
logAdvisoryQuerySchemaFailure({
|
|
145
|
+
query: "findByFingerprint",
|
|
146
|
+
context: { fingerprint },
|
|
147
|
+
error,
|
|
148
|
+
});
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
137
151
|
console.warn(` ⚠️ Failed to query cached report by fingerprint: ${error instanceof Error ? error.message : String(error)}`);
|
|
138
152
|
return null;
|
|
139
153
|
}
|
|
@@ -147,6 +161,10 @@ export class ReportStore {
|
|
|
147
161
|
* "Comparable" means: same evaluation mode + same source name.
|
|
148
162
|
* More granular matching (areas, models) can be added as needed.
|
|
149
163
|
*
|
|
164
|
+
* Advisory lookup: see `findByFingerprint` — a malformed prior baseline
|
|
165
|
+
* is logged + counted and null is returned so the current run still
|
|
166
|
+
* publishes its report.
|
|
167
|
+
*
|
|
150
168
|
* @see docs/design-docs/report-store/architecture.md — Auto-comparison
|
|
151
169
|
*/
|
|
152
170
|
async findComparableBaseline(query) {
|
|
@@ -170,9 +188,21 @@ export class ReportStore {
|
|
|
170
188
|
return doc ? toReport(doc) : null;
|
|
171
189
|
}
|
|
172
190
|
catch (error) {
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
191
|
+
// Advisory lookup — see findByFingerprint for rationale. A malformed
|
|
192
|
+
// prior baseline returns null + counter so the current run still
|
|
193
|
+
// publishes; direct read(id) preserves the rethrow.
|
|
194
|
+
if (error instanceof ReportSchemaValidationError) {
|
|
195
|
+
logAdvisoryQuerySchemaFailure({
|
|
196
|
+
query: "findComparableBaseline",
|
|
197
|
+
context: {
|
|
198
|
+
mode: query.mode,
|
|
199
|
+
sourceName: query.source?.name,
|
|
200
|
+
before: query.before,
|
|
201
|
+
},
|
|
202
|
+
error,
|
|
203
|
+
});
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
176
206
|
console.warn(` ⚠️ Failed to query comparable baseline: ${error instanceof Error ? error.message : String(error)}`);
|
|
177
207
|
return null;
|
|
178
208
|
}
|
|
@@ -299,6 +329,25 @@ export class ReportSchemaValidationError extends Error {
|
|
|
299
329
|
this.name = "ReportSchemaValidationError";
|
|
300
330
|
}
|
|
301
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* Stable log marker for log-aggregator counters. Operators alert on the
|
|
334
|
+
* count of `[report-store.advisory] schema_validation_error` lines per
|
|
335
|
+
* window; the trailing JSON carries enough context (which advisory query,
|
|
336
|
+
* what filter values, error message) to find the offending document
|
|
337
|
+
* without spelunking through GROQ.
|
|
338
|
+
*
|
|
339
|
+
* Emitted via console.error rather than console.warn so it surfaces in
|
|
340
|
+
* the same severity tier as a real failure even though we're swallowing
|
|
341
|
+
* it for the current run.
|
|
342
|
+
*/
|
|
343
|
+
function logAdvisoryQuerySchemaFailure(input) {
|
|
344
|
+
const payload = {
|
|
345
|
+
query: input.query,
|
|
346
|
+
context: input.context,
|
|
347
|
+
error: input.error.message,
|
|
348
|
+
};
|
|
349
|
+
console.error(`[report-store.advisory] schema_validation_error ${JSON.stringify(payload)}`);
|
|
350
|
+
}
|
|
302
351
|
export function toSanityReportDoc(report) {
|
|
303
352
|
const comparison = report.comparison
|
|
304
353
|
? stripComparisonBulk(report.comparison)
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* document-renderers.ts
|
|
3
3
|
*
|
|
4
|
-
* Renderer registry that turns a Sanity document
|
|
5
|
-
* Markdown for inclusion in a literacy task's grader context.
|
|
4
|
+
* Renderer registry that turns a Sanity document into both:
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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):
|
|
9
20
|
*
|
|
10
21
|
* 1. Registered renderers (high fidelity) — `article`, `typesReference`.
|
|
11
22
|
* Hand-written for the document shapes we care about most.
|
|
@@ -14,9 +25,11 @@
|
|
|
14
25
|
* skips framework-internal fields. Lets pinning a `marketingPage`,
|
|
15
26
|
* `glossaryEntry`, etc. work without AILF code changes.
|
|
16
27
|
*
|
|
17
|
-
* Adding a new high-fidelity renderer: implement a `DocumentRenderer`
|
|
18
|
-
*
|
|
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`.
|
|
19
31
|
*/
|
|
32
|
+
import { type SymbolIndex } from "./symbol-index.js";
|
|
20
33
|
/**
|
|
21
34
|
* A Sanity document plus any references we've already resolved for it.
|
|
22
35
|
* The resolver fetches the doc once and may include common deref payloads
|
|
@@ -57,12 +70,37 @@ export interface RenderResult {
|
|
|
57
70
|
*/
|
|
58
71
|
slug: string;
|
|
59
72
|
}
|
|
60
|
-
export
|
|
73
|
+
export interface DocumentRenderer {
|
|
74
|
+
/**
|
|
75
|
+
* Produce rendered Markdown for the doc's content surface (for grader
|
|
76
|
+
* + candidate context inclusion).
|
|
77
|
+
*/
|
|
78
|
+
render(doc: DocumentForRender, ctx: RenderContext): Promise<RenderResult> | RenderResult;
|
|
79
|
+
/**
|
|
80
|
+
* Produce a symbol-reference index for the doc — a flat list of
|
|
81
|
+
* identifiers the doc legitimizes plus provenance snippets. Used by
|
|
82
|
+
* the grader-context pathway (W0197) instead of injecting the full
|
|
83
|
+
* rendered doc. Returning an empty index signals the caller to fall
|
|
84
|
+
* back to the rendered markdown.
|
|
85
|
+
*/
|
|
86
|
+
extractSymbols(doc: DocumentForRender, ctx: RenderContext): Promise<SymbolIndex> | SymbolIndex;
|
|
87
|
+
}
|
|
61
88
|
/**
|
|
62
89
|
* Render a document using the registered renderer for its `_type`, falling
|
|
63
90
|
* back to the default walker. The returned `fidelity` flag tells callers
|
|
64
91
|
* whether to emit the "info: dedicated renderer would help" log.
|
|
65
92
|
*/
|
|
66
93
|
export declare function renderDocument(doc: DocumentForRender, ctx?: RenderContext): Promise<RenderResult>;
|
|
94
|
+
/**
|
|
95
|
+
* Extract a symbol-reference index for a document using its registered
|
|
96
|
+
* renderer (or the default walker for unknown types). Used by the
|
|
97
|
+
* grader-context pathway (W0197) to feed the LLM judge a compact
|
|
98
|
+
* deterministic recognition reference instead of the full rendered doc.
|
|
99
|
+
*
|
|
100
|
+
* Returns an empty `SymbolIndex` (`{ symbols: [] }`) when extraction
|
|
101
|
+
* yields nothing — callers interpret this as the signal to fall back to
|
|
102
|
+
* the rendered markdown.
|
|
103
|
+
*/
|
|
104
|
+
export declare function extractSymbolsForDoc(doc: DocumentForRender, ctx?: RenderContext): Promise<SymbolIndex>;
|
|
67
105
|
/** Exported for tests and consumers that want the registered set. */
|
|
68
106
|
export declare const REGISTERED_RENDERER_TYPES: string[];
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* document-renderers.ts
|
|
3
3
|
*
|
|
4
|
-
* Renderer registry that turns a Sanity document
|
|
5
|
-
* Markdown for inclusion in a literacy task's grader context.
|
|
4
|
+
* Renderer registry that turns a Sanity document into both:
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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):
|
|
9
20
|
*
|
|
10
21
|
* 1. Registered renderers (high fidelity) — `article`, `typesReference`.
|
|
11
22
|
* Hand-written for the document shapes we care about most.
|
|
@@ -14,10 +25,12 @@
|
|
|
14
25
|
* skips framework-internal fields. Lets pinning a `marketingPage`,
|
|
15
26
|
* `glossaryEntry`, etc. work without AILF code changes.
|
|
16
27
|
*
|
|
17
|
-
* Adding a new high-fidelity renderer: implement a `DocumentRenderer`
|
|
18
|
-
*
|
|
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`.
|
|
19
31
|
*/
|
|
20
32
|
import { toMarkdown } from "./portable-text.js";
|
|
33
|
+
import { extractSymbolIndex, extractSymbolsFromTypedoc, mergeSymbolIndexes, } from "./symbol-index.js";
|
|
21
34
|
// ---------------------------------------------------------------------------
|
|
22
35
|
// Helpers
|
|
23
36
|
// ---------------------------------------------------------------------------
|
|
@@ -56,7 +69,7 @@ function slugForDoc(doc) {
|
|
|
56
69
|
return slugField;
|
|
57
70
|
return `${doc._type}:${doc._id}`;
|
|
58
71
|
}
|
|
59
|
-
function
|
|
72
|
+
function renderArticle(doc) {
|
|
60
73
|
const title = doc.title ?? "(untitled)";
|
|
61
74
|
const description = doc.description;
|
|
62
75
|
const section = doc.section;
|
|
@@ -70,7 +83,16 @@ function articleRenderer(doc) {
|
|
|
70
83
|
slug: slugForDoc(doc),
|
|
71
84
|
};
|
|
72
85
|
}
|
|
73
|
-
|
|
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) {
|
|
74
96
|
const title = doc.title ?? "(untitled)";
|
|
75
97
|
const slug = slugForDoc(doc);
|
|
76
98
|
const library = doc.library;
|
|
@@ -109,6 +131,24 @@ async function typesReferenceRenderer(doc, ctx) {
|
|
|
109
131
|
lines.push("```");
|
|
110
132
|
return { content: lines.join("\n"), fidelity: "high", slug };
|
|
111
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
|
+
};
|
|
112
152
|
// ---------------------------------------------------------------------------
|
|
113
153
|
// formatDefault — generic walker for any unknown `_type`.
|
|
114
154
|
//
|
|
@@ -174,7 +214,7 @@ function renderField(key, value, depth = 0) {
|
|
|
174
214
|
}
|
|
175
215
|
return null;
|
|
176
216
|
}
|
|
177
|
-
function
|
|
217
|
+
function renderDefault(doc) {
|
|
178
218
|
const title = doc.title ?? `(${doc._type})`;
|
|
179
219
|
const slug = slugForDoc(doc);
|
|
180
220
|
const lines = [`## ${title}`, "", `Type: \`${doc._type}\``];
|
|
@@ -199,6 +239,39 @@ function defaultRenderer(doc) {
|
|
|
199
239
|
}
|
|
200
240
|
return { content: lines.join("\n"), fidelity: "default", slug };
|
|
201
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
|
+
};
|
|
202
275
|
// ---------------------------------------------------------------------------
|
|
203
276
|
// Registry
|
|
204
277
|
// ---------------------------------------------------------------------------
|
|
@@ -206,16 +279,29 @@ const BUILT_IN_RENDERERS = {
|
|
|
206
279
|
article: articleRenderer,
|
|
207
280
|
typesReference: typesReferenceRenderer,
|
|
208
281
|
};
|
|
282
|
+
function rendererFor(type) {
|
|
283
|
+
return BUILT_IN_RENDERERS[type] ?? defaultRenderer;
|
|
284
|
+
}
|
|
209
285
|
/**
|
|
210
286
|
* Render a document using the registered renderer for its `_type`, falling
|
|
211
287
|
* back to the default walker. The returned `fidelity` flag tells callers
|
|
212
288
|
* whether to emit the "info: dedicated renderer would help" log.
|
|
213
289
|
*/
|
|
214
290
|
export async function renderDocument(doc, ctx = {}) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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);
|
|
219
305
|
}
|
|
220
306
|
/** Exported for tests and consumers that want the registered set. */
|
|
221
307
|
export const REGISTERED_RENDERER_TYPES = Object.keys(BUILT_IN_RENDERERS).sort();
|