@rulemetric/cli 0.1.1 → 0.2.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/README.md +66 -0
- package/dist/base-command.d.ts.map +1 -1
- package/dist/base-command.js +15 -0
- package/dist/base-command.js.map +1 -1
- package/dist/commands/auth/login.d.ts.map +1 -1
- package/dist/commands/auth/login.js +7 -0
- package/dist/commands/auth/login.js.map +1 -1
- package/dist/commands/crons/list.d.ts +7 -0
- package/dist/commands/crons/list.d.ts.map +1 -0
- package/dist/commands/crons/list.js +24 -0
- package/dist/commands/crons/list.js.map +1 -0
- package/dist/commands/crons/run.d.ts +13 -0
- package/dist/commands/crons/run.d.ts.map +1 -0
- package/dist/commands/crons/run.js +48 -0
- package/dist/commands/crons/run.js.map +1 -0
- package/dist/commands/evals/listen.d.ts +28 -2
- package/dist/commands/evals/listen.d.ts.map +1 -1
- package/dist/commands/evals/listen.js +159 -25
- package/dist/commands/evals/listen.js.map +1 -1
- package/dist/commands/evals/listen.test.d.ts +2 -0
- package/dist/commands/evals/listen.test.d.ts.map +1 -0
- package/dist/commands/evals/listen.test.js +48 -0
- package/dist/commands/evals/listen.test.js.map +1 -0
- package/dist/commands/gateway/ensure.js +1 -1
- package/dist/commands/gateway/ensure.js.map +1 -1
- package/dist/commands/hooks/install.d.ts.map +1 -1
- package/dist/commands/hooks/install.js +40 -3
- package/dist/commands/hooks/install.js.map +1 -1
- package/dist/commands/hooks/run.js +1 -1
- package/dist/commands/hooks/run.js.map +1 -1
- package/dist/commands/hooks/uninstall.d.ts.map +1 -1
- package/dist/commands/hooks/uninstall.js +9 -1
- package/dist/commands/hooks/uninstall.js.map +1 -1
- package/dist/commands/org/current.d.ts +7 -0
- package/dist/commands/org/current.d.ts.map +1 -0
- package/dist/commands/org/current.js +70 -0
- package/dist/commands/org/current.js.map +1 -0
- package/dist/commands/org/list.d.ts +7 -0
- package/dist/commands/org/list.d.ts.map +1 -0
- package/dist/commands/org/list.js +37 -0
- package/dist/commands/org/list.js.map +1 -0
- package/dist/commands/org/switch.d.ts +13 -0
- package/dist/commands/org/switch.d.ts.map +1 -0
- package/dist/commands/org/switch.js +52 -0
- package/dist/commands/org/switch.js.map +1 -0
- package/dist/commands/proxy/restart.d.ts +11 -0
- package/dist/commands/proxy/restart.d.ts.map +1 -0
- package/dist/commands/proxy/restart.js +36 -0
- package/dist/commands/proxy/restart.js.map +1 -0
- package/dist/commands/proxy/start.d.ts.map +1 -1
- package/dist/commands/proxy/start.js +4 -0
- package/dist/commands/proxy/start.js.map +1 -1
- package/dist/commands/service/install.d.ts +16 -0
- package/dist/commands/service/install.d.ts.map +1 -1
- package/dist/commands/service/install.js +145 -6
- package/dist/commands/service/install.js.map +1 -1
- package/dist/commands/service/uninstall.d.ts.map +1 -1
- package/dist/commands/service/uninstall.js +28 -0
- package/dist/commands/service/uninstall.js.map +1 -1
- package/dist/commands/sessions/start.d.ts.map +1 -1
- package/dist/commands/sessions/start.js +5 -0
- package/dist/commands/sessions/start.js.map +1 -1
- package/dist/commands/signals/report.d.ts +10 -0
- package/dist/commands/signals/report.d.ts.map +1 -0
- package/dist/commands/signals/report.js +35 -0
- package/dist/commands/signals/report.js.map +1 -0
- package/dist/commands/signals/report.test.d.ts +2 -0
- package/dist/commands/signals/report.test.d.ts.map +1 -0
- package/dist/commands/signals/report.test.js +65 -0
- package/dist/commands/signals/report.test.js.map +1 -0
- package/dist/commands/skills/export-approved.d.ts +10 -0
- package/dist/commands/skills/export-approved.d.ts.map +1 -0
- package/dist/commands/skills/export-approved.js +31 -0
- package/dist/commands/skills/export-approved.js.map +1 -0
- package/dist/commands/skills/publish.d.ts +17 -0
- package/dist/commands/skills/publish.d.ts.map +1 -0
- package/dist/commands/skills/publish.js +80 -0
- package/dist/commands/skills/publish.js.map +1 -0
- package/dist/commands/skills/update.d.ts +3 -0
- package/dist/commands/skills/update.d.ts.map +1 -1
- package/dist/commands/skills/update.js +17 -6
- package/dist/commands/skills/update.js.map +1 -1
- package/dist/commands/suggestions/accept.d.ts +16 -0
- package/dist/commands/suggestions/accept.d.ts.map +1 -0
- package/dist/commands/suggestions/accept.js +61 -0
- package/dist/commands/suggestions/accept.js.map +1 -0
- package/dist/commands/suggestions/accept.test.d.ts +2 -0
- package/dist/commands/suggestions/accept.test.d.ts.map +1 -0
- package/dist/commands/suggestions/accept.test.js +52 -0
- package/dist/commands/suggestions/accept.test.js.map +1 -0
- package/dist/commands/suggestions/dismiss.d.ts +10 -0
- package/dist/commands/suggestions/dismiss.d.ts.map +1 -0
- package/dist/commands/suggestions/dismiss.js +26 -0
- package/dist/commands/suggestions/dismiss.js.map +1 -0
- package/dist/commands/suggestions/list.d.ts +10 -0
- package/dist/commands/suggestions/list.d.ts.map +1 -0
- package/dist/commands/suggestions/list.js +40 -0
- package/dist/commands/suggestions/list.js.map +1 -0
- package/dist/commands/suggestions/refresh.d.ts +7 -0
- package/dist/commands/suggestions/refresh.d.ts.map +1 -0
- package/dist/commands/suggestions/refresh.js +11 -0
- package/dist/commands/suggestions/refresh.js.map +1 -0
- package/dist/dashboard/Dashboard.d.ts.map +1 -1
- package/dist/dashboard/Dashboard.js +7 -1
- package/dist/dashboard/Dashboard.js.map +1 -1
- package/dist/lib/active-org-refresh.d.ts +22 -0
- package/dist/lib/active-org-refresh.d.ts.map +1 -0
- package/dist/lib/active-org-refresh.js +108 -0
- package/dist/lib/active-org-refresh.js.map +1 -0
- package/dist/lib/active-org-refresh.test.d.ts +2 -0
- package/dist/lib/active-org-refresh.test.d.ts.map +1 -0
- package/dist/lib/active-org-refresh.test.js +190 -0
- package/dist/lib/active-org-refresh.test.js.map +1 -0
- package/dist/lib/active-org.d.ts +43 -0
- package/dist/lib/active-org.d.ts.map +1 -0
- package/dist/lib/active-org.js +96 -0
- package/dist/lib/active-org.js.map +1 -0
- package/dist/lib/active-org.test.d.ts +2 -0
- package/dist/lib/active-org.test.d.ts.map +1 -0
- package/dist/lib/active-org.test.js +147 -0
- package/dist/lib/active-org.test.js.map +1 -0
- package/dist/lib/cron-registry.d.ts +47 -0
- package/dist/lib/cron-registry.d.ts.map +1 -0
- package/dist/lib/cron-registry.js +102 -0
- package/dist/lib/cron-registry.js.map +1 -0
- package/dist/lib/cron-registry.test.d.ts +2 -0
- package/dist/lib/cron-registry.test.d.ts.map +1 -0
- package/dist/lib/cron-registry.test.js +136 -0
- package/dist/lib/cron-registry.test.js.map +1 -0
- package/dist/lib/cron-runner.d.ts +16 -0
- package/dist/lib/cron-runner.d.ts.map +1 -0
- package/dist/lib/cron-runner.js +68 -0
- package/dist/lib/cron-runner.js.map +1 -0
- package/dist/lib/detect-languages.d.ts +7 -0
- package/dist/lib/detect-languages.d.ts.map +1 -0
- package/dist/lib/detect-languages.js +113 -0
- package/dist/lib/detect-languages.js.map +1 -0
- package/dist/lib/detect-languages.test.d.ts +2 -0
- package/dist/lib/detect-languages.test.d.ts.map +1 -0
- package/dist/lib/detect-languages.test.js +29 -0
- package/dist/lib/detect-languages.test.js.map +1 -0
- package/dist/lib/eval-executor.d.ts.map +1 -1
- package/dist/lib/eval-executor.js +5 -2
- package/dist/lib/eval-executor.js.map +1 -1
- package/dist/lib/gateway-entry.js +4 -1
- package/dist/lib/gateway-entry.js.map +1 -1
- package/dist/lib/gateway-lifecycle.d.ts +1 -1
- package/dist/lib/gateway-lifecycle.d.ts.map +1 -1
- package/dist/lib/gateway-lifecycle.js +13 -2
- package/dist/lib/gateway-lifecycle.js.map +1 -1
- package/dist/lib/gateway.d.ts +4 -5
- package/dist/lib/gateway.d.ts.map +1 -1
- package/dist/lib/gateway.js +68 -2
- package/dist/lib/gateway.js.map +1 -1
- package/dist/lib/graphile-worker-runner.d.ts +124 -0
- package/dist/lib/graphile-worker-runner.d.ts.map +1 -0
- package/dist/lib/graphile-worker-runner.js +530 -0
- package/dist/lib/graphile-worker-runner.js.map +1 -0
- package/dist/lib/graphile-worker-runner.test.d.ts +2 -0
- package/dist/lib/graphile-worker-runner.test.d.ts.map +1 -0
- package/dist/lib/graphile-worker-runner.test.js +153 -0
- package/dist/lib/graphile-worker-runner.test.js.map +1 -0
- package/dist/lib/handlers/cron-close-stale-sessions.d.ts +3 -0
- package/dist/lib/handlers/cron-close-stale-sessions.d.ts.map +1 -0
- package/dist/lib/handlers/cron-close-stale-sessions.js +8 -0
- package/dist/lib/handlers/cron-close-stale-sessions.js.map +1 -0
- package/dist/lib/handlers/cron-data-integrity-audit.d.ts +3 -0
- package/dist/lib/handlers/cron-data-integrity-audit.d.ts.map +1 -0
- package/dist/lib/handlers/cron-data-integrity-audit.js +16 -0
- package/dist/lib/handlers/cron-data-integrity-audit.js.map +1 -0
- package/dist/lib/handlers/cron-refresh-skills.d.ts +3 -0
- package/dist/lib/handlers/cron-refresh-skills.d.ts.map +1 -0
- package/dist/lib/handlers/cron-refresh-skills.js +91 -0
- package/dist/lib/handlers/cron-refresh-skills.js.map +1 -0
- package/dist/lib/handlers/cron-refresh-skills.test.d.ts +2 -0
- package/dist/lib/handlers/cron-refresh-skills.test.d.ts.map +1 -0
- package/dist/lib/handlers/cron-refresh-skills.test.js +114 -0
- package/dist/lib/handlers/cron-refresh-skills.test.js.map +1 -0
- package/dist/lib/handlers/cron-regenerate-insights.d.ts +3 -0
- package/dist/lib/handlers/cron-regenerate-insights.d.ts.map +1 -0
- package/dist/lib/handlers/cron-regenerate-insights.js +6 -0
- package/dist/lib/handlers/cron-regenerate-insights.js.map +1 -0
- package/dist/lib/handlers/cron-suggest-instructions.d.ts +3 -0
- package/dist/lib/handlers/cron-suggest-instructions.d.ts.map +1 -0
- package/dist/lib/handlers/cron-suggest-instructions.js +159 -0
- package/dist/lib/handlers/cron-suggest-instructions.js.map +1 -0
- package/dist/lib/handlers/cron-suggest-instructions.test.d.ts +2 -0
- package/dist/lib/handlers/cron-suggest-instructions.test.d.ts.map +1 -0
- package/dist/lib/handlers/cron-suggest-instructions.test.js +80 -0
- package/dist/lib/handlers/cron-suggest-instructions.test.js.map +1 -0
- package/dist/lib/handlers/process-conversation.d.ts +35 -0
- package/dist/lib/handlers/process-conversation.d.ts.map +1 -0
- package/dist/lib/handlers/process-conversation.js +35 -0
- package/dist/lib/handlers/process-conversation.js.map +1 -0
- package/dist/lib/handlers/process-eval.d.ts +32 -0
- package/dist/lib/handlers/process-eval.d.ts.map +1 -0
- package/dist/lib/handlers/process-eval.js +207 -0
- package/dist/lib/handlers/process-eval.js.map +1 -0
- package/dist/lib/handlers/process-insights.d.ts +39 -0
- package/dist/lib/handlers/process-insights.d.ts.map +1 -0
- package/dist/lib/handlers/process-insights.js +300 -0
- package/dist/lib/handlers/process-insights.js.map +1 -0
- package/dist/lib/handlers/process-launch.d.ts +29 -0
- package/dist/lib/handlers/process-launch.d.ts.map +1 -0
- package/dist/lib/handlers/process-launch.js +41 -0
- package/dist/lib/handlers/process-launch.js.map +1 -0
- package/dist/lib/handlers/process-session-goal.d.ts +29 -0
- package/dist/lib/handlers/process-session-goal.d.ts.map +1 -0
- package/dist/lib/handlers/process-session-goal.js +74 -0
- package/dist/lib/handlers/process-session-goal.js.map +1 -0
- package/dist/lib/handlers/process-session-goal.test.d.ts +2 -0
- package/dist/lib/handlers/process-session-goal.test.d.ts.map +1 -0
- package/dist/lib/handlers/process-session-goal.test.js +134 -0
- package/dist/lib/handlers/process-session-goal.test.js.map +1 -0
- package/dist/lib/hooks-config.d.ts +28 -4
- package/dist/lib/hooks-config.d.ts.map +1 -1
- package/dist/lib/hooks-config.js +138 -10
- package/dist/lib/hooks-config.js.map +1 -1
- package/dist/lib/llm-client.d.ts.map +1 -1
- package/dist/lib/llm-client.js +149 -16
- package/dist/lib/llm-client.js.map +1 -1
- package/dist/lib/mirror-writer-db.d.ts +12 -0
- package/dist/lib/mirror-writer-db.d.ts.map +1 -0
- package/dist/lib/mirror-writer-db.js +51 -0
- package/dist/lib/mirror-writer-db.js.map +1 -0
- package/dist/lib/mirror-writer.d.ts +35 -0
- package/dist/lib/mirror-writer.d.ts.map +1 -0
- package/dist/lib/mirror-writer.js +27 -0
- package/dist/lib/mirror-writer.js.map +1 -0
- package/dist/lib/mirror-writer.test.d.ts +2 -0
- package/dist/lib/mirror-writer.test.d.ts.map +1 -0
- package/dist/lib/mirror-writer.test.js +47 -0
- package/dist/lib/mirror-writer.test.js.map +1 -0
- package/dist/lib/permanent-job-error.d.ts +17 -0
- package/dist/lib/permanent-job-error.d.ts.map +1 -0
- package/dist/lib/permanent-job-error.js +23 -0
- package/dist/lib/permanent-job-error.js.map +1 -0
- package/dist/lib/permanent-job-error.test.d.ts +2 -0
- package/dist/lib/permanent-job-error.test.d.ts.map +1 -0
- package/dist/lib/permanent-job-error.test.js +29 -0
- package/dist/lib/permanent-job-error.test.js.map +1 -0
- package/dist/lib/version-check.d.ts +11 -0
- package/dist/lib/version-check.d.ts.map +1 -0
- package/dist/lib/version-check.js +106 -0
- package/dist/lib/version-check.js.map +1 -0
- package/oclif.manifest.json +611 -81
- package/package.json +21 -5
- package/dist/commands/insights/sync.d.ts +0 -17
- package/dist/commands/insights/sync.d.ts.map +0 -1
- package/dist/commands/insights/sync.js +0 -477
- package/dist/commands/insights/sync.js.map +0 -1
- package/dist/lib/eval-worker.d.ts +0 -12
- package/dist/lib/eval-worker.d.ts.map +0 -1
- package/dist/lib/eval-worker.js +0 -395
- package/dist/lib/eval-worker.js.map +0 -1
- package/dist/lib/insights-worker.d.ts +0 -10
- package/dist/lib/insights-worker.d.ts.map +0 -1
- package/dist/lib/insights-worker.js +0 -329
- package/dist/lib/insights-worker.js.map +0 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { apiGet, apiPost } from '../api-client.js';
|
|
5
|
+
import { runLlmPrompt, detectLlmBackend } from '../llm-client.js';
|
|
6
|
+
import { buildRecommendationPrompt } from '@rulemetric/core';
|
|
7
|
+
import { normalizeProjectPath } from '@rulemetric/core/paths';
|
|
8
|
+
import { detectLanguages } from '../detect-languages.js';
|
|
9
|
+
const MAX_SUGGESTIONS_PER_PROJECT = 5;
|
|
10
|
+
const MAX_PROJECTS_PER_RUN = Number(process.env.RULEMETRIC_MAX_SUGGESTION_PROJECTS ?? 10);
|
|
11
|
+
const STALE_THRESHOLD_HOURS = 48;
|
|
12
|
+
const MIN_SCORE = 0.5;
|
|
13
|
+
const CONTENT_PREVIEW_CHARS = 400;
|
|
14
|
+
async function checkSkillsRegistryStale() {
|
|
15
|
+
const registryPath = path.join(os.homedir(), '.config', 'rulemetric', 'skills-registry.json');
|
|
16
|
+
try {
|
|
17
|
+
const raw = readFileSync(registryPath, 'utf-8');
|
|
18
|
+
const registry = JSON.parse(raw);
|
|
19
|
+
const ageMs = Date.now() - new Date(registry.generatedAt).getTime();
|
|
20
|
+
return ageMs > STALE_THRESHOLD_HOURS * 60 * 60 * 1000;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export const cronSuggestInstructions = async (_payload, helpers) => {
|
|
27
|
+
// Scoring uses the user's locally-installed LLM tooling — Claude Code CLI
|
|
28
|
+
// when available, otherwise the Anthropic SDK with ANTHROPIC_API_KEY.
|
|
29
|
+
// No third-party LLM_PROVIDER / LLM_API_KEY required. If neither backend
|
|
30
|
+
// is reachable, skip cleanly.
|
|
31
|
+
const backend = detectLlmBackend();
|
|
32
|
+
if (backend === 'none') {
|
|
33
|
+
helpers.logger.warn('No LLM backend available (claude CLI missing and ANTHROPIC_API_KEY unset); skipping suggestion compute');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
helpers.logger.info(`Scoring backend: ${backend}`);
|
|
37
|
+
// 1. Fetch recent project paths
|
|
38
|
+
let projectPaths;
|
|
39
|
+
try {
|
|
40
|
+
const res = await apiGet('/api/sessions/project-paths?limit=' + MAX_PROJECTS_PER_RUN);
|
|
41
|
+
projectPaths = res.projectPaths;
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
helpers.logger.warn(`Failed to fetch project paths: ${err instanceof Error ? err.message : String(err)}`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (projectPaths.length === 0) {
|
|
48
|
+
helpers.logger.info('No recent project paths; nothing to score');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// 2. Fetch candidate instructions: public+org+team from /catalog, plus
|
|
52
|
+
// the user's own (personal+created) from /instructions. Dedup by id.
|
|
53
|
+
const candidates = new Map();
|
|
54
|
+
try {
|
|
55
|
+
const catalogRows = await apiGet('/api/catalog?limit=200');
|
|
56
|
+
for (const row of catalogRows) {
|
|
57
|
+
candidates.set(row.id, {
|
|
58
|
+
id: row.id,
|
|
59
|
+
name: row.name,
|
|
60
|
+
description: row.description ?? '',
|
|
61
|
+
tags: row.tags ?? [],
|
|
62
|
+
contentPreview: (row.content ?? '').slice(0, CONTENT_PREVIEW_CHARS),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
helpers.logger.warn(`Failed to fetch catalog: ${err instanceof Error ? err.message : String(err)}`);
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const personalRes = await apiGet('/api/instructions?limit=100');
|
|
71
|
+
for (const row of personalRes.instructions ?? []) {
|
|
72
|
+
if (!candidates.has(row.id)) {
|
|
73
|
+
candidates.set(row.id, {
|
|
74
|
+
id: row.id,
|
|
75
|
+
name: row.name,
|
|
76
|
+
description: row.description ?? '',
|
|
77
|
+
tags: row.tags ?? [],
|
|
78
|
+
contentPreview: (row.content ?? '').slice(0, CONTENT_PREVIEW_CHARS),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
helpers.logger.warn(`Failed to fetch personal instructions: ${err instanceof Error ? err.message : String(err)}`);
|
|
85
|
+
}
|
|
86
|
+
const candidateList = [...candidates.values()];
|
|
87
|
+
if (candidateList.length === 0) {
|
|
88
|
+
helpers.logger.info('No candidate instructions; nothing to score');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
helpers.logger.info(`Scoring ${candidateList.length} candidates across ${projectPaths.length} project(s)`);
|
|
92
|
+
// 3. Score per project
|
|
93
|
+
let scored = 0;
|
|
94
|
+
for (const rawPath of projectPaths) {
|
|
95
|
+
try {
|
|
96
|
+
const projectPath = normalizeProjectPath(rawPath);
|
|
97
|
+
const languages = detectLanguages(projectPath);
|
|
98
|
+
if (languages.length === 0) {
|
|
99
|
+
helpers.logger.info(`Skipping ${projectPath}: insufficient language signals`);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const context = {
|
|
103
|
+
name: path.basename(projectPath),
|
|
104
|
+
description: null,
|
|
105
|
+
languages,
|
|
106
|
+
topics: [],
|
|
107
|
+
dependencies: [],
|
|
108
|
+
};
|
|
109
|
+
const prompt = buildRecommendationPrompt(context, candidateList);
|
|
110
|
+
// runLlmPrompt expects (systemPrompt, data, maxTokens). The recommendation
|
|
111
|
+
// prompt is fully self-contained; pass an empty data block.
|
|
112
|
+
const parsed = await runLlmPrompt(prompt, '', 4096);
|
|
113
|
+
if (!parsed) {
|
|
114
|
+
helpers.logger.warn(`LLM returned null for ${projectPath} (backend ${backend})`);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const raw = (parsed.recommendations ?? []);
|
|
118
|
+
const recommendations = raw
|
|
119
|
+
.filter((r) => typeof r.instructionId === 'string' && typeof r.score === 'number' && typeof r.reason === 'string')
|
|
120
|
+
.map((r) => ({
|
|
121
|
+
instructionId: r.instructionId,
|
|
122
|
+
score: r.score,
|
|
123
|
+
reason: r.reason,
|
|
124
|
+
}));
|
|
125
|
+
const top = recommendations
|
|
126
|
+
.filter((r) => r.score >= MIN_SCORE)
|
|
127
|
+
.sort((a, b) => b.score - a.score)
|
|
128
|
+
.slice(0, MAX_SUGGESTIONS_PER_PROJECT);
|
|
129
|
+
if (top.length === 0) {
|
|
130
|
+
helpers.logger.info(`No suggestions over threshold for ${projectPath}`);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
await apiPost('/api/instruction-suggestions/batch-upsert', {
|
|
134
|
+
projectPath,
|
|
135
|
+
suggestions: top.map((r) => ({
|
|
136
|
+
instructionId: r.instructionId,
|
|
137
|
+
score: r.score,
|
|
138
|
+
reason: r.reason,
|
|
139
|
+
})),
|
|
140
|
+
});
|
|
141
|
+
scored++;
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
helpers.logger.warn(`Failed to score ${rawPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// 4. Mark stale if registry is old
|
|
148
|
+
if (await checkSkillsRegistryStale()) {
|
|
149
|
+
try {
|
|
150
|
+
await apiPost('/api/instruction-suggestions/mark-stale', {});
|
|
151
|
+
helpers.logger.warn('Skills registry is stale (>48h); marked all suggestions stale');
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// Non-fatal
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
helpers.logger.info(`Done: scored ${scored}/${projectPaths.length} projects`);
|
|
158
|
+
};
|
|
159
|
+
//# sourceMappingURL=cron-suggest-instructions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron-suggest-instructions.js","sourceRoot":"","sources":["../../../src/lib/handlers/cron-suggest-instructions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron-suggest-instructions.test.d.ts","sourceRoot":"","sources":["../../../src/lib/handlers/cron-suggest-instructions.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { detectLanguages } from '../detect-languages.js';
|
|
6
|
+
describe('detectLanguages', () => {
|
|
7
|
+
let root;
|
|
8
|
+
beforeAll(() => {
|
|
9
|
+
root = mkdtempSync(path.join(tmpdir(), 'rulemetric-detect-'));
|
|
10
|
+
});
|
|
11
|
+
afterAll(() => {
|
|
12
|
+
rmSync(root, { recursive: true, force: true });
|
|
13
|
+
});
|
|
14
|
+
function fresh(name) {
|
|
15
|
+
const dir = path.join(root, name);
|
|
16
|
+
mkdirSync(dir, { recursive: true });
|
|
17
|
+
return dir;
|
|
18
|
+
}
|
|
19
|
+
it('returns [] for an empty directory', () => {
|
|
20
|
+
const dir = fresh('empty');
|
|
21
|
+
expect(detectLanguages(dir)).toEqual([]);
|
|
22
|
+
});
|
|
23
|
+
it('returns [] for a single signal (insufficient evidence)', () => {
|
|
24
|
+
const dir = fresh('one-signal');
|
|
25
|
+
writeFileSync(path.join(dir, 'package.json'), '{}');
|
|
26
|
+
// Just package.json with no .ts/.js source = 1 signal
|
|
27
|
+
expect(detectLanguages(dir)).toEqual([]);
|
|
28
|
+
});
|
|
29
|
+
it('detects TypeScript from package.json + tsconfig.json', () => {
|
|
30
|
+
const dir = fresh('ts-config');
|
|
31
|
+
writeFileSync(path.join(dir, 'package.json'), '{}');
|
|
32
|
+
writeFileSync(path.join(dir, 'tsconfig.json'), '{}');
|
|
33
|
+
expect(detectLanguages(dir)).toContain('TypeScript');
|
|
34
|
+
});
|
|
35
|
+
it('detects TypeScript from package.json + a .ts source file', () => {
|
|
36
|
+
const dir = fresh('ts-source');
|
|
37
|
+
writeFileSync(path.join(dir, 'package.json'), '{}');
|
|
38
|
+
const src = path.join(dir, 'src');
|
|
39
|
+
mkdirSync(src);
|
|
40
|
+
writeFileSync(path.join(src, 'index.ts'), 'export {};');
|
|
41
|
+
expect(detectLanguages(dir)).toContain('TypeScript');
|
|
42
|
+
});
|
|
43
|
+
it('detects Python from pyproject.toml + requirements.txt', () => {
|
|
44
|
+
const dir = fresh('py');
|
|
45
|
+
writeFileSync(path.join(dir, 'pyproject.toml'), '');
|
|
46
|
+
writeFileSync(path.join(dir, 'requirements.txt'), '');
|
|
47
|
+
expect(detectLanguages(dir)).toContain('Python');
|
|
48
|
+
});
|
|
49
|
+
it('detects multiple languages in a polyglot project', () => {
|
|
50
|
+
const dir = fresh('polyglot');
|
|
51
|
+
writeFileSync(path.join(dir, 'package.json'), '{}');
|
|
52
|
+
writeFileSync(path.join(dir, 'pyproject.toml'), '');
|
|
53
|
+
const langs = detectLanguages(dir);
|
|
54
|
+
expect(langs).toContain('JavaScript');
|
|
55
|
+
expect(langs).toContain('Python');
|
|
56
|
+
});
|
|
57
|
+
it('walks 3 levels deep (monorepo case)', () => {
|
|
58
|
+
const dir = fresh('mono');
|
|
59
|
+
// packages/foo/src/index.ts is 3 levels deep
|
|
60
|
+
const deep = path.join(dir, 'packages', 'foo', 'src');
|
|
61
|
+
mkdirSync(deep, { recursive: true });
|
|
62
|
+
writeFileSync(path.join(dir, 'package.json'), '{}');
|
|
63
|
+
writeFileSync(path.join(deep, 'index.ts'), 'export {};');
|
|
64
|
+
expect(detectLanguages(dir)).toContain('TypeScript');
|
|
65
|
+
});
|
|
66
|
+
it('skips excluded directories like node_modules', () => {
|
|
67
|
+
const dir = fresh('exclude');
|
|
68
|
+
// ONLY signals are inside node_modules — should not count
|
|
69
|
+
const nm = path.join(dir, 'node_modules', 'foo');
|
|
70
|
+
mkdirSync(nm, { recursive: true });
|
|
71
|
+
writeFileSync(path.join(nm, 'package.json'), '{}');
|
|
72
|
+
writeFileSync(path.join(nm, 'index.ts'), 'export {};');
|
|
73
|
+
// Root itself has nothing → 0 signals → []
|
|
74
|
+
expect(detectLanguages(dir)).toEqual([]);
|
|
75
|
+
});
|
|
76
|
+
it('returns [] for a non-existent path without throwing', () => {
|
|
77
|
+
expect(detectLanguages(path.join(root, 'nonexistent'))).toEqual([]);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
//# sourceMappingURL=cron-suggest-instructions.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron-suggest-instructions.test.js","sourceRoot":"","sources":["../../../src/lib/handlers/cron-suggest-instructions.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { JobHelpers } from 'graphile-worker';
|
|
2
|
+
interface ConversationJobConfig {
|
|
3
|
+
type: 'conversation';
|
|
4
|
+
conversationId: string;
|
|
5
|
+
messageId: string;
|
|
6
|
+
messageContent: string;
|
|
7
|
+
}
|
|
8
|
+
interface ConversationPayload {
|
|
9
|
+
mirrorId: string;
|
|
10
|
+
evalTargetId: string;
|
|
11
|
+
config: ConversationJobConfig;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Graphile-shaped handler for the `process_conversation` task.
|
|
15
|
+
*
|
|
16
|
+
* Thin wrapper around `eval-conversation-handler.processConversationJob` —
|
|
17
|
+
* the existing function already does all the work (action decision,
|
|
18
|
+
* run_evals/generate_cases/analyze branches, idempotency guard via
|
|
19
|
+
* already-posted assistant messages). The handler just unpacks the payload
|
|
20
|
+
* and threads the helpers' logger through.
|
|
21
|
+
*
|
|
22
|
+
* Mechanical contract vs. the legacy worker:
|
|
23
|
+
* - no claim — Graphile claimed the job before invoking us
|
|
24
|
+
* - no /complete POST — we return a small marker on success (matches the
|
|
25
|
+
* `{ type: 'conversation' }` blob the legacy code POSTed)
|
|
26
|
+
* - no surrounding try/catch — errors propagate to Graphile, which flips
|
|
27
|
+
* the mirror row to `failed` via the runner.events handler
|
|
28
|
+
*
|
|
29
|
+
* Conversation jobs run with `maxAttempts: 1` (set at enqueue time in
|
|
30
|
+
* `apps/api/src/lib/queue.ts`) — chat is interactive and silent retries
|
|
31
|
+
* would confuse users mid-flow.
|
|
32
|
+
*/
|
|
33
|
+
export declare function processConversation(payload: ConversationPayload, helpers: JobHelpers): Promise<Record<string, unknown>>;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=process-conversation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-conversation.d.ts","sourceRoot":"","sources":["../../../src/lib/handlers/process-conversation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAIlD,UAAU,qBAAqB;IAC7B,IAAI,EAAE,cAAc,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,qBAAqB,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,mBAAmB,EAC5B,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAclC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { apiPatch } from '../api-client.js';
|
|
2
|
+
import { processConversationJob } from '../eval-conversation-handler.js';
|
|
3
|
+
/**
|
|
4
|
+
* Graphile-shaped handler for the `process_conversation` task.
|
|
5
|
+
*
|
|
6
|
+
* Thin wrapper around `eval-conversation-handler.processConversationJob` —
|
|
7
|
+
* the existing function already does all the work (action decision,
|
|
8
|
+
* run_evals/generate_cases/analyze branches, idempotency guard via
|
|
9
|
+
* already-posted assistant messages). The handler just unpacks the payload
|
|
10
|
+
* and threads the helpers' logger through.
|
|
11
|
+
*
|
|
12
|
+
* Mechanical contract vs. the legacy worker:
|
|
13
|
+
* - no claim — Graphile claimed the job before invoking us
|
|
14
|
+
* - no /complete POST — we return a small marker on success (matches the
|
|
15
|
+
* `{ type: 'conversation' }` blob the legacy code POSTed)
|
|
16
|
+
* - no surrounding try/catch — errors propagate to Graphile, which flips
|
|
17
|
+
* the mirror row to `failed` via the runner.events handler
|
|
18
|
+
*
|
|
19
|
+
* Conversation jobs run with `maxAttempts: 1` (set at enqueue time in
|
|
20
|
+
* `apps/api/src/lib/queue.ts`) — chat is interactive and silent retries
|
|
21
|
+
* would confuse users mid-flow.
|
|
22
|
+
*/
|
|
23
|
+
export async function processConversation(payload, helpers) {
|
|
24
|
+
const { mirrorId, evalTargetId, config } = payload;
|
|
25
|
+
const log = (msg) => helpers.logger.info(msg);
|
|
26
|
+
await processConversationJob(config, evalTargetId, log);
|
|
27
|
+
const result = { type: 'conversation', conversationId: config.conversationId };
|
|
28
|
+
// Graphile discards task return values, so persist a small marker onto
|
|
29
|
+
// the mirror row so the dashboard can tell the job finished. Best-effort.
|
|
30
|
+
await apiPatch(`/api/eval-jobs/${mirrorId}/state`, { result }).catch((err) => {
|
|
31
|
+
log(`result persist failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
32
|
+
});
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=process-conversation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-conversation.js","sourceRoot":"","sources":["../../../src/lib/handlers/process-conversation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAezE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAA4B,EAC5B,OAAmB;IAEnB,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACnD,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEtD,MAAM,sBAAsB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,CAAC;IACxD,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC;IAE/E,uEAAuE;IACvE,0EAA0E;IAC1E,MAAM,QAAQ,CAAC,kBAAkB,QAAQ,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;QACpF,GAAG,CAAC,0BAA0B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { JobHelpers } from 'graphile-worker';
|
|
2
|
+
interface EvalPayload {
|
|
3
|
+
mirrorId: string;
|
|
4
|
+
evalTargetId: string;
|
|
5
|
+
config: {
|
|
6
|
+
parallel?: number;
|
|
7
|
+
runsPerEval?: number;
|
|
8
|
+
iteration?: number;
|
|
9
|
+
skipBaseline?: boolean;
|
|
10
|
+
skipGrading?: boolean;
|
|
11
|
+
model?: string;
|
|
12
|
+
graderModel?: string;
|
|
13
|
+
timeout?: number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Graphile-shaped handler for the `process_eval` task.
|
|
18
|
+
*
|
|
19
|
+
* Lifted verbatim from the legacy `eval-worker.ts:processJob` body, with the
|
|
20
|
+
* queue plumbing stripped out:
|
|
21
|
+
* - no claim — Graphile already claimed the job before invoking us
|
|
22
|
+
* - no /complete POST — we return the result object (or throw)
|
|
23
|
+
* - no `processing` flag — that lived in the polling loop's closure
|
|
24
|
+
* - no conversation branch — split into its own handler (Task 14)
|
|
25
|
+
*
|
|
26
|
+
* Mid-job progress patches still hit the existing
|
|
27
|
+
* `PATCH /api/eval-jobs/:id/progress` endpoint, just keyed by the mirror row's
|
|
28
|
+
* id instead of the legacy queue id.
|
|
29
|
+
*/
|
|
30
|
+
export declare function processEval(payload: EvalPayload, helpers: JobHelpers): Promise<Record<string, unknown>>;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=process-eval.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-eval.d.ts","sourceRoot":"","sources":["../../../src/lib/handlers/process-eval.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAwBlD,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE;QACN,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAiMlC"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { apiGet, apiPost, apiPatch } from '../api-client.js';
|
|
2
|
+
import { executeEval } from '../eval-executor.js';
|
|
3
|
+
import { gradeEvalOutput } from '../eval-grader.js';
|
|
4
|
+
import { judgeEvalQuality } from '../eval-meta-judge.js';
|
|
5
|
+
import { createEvalWorktree } from '../eval-worktree.js';
|
|
6
|
+
import { PermanentJobError } from '../permanent-job-error.js';
|
|
7
|
+
/**
|
|
8
|
+
* Graphile-shaped handler for the `process_eval` task.
|
|
9
|
+
*
|
|
10
|
+
* Lifted verbatim from the legacy `eval-worker.ts:processJob` body, with the
|
|
11
|
+
* queue plumbing stripped out:
|
|
12
|
+
* - no claim — Graphile already claimed the job before invoking us
|
|
13
|
+
* - no /complete POST — we return the result object (or throw)
|
|
14
|
+
* - no `processing` flag — that lived in the polling loop's closure
|
|
15
|
+
* - no conversation branch — split into its own handler (Task 14)
|
|
16
|
+
*
|
|
17
|
+
* Mid-job progress patches still hit the existing
|
|
18
|
+
* `PATCH /api/eval-jobs/:id/progress` endpoint, just keyed by the mirror row's
|
|
19
|
+
* id instead of the legacy queue id.
|
|
20
|
+
*/
|
|
21
|
+
export async function processEval(payload, helpers) {
|
|
22
|
+
const { mirrorId, evalTargetId, config } = payload;
|
|
23
|
+
const log = (msg) => helpers.logger.info(msg);
|
|
24
|
+
// Fail-fast on retry. Each eval_run is INSERT'd as it completes, but
|
|
25
|
+
// there's no idempotency key tying a row to a specific job — re-running
|
|
26
|
+
// the whole batch would duplicate every previously-completed row in
|
|
27
|
+
// eval_runs. Until proper resume-from-progress-checkpoint lands, surface
|
|
28
|
+
// the failure to the user instead of silently doubling-up rows. See
|
|
29
|
+
// docs/audits/2026-05-07-worker-audit.md "Handlers not idempotent".
|
|
30
|
+
if (helpers.job.attempts > 1) {
|
|
31
|
+
throw new PermanentJobError(`process_eval is not safe to retry (attempt ${helpers.job.attempts}). Re-trigger the eval run from the UI.`);
|
|
32
|
+
}
|
|
33
|
+
const target = await apiGet(`/api/eval-targets/${evalTargetId}`);
|
|
34
|
+
const evalCases = await apiGet(`/api/eval-targets/${evalTargetId}/evals`);
|
|
35
|
+
if (evalCases.length === 0) {
|
|
36
|
+
// No retries — eval cases don't materialize on their own; the user has
|
|
37
|
+
// to add one via the UI/CLI. Skip Graphile's retry loop.
|
|
38
|
+
throw new PermanentJobError('No evals defined for target');
|
|
39
|
+
}
|
|
40
|
+
const runsPerEval = config.runsPerEval ?? 1;
|
|
41
|
+
const timeout = config.timeout ?? 120_000;
|
|
42
|
+
const iteration = config.iteration ?? 1;
|
|
43
|
+
const configurations = [
|
|
44
|
+
{ name: 'with_target', content: target.content },
|
|
45
|
+
];
|
|
46
|
+
if (!config.skipBaseline) {
|
|
47
|
+
configurations.push({ name: 'without_target' });
|
|
48
|
+
}
|
|
49
|
+
// Build work queue
|
|
50
|
+
const workQueue = [];
|
|
51
|
+
for (const eval_ of evalCases) {
|
|
52
|
+
for (const cfg of configurations) {
|
|
53
|
+
for (let run = 1; run <= runsPerEval; run++) {
|
|
54
|
+
workQueue.push({ eval_: eval_, configuration: cfg.name, targetContent: cfg.content, runNumber: run });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const total = workQueue.length;
|
|
59
|
+
let completed = 0;
|
|
60
|
+
const concurrency = Math.min(config.parallel ?? 1, 4); // cap at 4 parallel
|
|
61
|
+
await apiPatch(`/api/eval-jobs/${mirrorId}/progress`, {
|
|
62
|
+
status: 'running',
|
|
63
|
+
progress: { total, completed: 0, current: null, message: `Starting ${total} eval runs (concurrency: ${concurrency})...` },
|
|
64
|
+
});
|
|
65
|
+
const results = [];
|
|
66
|
+
// Process work items — sequential if concurrency=1, parallel otherwise
|
|
67
|
+
async function processItem(item) {
|
|
68
|
+
const label = `${item.configuration} · ${item.eval_.name} · run#${item.runNumber}`;
|
|
69
|
+
log(`[${completed + 1}/${total}] ${label}`);
|
|
70
|
+
// Create isolated worktree for parallel execution
|
|
71
|
+
const worktree = concurrency > 1 ? createEvalWorktree() : null;
|
|
72
|
+
try {
|
|
73
|
+
const execResult = await executeEval({
|
|
74
|
+
prompt: item.eval_.prompt,
|
|
75
|
+
targetContent: item.targetContent,
|
|
76
|
+
workDir: worktree?.workDir,
|
|
77
|
+
timeout,
|
|
78
|
+
model: config.model,
|
|
79
|
+
});
|
|
80
|
+
let grading = {};
|
|
81
|
+
if (!config.skipGrading && item.eval_.expectations.length > 0 && !execResult.error) {
|
|
82
|
+
const gradingResult = await gradeEvalOutput({
|
|
83
|
+
prompt: item.eval_.prompt,
|
|
84
|
+
output: execResult.output,
|
|
85
|
+
expectations: item.eval_.expectations,
|
|
86
|
+
model: config.graderModel ?? config.model,
|
|
87
|
+
timeout,
|
|
88
|
+
});
|
|
89
|
+
grading = gradingResult;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
evalId: item.eval_.id,
|
|
93
|
+
evalTargetId: target.id,
|
|
94
|
+
evalTargetVersion: target.version,
|
|
95
|
+
configuration: item.configuration,
|
|
96
|
+
runNumber: item.runNumber,
|
|
97
|
+
iteration,
|
|
98
|
+
status: execResult.error ? 'failed' : 'completed',
|
|
99
|
+
outputs: execResult.error ? { error: execResult.error } : { transcript: execResult.output },
|
|
100
|
+
timing: { duration_ms: execResult.durationMs, total_tokens: execResult.totalTokens },
|
|
101
|
+
grading,
|
|
102
|
+
worktree: worktree?.isWorktree ? true : undefined,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
worktree?.cleanup();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (concurrency <= 1) {
|
|
110
|
+
// Sequential execution (existing behavior)
|
|
111
|
+
for (const item of workQueue) {
|
|
112
|
+
const label = `${item.configuration} · ${item.eval_.name} · run#${item.runNumber}`;
|
|
113
|
+
await apiPatch(`/api/eval-jobs/${mirrorId}/progress`, {
|
|
114
|
+
status: 'running',
|
|
115
|
+
progress: { total, completed, current: label, message: `Executing ${label}...` },
|
|
116
|
+
});
|
|
117
|
+
const runResult = await processItem(item);
|
|
118
|
+
await apiPost('/api/eval-runs', runResult);
|
|
119
|
+
results.push(runResult);
|
|
120
|
+
completed++;
|
|
121
|
+
await apiPatch(`/api/eval-jobs/${mirrorId}/progress`, {
|
|
122
|
+
status: 'running',
|
|
123
|
+
progress: { total, completed, current: label, message: `Completed ${label}` },
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
// Parallel execution with worktree isolation
|
|
129
|
+
log(`Running ${total} evals with concurrency=${concurrency} (worktree isolation)`);
|
|
130
|
+
for (let i = 0; i < workQueue.length; i += concurrency) {
|
|
131
|
+
const batch = workQueue.slice(i, i + concurrency);
|
|
132
|
+
const batchLabel = `batch ${Math.floor(i / concurrency) + 1}/${Math.ceil(workQueue.length / concurrency)}`;
|
|
133
|
+
await apiPatch(`/api/eval-jobs/${mirrorId}/progress`, {
|
|
134
|
+
status: 'running',
|
|
135
|
+
progress: { total, completed, current: batchLabel, message: `Executing ${batchLabel} (${batch.length} parallel)...` },
|
|
136
|
+
});
|
|
137
|
+
const batchResults = await Promise.all(batch.map(processItem));
|
|
138
|
+
for (const runResult of batchResults) {
|
|
139
|
+
await apiPost('/api/eval-runs', runResult);
|
|
140
|
+
results.push(runResult);
|
|
141
|
+
completed++;
|
|
142
|
+
}
|
|
143
|
+
await apiPatch(`/api/eval-jobs/${mirrorId}/progress`, {
|
|
144
|
+
status: 'running',
|
|
145
|
+
progress: { total, completed, current: batchLabel, message: `Completed ${batchLabel}` },
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const summary = buildSummary(results);
|
|
150
|
+
// Meta-judge: assess eval quality if we have grading results
|
|
151
|
+
let metaJudgement;
|
|
152
|
+
if (!config.skipGrading && results.some(r => r.grading && Object.keys(r.grading).length > 0)) {
|
|
153
|
+
try {
|
|
154
|
+
log(`Running meta-judge for job ${mirrorId}...`);
|
|
155
|
+
const evalInputs = evalCases.map(e => {
|
|
156
|
+
const matchingResults = results.filter(r => r.evalId === e.id && r.configuration === 'with_target');
|
|
157
|
+
const firstResult = matchingResults[0];
|
|
158
|
+
return {
|
|
159
|
+
evalName: e.name,
|
|
160
|
+
prompt: e.prompt,
|
|
161
|
+
expectations: e.expectations,
|
|
162
|
+
grading: firstResult?.grading ? firstResult.grading : null,
|
|
163
|
+
output: firstResult?.outputs?.transcript?.slice(0, 500),
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
metaJudgement = await judgeEvalQuality({
|
|
167
|
+
targetName: target.name,
|
|
168
|
+
evalResults: evalInputs,
|
|
169
|
+
model: config.graderModel ?? config.model,
|
|
170
|
+
timeout: 60000,
|
|
171
|
+
});
|
|
172
|
+
log(`Meta-judge: quality=${metaJudgement.quality_score}/5, issues=${metaJudgement.issues.length}`);
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
log(`Meta-judge failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const result = metaJudgement
|
|
179
|
+
? { ...summary, meta_judgement: metaJudgement }
|
|
180
|
+
: summary;
|
|
181
|
+
log(`Job ${mirrorId} completed: ${completed}/${total} runs`);
|
|
182
|
+
// Graphile discards task return values, so persist the summary onto the
|
|
183
|
+
// mirror row here. Best-effort: a failure to write the result must not
|
|
184
|
+
// mark the job as failed (it succeeded; we just lose the summary blob).
|
|
185
|
+
await apiPatch(`/api/eval-jobs/${mirrorId}/state`, { result }).catch((err) => {
|
|
186
|
+
log(`result persist failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
187
|
+
});
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
function buildSummary(results) {
|
|
191
|
+
const completed = results.filter((r) => r.status === 'completed');
|
|
192
|
+
const failed = results.filter((r) => r.status === 'failed');
|
|
193
|
+
const gradedRuns = completed.filter((r) => {
|
|
194
|
+
const grading = r.grading;
|
|
195
|
+
return grading && Object.keys(grading).length > 0;
|
|
196
|
+
});
|
|
197
|
+
const avgPassRate = gradedRuns.length > 0
|
|
198
|
+
? gradedRuns.reduce((sum, r) => sum + (r.grading?.summary?.pass_rate ?? 0), 0) / gradedRuns.length
|
|
199
|
+
: null;
|
|
200
|
+
return {
|
|
201
|
+
total: results.length,
|
|
202
|
+
completed: completed.length,
|
|
203
|
+
failed: failed.length,
|
|
204
|
+
avgPassRate,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=process-eval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-eval.js","sourceRoot":"","sources":["../../../src/lib/handlers/process-eval.ts"],"names":[],"mappings":"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"}
|