@sanity/ailf 2.0.1 → 2.1.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/LICENSE +21 -0
- package/dist/cli.js +0 -0
- package/dist/orchestration/steps/run-eval-step.js +1 -1
- package/dist/pipeline/checks.d.ts +8 -3
- package/dist/pipeline/checks.js +23 -3
- package/package.json +25 -25
- package/dist/_vendor/ailf-core/__tests__/comparison-formatters.test.d.ts +0 -10
- package/dist/_vendor/ailf-core/__tests__/comparison-formatters.test.js +0 -185
- package/dist/_vendor/ailf-core/artifact-capture/__tests__/noop-collector.test.d.ts +0 -6
- package/dist/_vendor/ailf-core/artifact-capture/__tests__/noop-collector.test.js +0 -42
- package/dist/_vendor/ailf-tasks/cli.d.ts +0 -8
- package/dist/_vendor/ailf-tasks/cli.js +0 -61
- package/dist/_vendor/ailf-tasks/index.d.ts +0 -13
- package/dist/_vendor/ailf-tasks/index.js +0 -16
- package/dist/_vendor/ailf-tasks/parser.d.ts +0 -27
- package/dist/_vendor/ailf-tasks/parser.js +0 -73
- package/dist/_vendor/ailf-tasks/schemas.d.ts +0 -198
- package/dist/_vendor/ailf-tasks/schemas.js +0 -180
- package/dist/_vendor/ailf-tasks/validation.d.ts +0 -47
- package/dist/_vendor/ailf-tasks/validation.js +0 -162
- package/dist/adapters/task-sources/yaml-task-source.d.ts +0 -18
- package/dist/adapters/task-sources/yaml-task-source.js +0 -139
- package/dist/agent-observer/test-imports.d.ts +0 -7
- package/dist/agent-observer/test-imports.js +0 -185
- package/dist/commands/update-quality-scores.d.ts +0 -5
- package/dist/commands/update-quality-scores.js +0 -20
- package/dist/lib/agent-behavior-report.d.ts +0 -8
- package/dist/lib/agent-behavior-report.js +0 -185
- package/dist/lib/baseline.d.ts +0 -19
- package/dist/lib/baseline.js +0 -153
- package/dist/lib/calculate-scores.d.ts +0 -23
- package/dist/lib/calculate-scores.js +0 -42
- package/dist/lib/compare.d.ts +0 -18
- package/dist/lib/compare.js +0 -170
- package/dist/lib/coverage-audit.d.ts +0 -4
- package/dist/lib/coverage-audit.js +0 -42
- package/dist/lib/discovery-report.d.ts +0 -13
- package/dist/lib/discovery-report.js +0 -57
- package/dist/lib/fetch-docs.d.ts +0 -30
- package/dist/lib/fetch-docs.js +0 -171
- package/dist/lib/generate-configs.d.ts +0 -25
- package/dist/lib/generate-configs.js +0 -42
- package/dist/lib/grader-api.d.ts +0 -21
- package/dist/lib/grader-api.js +0 -34
- package/dist/lib/grader-compare.d.ts +0 -19
- package/dist/lib/grader-compare.js +0 -91
- package/dist/lib/grader-consistency.d.ts +0 -27
- package/dist/lib/grader-consistency.js +0 -79
- package/dist/lib/grader-sensitivity.d.ts +0 -19
- package/dist/lib/grader-sensitivity.js +0 -75
- package/dist/lib/grader-validate.d.ts +0 -19
- package/dist/lib/grader-validate.js +0 -78
- package/dist/lib/measure-retrieval.d.ts +0 -14
- package/dist/lib/measure-retrieval.js +0 -71
- package/dist/lib/pr-comment.d.ts +0 -16
- package/dist/lib/pr-comment.js +0 -28
- package/dist/lib/readiness-report.d.ts +0 -13
- package/dist/lib/readiness-report.js +0 -108
- package/dist/lib/webhook-server.d.ts +0 -11
- package/dist/lib/webhook-server.js +0 -24
- package/dist/lib/weekly-digest.d.ts +0 -24
- package/dist/lib/weekly-digest.js +0 -148
- package/dist/orchestration/env-bridge.d.ts +0 -21
- package/dist/orchestration/env-bridge.js +0 -66
- package/dist/orchestration/steps/fetch-docs-shell.d.ts +0 -17
- package/dist/orchestration/steps/fetch-docs-shell.js +0 -30
- package/dist/pipeline/compiler/__tests__/task-bridge.test.d.ts +0 -9
- package/dist/pipeline/compiler/__tests__/task-bridge.test.js +0 -339
- package/dist/pipeline/compiler/mode-handlers/agent-harness-handler.d.ts +0 -70
- package/dist/pipeline/compiler/mode-handlers/agent-harness-handler.js +0 -485
- package/dist/pipeline/compiler/mode-handlers/knowledge-probe-handler.d.ts +0 -76
- package/dist/pipeline/compiler/mode-handlers/knowledge-probe-handler.js +0 -245
- package/dist/pipeline/compiler/mode-handlers/literacy-handler.d.ts +0 -89
- package/dist/pipeline/compiler/mode-handlers/literacy-handler.js +0 -379
- package/dist/pipeline/compiler/mode-handlers/mcp-assertions.d.ts +0 -50
- package/dist/pipeline/compiler/mode-handlers/mcp-assertions.js +0 -334
- package/dist/pipeline/compiler/mode-handlers/mcp-server-handler.d.ts +0 -69
- package/dist/pipeline/compiler/mode-handlers/mcp-server-handler.js +0 -307
- package/dist/pipeline/compiler/mode-handlers/mcp-tool-provider.d.ts +0 -65
- package/dist/pipeline/compiler/mode-handlers/mcp-tool-provider.js +0 -368
- package/dist/pipeline/compiler/task-bridge.d.ts +0 -41
- package/dist/pipeline/compiler/task-bridge.js +0 -92
- package/dist/pipeline/expand-tasks.d.ts +0 -232
- package/dist/pipeline/expand-tasks.js +0 -467
- package/dist/pipeline/generate-configs.d.ts +0 -92
- package/dist/pipeline/generate-configs.js +0 -445
- package/dist/pipeline/steps/calculate-scores-step.d.ts +0 -11
- package/dist/pipeline/steps/calculate-scores-step.js +0 -89
- package/dist/pipeline/steps/compare-step.d.ts +0 -18
- package/dist/pipeline/steps/compare-step.js +0 -90
- package/dist/pipeline/steps/eval-step.d.ts +0 -53
- package/dist/pipeline/steps/eval-step.js +0 -347
- package/dist/pipeline/steps/fetch-docs-step.d.ts +0 -11
- package/dist/pipeline/steps/fetch-docs-step.js +0 -84
- package/dist/pipeline/steps/generate-configs-step.d.ts +0 -11
- package/dist/pipeline/steps/generate-configs-step.js +0 -98
- package/dist/pipeline/steps/grader-consistency-step.d.ts +0 -21
- package/dist/pipeline/steps/grader-consistency-step.js +0 -74
- package/dist/pipeline/steps/publish-report-step.d.ts +0 -57
- package/dist/pipeline/steps/publish-report-step.js +0 -243
- package/dist/pipeline/steps/report-step.d.ts +0 -13
- package/dist/pipeline/steps/report-step.js +0 -56
- package/dist/pipeline/steps/update-scores-step.d.ts +0 -11
- package/dist/pipeline/steps/update-scores-step.js +0 -42
- package/dist/scripts/agent-behavior-report.d.ts +0 -19
- package/dist/scripts/agent-behavior-report.js +0 -315
- package/dist/scripts/baseline.d.ts +0 -43
- package/dist/scripts/baseline.js +0 -267
- package/dist/scripts/calculate-scores.d.ts +0 -166
- package/dist/scripts/calculate-scores.js +0 -1296
- package/dist/scripts/compare.d.ts +0 -22
- package/dist/scripts/compare.js +0 -334
- package/dist/scripts/coverage-audit.d.ts +0 -44
- package/dist/scripts/coverage-audit.js +0 -209
- package/dist/scripts/debug-eval.d.ts +0 -19
- package/dist/scripts/debug-eval.js +0 -73
- package/dist/scripts/discovery-report.d.ts +0 -58
- package/dist/scripts/discovery-report.js +0 -250
- package/dist/scripts/fetch-docs.d.ts +0 -35
- package/dist/scripts/fetch-docs.js +0 -472
- package/dist/scripts/generate-configs.d.ts +0 -66
- package/dist/scripts/generate-configs.js +0 -459
- package/dist/scripts/grader-api.d.ts +0 -27
- package/dist/scripts/grader-api.js +0 -206
- package/dist/scripts/grader-compare.d.ts +0 -22
- package/dist/scripts/grader-compare.js +0 -368
- package/dist/scripts/grader-consistency.d.ts +0 -20
- package/dist/scripts/grader-consistency.js +0 -313
- package/dist/scripts/grader-sensitivity.d.ts +0 -22
- package/dist/scripts/grader-sensitivity.js +0 -354
- package/dist/scripts/grader-validate.d.ts +0 -19
- package/dist/scripts/grader-validate.js +0 -267
- package/dist/scripts/measure-retrieval.d.ts +0 -10
- package/dist/scripts/measure-retrieval.js +0 -145
- package/dist/scripts/migrate-tasks-to-content-lake.d.ts +0 -24
- package/dist/scripts/migrate-tasks-to-content-lake.js +0 -328
- package/dist/scripts/pipeline.d.ts +0 -76
- package/dist/scripts/pipeline.js +0 -1031
- package/dist/scripts/pr-comment.d.ts +0 -10
- package/dist/scripts/pr-comment.js +0 -510
- package/dist/scripts/readiness-report.d.ts +0 -88
- package/dist/scripts/readiness-report.js +0 -342
- package/dist/scripts/update-quality-scores.d.ts +0 -15
- package/dist/scripts/update-quality-scores.js +0 -184
- package/dist/scripts/validate-task-sources.d.ts +0 -21
- package/dist/scripts/validate-task-sources.js +0 -210
- package/dist/scripts/validate.d.ts +0 -13
- package/dist/scripts/validate.js +0 -79
- package/dist/scripts/webhook-server.d.ts +0 -26
- package/dist/scripts/webhook-server.js +0 -147
- package/dist/scripts/weekly-digest.d.ts +0 -24
- package/dist/scripts/weekly-digest.js +0 -144
- package/dist/sinks/format-slack.d.ts +0 -64
- package/dist/sinks/format-slack.js +0 -306
- package/dist/sinks/slack-sink.d.ts +0 -27
- package/dist/sinks/slack-sink.js +0 -78
- package/dist/sinks/webhook-sink.d.ts +0 -19
- package/dist/sinks/webhook-sink.js +0 -50
- package/tasks/.expanded.agentic.yaml +0 -280
- package/tasks/.expanded.yaml +0 -565
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sinks/format-slack.ts
|
|
3
|
-
*
|
|
4
|
-
* Formats evaluation report data into Slack Block Kit structures for the
|
|
5
|
-
* SlackSink. Provides two message formats:
|
|
6
|
-
*
|
|
7
|
-
* - `formatRegressionAlert` — detailed regression notification with
|
|
8
|
-
* per-area dimension breakdowns
|
|
9
|
-
* - `formatScoreSummary` — compact score overview for general reporting
|
|
10
|
-
*
|
|
11
|
-
* @see docs/design-docs/report-store/sink-architecture.md
|
|
12
|
-
*/
|
|
13
|
-
import type { Report } from "../pipeline/types.js";
|
|
14
|
-
import type { DigestSummary } from "../schedules/digest.js";
|
|
15
|
-
export interface SlackMessage {
|
|
16
|
-
blocks: SlackBlock[];
|
|
17
|
-
text: string;
|
|
18
|
-
}
|
|
19
|
-
interface SlackBlock {
|
|
20
|
-
elements?: Array<{
|
|
21
|
-
text: string;
|
|
22
|
-
type: "mrkdwn" | "plain_text";
|
|
23
|
-
}>;
|
|
24
|
-
fields?: Array<{
|
|
25
|
-
text: string;
|
|
26
|
-
type: "mrkdwn" | "plain_text";
|
|
27
|
-
}>;
|
|
28
|
-
text?: {
|
|
29
|
-
text: string;
|
|
30
|
-
type: "mrkdwn" | "plain_text";
|
|
31
|
-
};
|
|
32
|
-
type: "context" | "divider" | "header" | "section";
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Format a regression alert for areas that have regressed.
|
|
36
|
-
*
|
|
37
|
-
* Produces a rich Slack message with:
|
|
38
|
-
* - Header with overall score change
|
|
39
|
-
* - Context metadata (mode, source, timestamp, promptfoo link)
|
|
40
|
-
* - Per-area regression details with dimension breakdowns
|
|
41
|
-
* - Brief mentions of improved and unchanged areas
|
|
42
|
-
*/
|
|
43
|
-
export declare function formatRegressionAlert(report: Report): SlackMessage;
|
|
44
|
-
/**
|
|
45
|
-
* Format a general score summary for Slack reporting.
|
|
46
|
-
*
|
|
47
|
-
* Produces a compact overview with:
|
|
48
|
-
* - Overall score with grade emoji
|
|
49
|
-
* - Per-area score table
|
|
50
|
-
* - Cost summary (if available)
|
|
51
|
-
* - Promptfoo link (if available)
|
|
52
|
-
*/
|
|
53
|
-
export declare function formatScoreSummary(report: Report): SlackMessage;
|
|
54
|
-
/**
|
|
55
|
-
* Format a weekly digest summary for Slack.
|
|
56
|
-
*
|
|
57
|
-
* Produces a summary message covering score trends over a time window:
|
|
58
|
-
* - Header with overall trend direction and score
|
|
59
|
-
* - Per-area trend table with arrows
|
|
60
|
-
* - Lists of improved, regressed, and stable areas
|
|
61
|
-
* - Report count and time window metadata
|
|
62
|
-
*/
|
|
63
|
-
export declare function formatWeeklyDigest(digest: DigestSummary): SlackMessage;
|
|
64
|
-
export {};
|
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sinks/format-slack.ts
|
|
3
|
-
*
|
|
4
|
-
* Formats evaluation report data into Slack Block Kit structures for the
|
|
5
|
-
* SlackSink. Provides two message formats:
|
|
6
|
-
*
|
|
7
|
-
* - `formatRegressionAlert` — detailed regression notification with
|
|
8
|
-
* per-area dimension breakdowns
|
|
9
|
-
* - `formatScoreSummary` — compact score overview for general reporting
|
|
10
|
-
*
|
|
11
|
-
* @see docs/design-docs/report-store/sink-architecture.md
|
|
12
|
-
*/
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// Helpers
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
/**
|
|
17
|
-
* Format a regression alert for areas that have regressed.
|
|
18
|
-
*
|
|
19
|
-
* Produces a rich Slack message with:
|
|
20
|
-
* - Header with overall score change
|
|
21
|
-
* - Context metadata (mode, source, timestamp, promptfoo link)
|
|
22
|
-
* - Per-area regression details with dimension breakdowns
|
|
23
|
-
* - Brief mentions of improved and unchanged areas
|
|
24
|
-
*/
|
|
25
|
-
export function formatRegressionAlert(report) {
|
|
26
|
-
const { comparison, provenance, summary } = report;
|
|
27
|
-
if (!comparison) {
|
|
28
|
-
return {
|
|
29
|
-
blocks: [
|
|
30
|
-
{
|
|
31
|
-
text: { text: "⚠️ No comparison data available", type: "mrkdwn" },
|
|
32
|
-
type: "section",
|
|
33
|
-
},
|
|
34
|
-
],
|
|
35
|
-
text: "No comparison data available",
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
const baselineScore = Math.round(comparison.baseline.overall.avgScore);
|
|
39
|
-
const experimentScore = Math.round(comparison.experiment.overall.avgScore);
|
|
40
|
-
const delta = Math.round(comparison.deltas.overall);
|
|
41
|
-
const blocks = [];
|
|
42
|
-
// Header — emoji + title + overall score change
|
|
43
|
-
blocks.push({
|
|
44
|
-
text: {
|
|
45
|
-
text: `📉 *AI Literacy Score Regression*\n` +
|
|
46
|
-
`Overall: ${baselineScore} → ${experimentScore} (${formatDelta(delta)})`,
|
|
47
|
-
type: "mrkdwn",
|
|
48
|
-
},
|
|
49
|
-
type: "section",
|
|
50
|
-
});
|
|
51
|
-
// Context — mode, source, timestamp, promptfoo link
|
|
52
|
-
const contextElements = [
|
|
53
|
-
{ text: `*Mode:* ${provenance.mode}`, type: "mrkdwn" },
|
|
54
|
-
{ text: `*Source:* ${provenance.source.name}`, type: "mrkdwn" },
|
|
55
|
-
{ text: `*Date:* ${readableDate(summary.timestamp)}`, type: "mrkdwn" },
|
|
56
|
-
];
|
|
57
|
-
if (provenance.promptfooUrl) {
|
|
58
|
-
contextElements.push({
|
|
59
|
-
text: `<${provenance.promptfooUrl}|View in Promptfoo>`,
|
|
60
|
-
type: "mrkdwn",
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
blocks.push({ elements: contextElements, type: "context" });
|
|
64
|
-
// Divider
|
|
65
|
-
blocks.push({ type: "divider" });
|
|
66
|
-
// Regressed areas — detailed fields with dimension breakdowns
|
|
67
|
-
const regressedAreas = comparison.areas.filter((a) => a.change === "regressed");
|
|
68
|
-
if (regressedAreas.length > 0) {
|
|
69
|
-
const fields = regressedAreas.map((area) => ({
|
|
70
|
-
text: `*${area.area}:* ${Math.round(area.baseline)} → ` +
|
|
71
|
-
`${Math.round(area.experiment)} (${formatDelta(Math.round(area.delta))})\n` +
|
|
72
|
-
dimensionBreakdown(area.dimensions),
|
|
73
|
-
type: "mrkdwn",
|
|
74
|
-
}));
|
|
75
|
-
blocks.push({
|
|
76
|
-
fields,
|
|
77
|
-
text: {
|
|
78
|
-
text: `*Regressed Areas (${regressedAreas.length})*`,
|
|
79
|
-
type: "mrkdwn",
|
|
80
|
-
},
|
|
81
|
-
type: "section",
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
// Improved areas — compact mention
|
|
85
|
-
if (comparison.improved.length > 0) {
|
|
86
|
-
blocks.push({
|
|
87
|
-
text: {
|
|
88
|
-
text: `📈 ${comparison.improved.length} area${comparison.improved.length === 1 ? "" : "s"} improved: ${comparison.improved.join(", ")}`,
|
|
89
|
-
type: "mrkdwn",
|
|
90
|
-
},
|
|
91
|
-
type: "section",
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
// Unchanged areas — brief mention
|
|
95
|
-
if (comparison.unchanged.length > 0) {
|
|
96
|
-
blocks.push({
|
|
97
|
-
text: {
|
|
98
|
-
text: `➡️ ${comparison.unchanged.length} area${comparison.unchanged.length === 1 ? "" : "s"} unchanged`,
|
|
99
|
-
type: "mrkdwn",
|
|
100
|
-
},
|
|
101
|
-
type: "section",
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
return {
|
|
105
|
-
blocks,
|
|
106
|
-
text: `📉 AI Literacy Score Regression: ${baselineScore} → ${experimentScore} (${formatDelta(delta)})`,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Format a general score summary for Slack reporting.
|
|
111
|
-
*
|
|
112
|
-
* Produces a compact overview with:
|
|
113
|
-
* - Overall score with grade emoji
|
|
114
|
-
* - Per-area score table
|
|
115
|
-
* - Cost summary (if available)
|
|
116
|
-
* - Promptfoo link (if available)
|
|
117
|
-
*/
|
|
118
|
-
export function formatScoreSummary(report) {
|
|
119
|
-
const { provenance, summary } = report;
|
|
120
|
-
const overall = Math.round(summary.overall.avgScore);
|
|
121
|
-
const blocks = [];
|
|
122
|
-
// Header — overall score with emoji
|
|
123
|
-
blocks.push({
|
|
124
|
-
text: {
|
|
125
|
-
text: `${gradeEmoji(overall)} *AI Literacy Score: ${overall}*`,
|
|
126
|
-
type: "mrkdwn",
|
|
127
|
-
},
|
|
128
|
-
type: "section",
|
|
129
|
-
});
|
|
130
|
-
// Context — mode, source
|
|
131
|
-
const contextElements = [
|
|
132
|
-
{ text: `*Mode:* ${provenance.mode}`, type: "mrkdwn" },
|
|
133
|
-
{ text: `*Source:* ${provenance.source.name}`, type: "mrkdwn" },
|
|
134
|
-
{ text: `*Date:* ${readableDate(summary.timestamp)}`, type: "mrkdwn" },
|
|
135
|
-
];
|
|
136
|
-
blocks.push({ elements: contextElements, type: "context" });
|
|
137
|
-
// Divider
|
|
138
|
-
blocks.push({ type: "divider" });
|
|
139
|
-
// Per-area score table as markdown
|
|
140
|
-
const rows = summary.scores
|
|
141
|
-
.map((s) => {
|
|
142
|
-
const emoji = gradeEmoji(s.totalScore);
|
|
143
|
-
return `${emoji} *${s.feature}*: ${Math.round(s.totalScore)} _(T:${Math.round(s.taskCompletion)} · C:${Math.round(s.codeCorrectness)} · D:${Math.round(s.docCoverage)})_`;
|
|
144
|
-
})
|
|
145
|
-
.join("\n");
|
|
146
|
-
blocks.push({
|
|
147
|
-
text: { text: rows, type: "mrkdwn" },
|
|
148
|
-
type: "section",
|
|
149
|
-
});
|
|
150
|
-
// Cost summary
|
|
151
|
-
if (summary.overall.cost) {
|
|
152
|
-
blocks.push({
|
|
153
|
-
text: {
|
|
154
|
-
text: `💰 Total cost: $${summary.overall.cost.total.toFixed(2)} ($${summary.overall.cost.perTest.toFixed(3)}/test)`,
|
|
155
|
-
type: "mrkdwn",
|
|
156
|
-
},
|
|
157
|
-
type: "section",
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
// Promptfoo link
|
|
161
|
-
if (provenance.promptfooUrl) {
|
|
162
|
-
blocks.push({
|
|
163
|
-
text: {
|
|
164
|
-
text: `🔗 <${provenance.promptfooUrl}|View full results in Promptfoo>`,
|
|
165
|
-
type: "mrkdwn",
|
|
166
|
-
},
|
|
167
|
-
type: "section",
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
return {
|
|
171
|
-
blocks,
|
|
172
|
-
text: `${gradeEmoji(overall)} AI Literacy Score: ${overall}`,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Format a weekly digest summary for Slack.
|
|
177
|
-
*
|
|
178
|
-
* Produces a summary message covering score trends over a time window:
|
|
179
|
-
* - Header with overall trend direction and score
|
|
180
|
-
* - Per-area trend table with arrows
|
|
181
|
-
* - Lists of improved, regressed, and stable areas
|
|
182
|
-
* - Report count and time window metadata
|
|
183
|
-
*/
|
|
184
|
-
export function formatWeeklyDigest(digest) {
|
|
185
|
-
const trendEmoji = digest.overallTrend === "improving"
|
|
186
|
-
? "📈"
|
|
187
|
-
: digest.overallTrend === "regressing"
|
|
188
|
-
? "📉"
|
|
189
|
-
: "➡️";
|
|
190
|
-
const blocks = [];
|
|
191
|
-
// Header — overall trend
|
|
192
|
-
blocks.push({
|
|
193
|
-
text: {
|
|
194
|
-
text: `${trendEmoji} *Weekly AI Literacy Digest*\n` +
|
|
195
|
-
`Overall: ${Math.round(digest.overallLatest)} (${formatDelta(Math.round(digest.overallDelta))} this week)`,
|
|
196
|
-
type: "mrkdwn",
|
|
197
|
-
},
|
|
198
|
-
type: "section",
|
|
199
|
-
});
|
|
200
|
-
// Context — time window and report count
|
|
201
|
-
blocks.push({
|
|
202
|
-
elements: [
|
|
203
|
-
{
|
|
204
|
-
text: `*Period:* ${readableDate(digest.lookbackStart)} – ${readableDate(digest.lookbackEnd)}`,
|
|
205
|
-
type: "mrkdwn",
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
text: `*Reports:* ${digest.reportCount}`,
|
|
209
|
-
type: "mrkdwn",
|
|
210
|
-
},
|
|
211
|
-
],
|
|
212
|
-
type: "context",
|
|
213
|
-
});
|
|
214
|
-
blocks.push({ type: "divider" });
|
|
215
|
-
// Per-area trend table
|
|
216
|
-
if (digest.areaTrends.length > 0) {
|
|
217
|
-
const rows = digest.areaTrends
|
|
218
|
-
.map((t) => {
|
|
219
|
-
const arrow = t.trend === "improving" ? "↑" : t.trend === "regressing" ? "↓" : "→";
|
|
220
|
-
const emoji = gradeEmoji(t.lastScore);
|
|
221
|
-
return `${emoji} *${t.area}*: ${Math.round(t.lastScore)} ${arrow} (${formatDelta(Math.round(t.scoreDelta))})`;
|
|
222
|
-
})
|
|
223
|
-
.join("\n");
|
|
224
|
-
blocks.push({
|
|
225
|
-
text: { text: rows, type: "mrkdwn" },
|
|
226
|
-
type: "section",
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
// Summary badges
|
|
230
|
-
if (digest.improved.length > 0) {
|
|
231
|
-
blocks.push({
|
|
232
|
-
text: {
|
|
233
|
-
text: `📈 *Improved:* ${digest.improved.join(", ")}`,
|
|
234
|
-
type: "mrkdwn",
|
|
235
|
-
},
|
|
236
|
-
type: "section",
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
if (digest.regressed.length > 0) {
|
|
240
|
-
blocks.push({
|
|
241
|
-
text: {
|
|
242
|
-
text: `📉 *Regressed:* ${digest.regressed.join(", ")}`,
|
|
243
|
-
type: "mrkdwn",
|
|
244
|
-
},
|
|
245
|
-
type: "section",
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
if (digest.stable.length > 0) {
|
|
249
|
-
blocks.push({
|
|
250
|
-
text: {
|
|
251
|
-
text: `➡️ *Stable:* ${digest.stable.join(", ")}`,
|
|
252
|
-
type: "mrkdwn",
|
|
253
|
-
},
|
|
254
|
-
type: "section",
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
// Cost summary if available
|
|
258
|
-
if (digest.totalCost !== undefined) {
|
|
259
|
-
blocks.push({
|
|
260
|
-
text: {
|
|
261
|
-
text: `💰 Total evaluation cost this week: ${digest.totalCost.toFixed(2)}`,
|
|
262
|
-
type: "mrkdwn",
|
|
263
|
-
},
|
|
264
|
-
type: "section",
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
return {
|
|
268
|
-
blocks,
|
|
269
|
-
text: `${trendEmoji} Weekly AI Literacy Digest: ${Math.round(digest.overallLatest)} (${formatDelta(Math.round(digest.overallDelta))})`,
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
/** Build a dimension breakdown string for an area delta */
|
|
273
|
-
function dimensionBreakdown(dimensions) {
|
|
274
|
-
return [
|
|
275
|
-
`Task: ${formatDelta(dimensions.taskCompletion.delta)}`,
|
|
276
|
-
`Code: ${formatDelta(dimensions.codeCorrectness.delta)}`,
|
|
277
|
-
`Docs: ${formatDelta(dimensions.docCoverage.delta)}`,
|
|
278
|
-
].join(" · ");
|
|
279
|
-
}
|
|
280
|
-
/** Format a numeric delta with explicit sign: "+4", "-2", or "0" */
|
|
281
|
-
function formatDelta(n) {
|
|
282
|
-
if (n > 0)
|
|
283
|
-
return `+${n}`;
|
|
284
|
-
if (n < 0)
|
|
285
|
-
return `${n}`;
|
|
286
|
-
return "0";
|
|
287
|
-
}
|
|
288
|
-
/** Score-tier emoji: ✅ (≥80), 🟡 (≥70), 🟠 (≥50), 🔴 (<50) */
|
|
289
|
-
function gradeEmoji(score) {
|
|
290
|
-
if (score >= 80)
|
|
291
|
-
return "✅";
|
|
292
|
-
if (score >= 70)
|
|
293
|
-
return "🟡";
|
|
294
|
-
if (score >= 50)
|
|
295
|
-
return "🟠";
|
|
296
|
-
return "🔴";
|
|
297
|
-
}
|
|
298
|
-
/** Format an ISO timestamp into a readable date string */
|
|
299
|
-
function readableDate(iso) {
|
|
300
|
-
const d = new Date(iso);
|
|
301
|
-
return d.toLocaleDateString("en-US", {
|
|
302
|
-
day: "numeric",
|
|
303
|
-
month: "short",
|
|
304
|
-
year: "numeric",
|
|
305
|
-
});
|
|
306
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sinks/slack-sink.ts
|
|
3
|
-
*
|
|
4
|
-
* Slack notification sink — posts formatted score change messages to
|
|
5
|
-
* configured Slack channels via incoming webhooks.
|
|
6
|
-
*
|
|
7
|
-
* By default, only posts when regressions are detected (avoids notification
|
|
8
|
-
* fatigue). Set `alwaysPost: true` to receive all reports.
|
|
9
|
-
*
|
|
10
|
-
* @see docs/design-docs/report-store/sink-architecture.md
|
|
11
|
-
* @see docs/design-docs/report-store/notifications.md
|
|
12
|
-
*/
|
|
13
|
-
import type { Report, SinkHealthStatus, SinkResult } from "../pipeline/types.js";
|
|
14
|
-
import type { ReportSink } from "./types.js";
|
|
15
|
-
export interface SlackSinkOptions {
|
|
16
|
-
/** Post all reports, not just regressions (default: false — only regressions) */
|
|
17
|
-
alwaysPost?: boolean;
|
|
18
|
-
}
|
|
19
|
-
export declare class SlackSink implements ReportSink {
|
|
20
|
-
private readonly webhookUrl;
|
|
21
|
-
private readonly channel?;
|
|
22
|
-
private readonly options;
|
|
23
|
-
readonly name = "slack";
|
|
24
|
-
constructor(webhookUrl: string, channel?: string | undefined, options?: SlackSinkOptions);
|
|
25
|
-
healthCheck(): Promise<SinkHealthStatus>;
|
|
26
|
-
publish(report: Report): Promise<SinkResult>;
|
|
27
|
-
}
|
package/dist/sinks/slack-sink.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sinks/slack-sink.ts
|
|
3
|
-
*
|
|
4
|
-
* Slack notification sink — posts formatted score change messages to
|
|
5
|
-
* configured Slack channels via incoming webhooks.
|
|
6
|
-
*
|
|
7
|
-
* By default, only posts when regressions are detected (avoids notification
|
|
8
|
-
* fatigue). Set `alwaysPost: true` to receive all reports.
|
|
9
|
-
*
|
|
10
|
-
* @see docs/design-docs/report-store/sink-architecture.md
|
|
11
|
-
* @see docs/design-docs/report-store/notifications.md
|
|
12
|
-
*/
|
|
13
|
-
import { formatRegressionAlert, formatScoreSummary, } from "./format-slack.js";
|
|
14
|
-
export class SlackSink {
|
|
15
|
-
webhookUrl;
|
|
16
|
-
channel;
|
|
17
|
-
options;
|
|
18
|
-
name = "slack";
|
|
19
|
-
constructor(webhookUrl, channel, options = {}) {
|
|
20
|
-
this.webhookUrl = webhookUrl;
|
|
21
|
-
this.channel = channel;
|
|
22
|
-
this.options = options;
|
|
23
|
-
}
|
|
24
|
-
healthCheck() {
|
|
25
|
-
try {
|
|
26
|
-
const url = new URL(this.webhookUrl);
|
|
27
|
-
if (url.protocol !== "https:") {
|
|
28
|
-
return Promise.resolve({
|
|
29
|
-
healthy: false,
|
|
30
|
-
reason: `Webhook URL must use HTTPS, got ${url.protocol}`,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
if (!this.webhookUrl.startsWith("https://hooks.slack.com/")) {
|
|
34
|
-
// Non-standard URL — warn but don't fail (could be a proxy)
|
|
35
|
-
return Promise.resolve({ healthy: true });
|
|
36
|
-
}
|
|
37
|
-
return Promise.resolve({ healthy: true });
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
return Promise.resolve({
|
|
41
|
-
healthy: false,
|
|
42
|
-
reason: `Invalid webhook URL: ${this.webhookUrl}`,
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
async publish(report) {
|
|
47
|
-
const hasRegressions = report.comparison !== undefined && report.comparison.regressed.length > 0;
|
|
48
|
-
let message;
|
|
49
|
-
if (hasRegressions) {
|
|
50
|
-
message = formatRegressionAlert(report);
|
|
51
|
-
}
|
|
52
|
-
else if (this.options.alwaysPost) {
|
|
53
|
-
message = formatScoreSummary(report);
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
return { reason: "No regressions detected", status: "skipped" };
|
|
57
|
-
}
|
|
58
|
-
const body = { ...message };
|
|
59
|
-
if (this.channel) {
|
|
60
|
-
body.channel = this.channel;
|
|
61
|
-
}
|
|
62
|
-
const response = await fetch(this.webhookUrl, {
|
|
63
|
-
body: JSON.stringify(body),
|
|
64
|
-
headers: { "Content-Type": "application/json" },
|
|
65
|
-
method: "POST",
|
|
66
|
-
});
|
|
67
|
-
if (!response.ok) {
|
|
68
|
-
return {
|
|
69
|
-
error: `Slack webhook returned HTTP ${response.status}`,
|
|
70
|
-
status: "failed",
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
return {
|
|
74
|
-
detail: hasRegressions ? "regression alert" : "score summary",
|
|
75
|
-
status: "success",
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sinks/webhook-sink.ts
|
|
3
|
-
*
|
|
4
|
-
* Generic HTTP webhook sink — POSTs the full report JSON to any endpoint.
|
|
5
|
-
* This is the universal adapter for integrations that don't have a
|
|
6
|
-
* dedicated sink (Airbyte, Zapier, custom services, etc.).
|
|
7
|
-
*
|
|
8
|
-
* @see docs/design-docs/report-store/sink-architecture.md
|
|
9
|
-
*/
|
|
10
|
-
import type { Report, SinkHealthStatus, SinkResult } from "../pipeline/types.js";
|
|
11
|
-
import type { ReportSink } from "./types.js";
|
|
12
|
-
export declare class WebhookSink implements ReportSink {
|
|
13
|
-
private readonly url;
|
|
14
|
-
private readonly headers;
|
|
15
|
-
readonly name: string;
|
|
16
|
-
constructor(url: string, headers?: Record<string, string>);
|
|
17
|
-
healthCheck(): Promise<SinkHealthStatus>;
|
|
18
|
-
publish(report: Report): Promise<SinkResult>;
|
|
19
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sinks/webhook-sink.ts
|
|
3
|
-
*
|
|
4
|
-
* Generic HTTP webhook sink — POSTs the full report JSON to any endpoint.
|
|
5
|
-
* This is the universal adapter for integrations that don't have a
|
|
6
|
-
* dedicated sink (Airbyte, Zapier, custom services, etc.).
|
|
7
|
-
*
|
|
8
|
-
* @see docs/design-docs/report-store/sink-architecture.md
|
|
9
|
-
*/
|
|
10
|
-
export class WebhookSink {
|
|
11
|
-
url;
|
|
12
|
-
headers;
|
|
13
|
-
name;
|
|
14
|
-
constructor(url, headers = {}) {
|
|
15
|
-
this.url = url;
|
|
16
|
-
this.headers = headers;
|
|
17
|
-
try {
|
|
18
|
-
this.name = `webhook:${new URL(url).hostname}`;
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
this.name = "webhook:invalid-url";
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
healthCheck() {
|
|
25
|
-
try {
|
|
26
|
-
new URL(this.url);
|
|
27
|
-
return Promise.resolve({ healthy: true });
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return Promise.resolve({
|
|
31
|
-
healthy: false,
|
|
32
|
-
reason: `Invalid URL: ${this.url}`,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
async publish(report) {
|
|
37
|
-
const response = await fetch(this.url, {
|
|
38
|
-
body: JSON.stringify(report),
|
|
39
|
-
headers: { "Content-Type": "application/json", ...this.headers },
|
|
40
|
-
method: "POST",
|
|
41
|
-
});
|
|
42
|
-
if (!response.ok) {
|
|
43
|
-
return {
|
|
44
|
-
error: `HTTP ${response.status}: ${response.statusText}`,
|
|
45
|
-
status: "failed",
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
return { detail: `HTTP ${response.status}`, status: "success" };
|
|
49
|
-
}
|
|
50
|
-
}
|