@sanity/ailf 0.1.34 → 0.2.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.
@@ -0,0 +1,118 @@
1
+ /**
2
+ * pipeline/report-title.ts
3
+ *
4
+ * Pure function that generates descriptive report titles from provenance
5
+ * metadata. The title is the primary display string shown in dashboards,
6
+ * Slack digests, and Studio views — it conveys trigger context, evaluated
7
+ * areas, source/perspective, and document scope at a glance.
8
+ *
9
+ * Score is intentionally omitted from the title since it is surfaced
10
+ * heavily elsewhere in the UI. The `tag` field (on Report) is preserved
11
+ * as a secondary label; the title is the primary display string.
12
+ *
13
+ * Segments are joined with ` · ` (middle dot with spaces).
14
+ *
15
+ * @see docs/design-docs/report-store/domain-model.md
16
+ * @see packages/eval/src/pipeline/provenance.ts — builds the provenance input
17
+ */
18
+ // ---------------------------------------------------------------------------
19
+ // Segment builders
20
+ // ---------------------------------------------------------------------------
21
+ const SEPARATOR = " · ";
22
+ /** Segment 1 — human-readable trigger context */
23
+ function triggerSegment(trigger) {
24
+ switch (trigger.type) {
25
+ case "scheduled": {
26
+ const name = trigger.schedule.replace(/-/g, " ");
27
+ return name.charAt(0).toUpperCase() + name.slice(1);
28
+ }
29
+ case "ci":
30
+ return "CI eval";
31
+ case "webhook":
32
+ return "Content change";
33
+ case "cross-repo": {
34
+ // Only show the repo name if callerRepo looks like "owner/repo".
35
+ // Numeric IDs (e.g. GITHUB_REPOSITORY_OWNER_ID fallback) are not useful.
36
+ const repo = trigger.callerRepo;
37
+ if (repo.includes("/")) {
38
+ const shortName = repo.split("/").pop() ?? repo;
39
+ return `Cross-repo (${shortName})`;
40
+ }
41
+ return "Cross-repo";
42
+ }
43
+ case "manual":
44
+ return "Manual eval";
45
+ }
46
+ }
47
+ /** Segment 2 — areas evaluated (omitted when empty) */
48
+ function areasSegment(areas, totalAreaCount) {
49
+ if (areas.length === 0)
50
+ return undefined;
51
+ if (areas.length <= 3) {
52
+ return areas.join(", ");
53
+ }
54
+ if (totalAreaCount !== undefined && areas.length === totalAreaCount) {
55
+ return "All areas";
56
+ }
57
+ return `${areas.length} areas`;
58
+ }
59
+ /** Segment 3 — source context (omitted when default production, no perspective) */
60
+ function sourceSegment(source) {
61
+ const parts = [];
62
+ if (source.perspective) {
63
+ parts.push(`perspective: ${source.perspective}`);
64
+ }
65
+ if (source.name !== "production") {
66
+ parts.push(source.name);
67
+ }
68
+ return parts.length > 0 ? parts.join(", ") : undefined;
69
+ }
70
+ /** Segment 4 — target documents (omitted when not scoped) */
71
+ function targetDocumentsSegment(targetDocuments) {
72
+ if (!targetDocuments || targetDocuments.length === 0)
73
+ return undefined;
74
+ if (targetDocuments.length === 1) {
75
+ return targetDocuments[0];
76
+ }
77
+ return `${targetDocuments.length} documents`;
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Public API
81
+ // ---------------------------------------------------------------------------
82
+ /**
83
+ * Generate a descriptive report title from provenance metadata.
84
+ *
85
+ * The title is composed of up to four segments separated by ` · `:
86
+ *
87
+ * 1. **Trigger context** — what initiated the evaluation (always present)
88
+ * 2. **Areas** — which feature areas were evaluated (omitted if empty)
89
+ * 3. **Source context** — non-default source or perspective (omitted if default)
90
+ * 4. **Target documents** — scoped document IDs (omitted if not scoped)
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * generateReportTitle({
95
+ * provenance: {
96
+ * areas: ["GROQ", "Mutations"],
97
+ * mode: "baseline",
98
+ * source: { name: "production" },
99
+ * trigger: { type: "manual" },
100
+ * },
101
+ * })
102
+ * // → "Manual eval · GROQ, Mutations"
103
+ * ```
104
+ */
105
+ export function generateReportTitle(input) {
106
+ const { provenance, totalAreaCount } = input;
107
+ const segments = [triggerSegment(provenance.trigger)];
108
+ const areas = areasSegment(provenance.areas, totalAreaCount);
109
+ if (areas)
110
+ segments.push(areas);
111
+ const source = sourceSegment(provenance.source);
112
+ if (source)
113
+ segments.push(source);
114
+ const docs = targetDocumentsSegment(provenance.targetDocuments);
115
+ if (docs)
116
+ segments.push(docs);
117
+ return segments.join(SEPARATOR);
118
+ }
@@ -203,6 +203,7 @@ export class ReportStore {
203
203
  reportId: report.id,
204
204
  summary: report.summary,
205
205
  tag: report.tag ?? null,
206
+ title: report.title ?? null,
206
207
  });
207
208
  return report.id;
208
209
  }
@@ -255,5 +256,6 @@ function toReport(doc) {
255
256
  provenance: doc.provenance,
256
257
  summary: doc.summary,
257
258
  tag: doc.tag,
259
+ title: doc.title,
258
260
  };
259
261
  }
@@ -71,6 +71,7 @@ export interface ReportRow {
71
71
  source_name: string;
72
72
  source_perspective: null | string;
73
73
  tag: null | string;
74
+ title: null | string;
74
75
  total_cost: null | number;
75
76
  trigger_caller_repo: null | string;
76
77
  trigger_type: string;
@@ -213,6 +213,7 @@ export function flattenReportRow(report) {
213
213
  source_name: provenance.source.name,
214
214
  source_perspective: provenance.source.perspective ?? null,
215
215
  tag: report.tag ?? null,
216
+ title: report.title ?? null,
216
217
  total_cost: summary.overall.cost?.total ?? null,
217
218
  trigger_caller_repo: provenance.trigger.type === "cross-repo"
218
219
  ? provenance.trigger.callerRepo
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/ailf",
3
- "version": "0.1.34",
3
+ "version": "0.2.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "restricted"
@@ -23,6 +23,27 @@
23
23
  "canonical",
24
24
  "tasks"
25
25
  ],
26
+ "dependencies": {
27
+ "@google-cloud/bigquery": "^8.1.1",
28
+ "@inquirer/prompts": "^8.3.0",
29
+ "@portabletext/markdown": "^1.0.0",
30
+ "@sanity/client": "^7.3.0",
31
+ "commander": "^14.0.3",
32
+ "dotenv": "^16.4.7",
33
+ "dotenv-cli": "^11.0.0",
34
+ "js-yaml": "^4.1.0",
35
+ "promptfoo": "^0.120.24",
36
+ "zod": "^4.3.6"
37
+ },
38
+ "devDependencies": {
39
+ "@types/js-yaml": "^4.0.9",
40
+ "@types/node": "^22.13.1",
41
+ "tsx": "^4.19.2",
42
+ "typescript": "^5.7.3",
43
+ "@sanity/ailf-core": "0.1.0",
44
+ "@sanity/ailf-shared": "0.1.0",
45
+ "@sanity/ailf-tasks": "0.1.4"
46
+ },
26
47
  "scripts": {
27
48
  "build": "tsc && tsx scripts/bundle-workspace-deps.ts",
28
49
  "generate-configs": "tsx src/cli.ts generate-configs",
@@ -48,26 +69,5 @@
48
69
  "discovery-report": "tsx src/cli.ts discovery-report",
49
70
  "webhook-server": "tsx src/cli.ts webhook-server",
50
71
  "weekly-digest": "tsx src/cli.ts weekly-digest"
51
- },
52
- "dependencies": {
53
- "@google-cloud/bigquery": "^8.1.1",
54
- "@inquirer/prompts": "^8.3.0",
55
- "@portabletext/markdown": "^1.0.0",
56
- "@sanity/client": "^7.3.0",
57
- "commander": "^14.0.3",
58
- "dotenv": "^16.4.7",
59
- "dotenv-cli": "^11.0.0",
60
- "js-yaml": "^4.1.0",
61
- "promptfoo": "^0.120.24",
62
- "zod": "^4.3.6"
63
- },
64
- "devDependencies": {
65
- "@sanity/ailf-core": "workspace:*",
66
- "@sanity/ailf-shared": "workspace:*",
67
- "@sanity/ailf-tasks": "workspace:*",
68
- "@types/js-yaml": "^4.0.9",
69
- "@types/node": "^22.13.1",
70
- "tsx": "^4.19.2",
71
- "typescript": "^5.7.3"
72
72
  }
73
- }
73
+ }