snapeval 2.0.0 → 2.1.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.
Files changed (46) hide show
  1. package/README.md +144 -104
  2. package/bin/snapeval.ts +39 -1
  3. package/dist/bin/snapeval.js +33 -0
  4. package/dist/bin/snapeval.js.map +1 -1
  5. package/dist/src/adapters/copilot-sdk-client.js +3 -1
  6. package/dist/src/adapters/copilot-sdk-client.js.map +1 -1
  7. package/dist/src/adapters/harness/copilot-sdk.d.ts +11 -0
  8. package/dist/src/adapters/harness/copilot-sdk.js +101 -0
  9. package/dist/src/adapters/harness/copilot-sdk.js.map +1 -0
  10. package/dist/src/adapters/harness/resolve.js +10 -2
  11. package/dist/src/adapters/harness/resolve.js.map +1 -1
  12. package/dist/src/adapters/inference/copilot-sdk.js +4 -1
  13. package/dist/src/adapters/inference/copilot-sdk.js.map +1 -1
  14. package/dist/src/adapters/report/terminal.js +89 -9
  15. package/dist/src/adapters/report/terminal.js.map +1 -1
  16. package/dist/src/commands/eval.d.ts +3 -0
  17. package/dist/src/commands/eval.js +146 -17
  18. package/dist/src/commands/eval.js.map +1 -1
  19. package/dist/src/commands/review.d.ts +1 -0
  20. package/dist/src/commands/review.js.map +1 -1
  21. package/dist/src/config.js +2 -1
  22. package/dist/src/config.js.map +1 -1
  23. package/dist/src/engine/grader.js +67 -9
  24. package/dist/src/engine/grader.js.map +1 -1
  25. package/dist/src/engine/runner.d.ts +1 -0
  26. package/dist/src/engine/runner.js +15 -12
  27. package/dist/src/engine/runner.js.map +1 -1
  28. package/dist/src/errors.d.ts +6 -0
  29. package/dist/src/errors.js +21 -3
  30. package/dist/src/errors.js.map +1 -1
  31. package/dist/src/types.d.ts +3 -0
  32. package/package.json +4 -1
  33. package/plugin.json +1 -1
  34. package/skills/snapeval/SKILL.md +132 -39
  35. package/src/adapters/copilot-sdk-client.ts +3 -1
  36. package/src/adapters/harness/copilot-sdk.ts +126 -0
  37. package/src/adapters/harness/resolve.ts +13 -2
  38. package/src/adapters/inference/copilot-sdk.ts +5 -1
  39. package/src/adapters/report/terminal.ts +99 -10
  40. package/src/commands/eval.ts +183 -31
  41. package/src/commands/review.ts +1 -1
  42. package/src/config.ts +2 -1
  43. package/src/engine/grader.ts +59 -8
  44. package/src/engine/runner.ts +16 -13
  45. package/src/errors.ts +24 -3
  46. package/src/types.ts +3 -0
@@ -1,9 +1,33 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import { execFileSync } from 'node:child_process';
4
+ const EXACT_MATCH_PATTERN = /^Output (?:is |equals )exactly:\s*"(.+)"$/i;
5
+ function gradeExactMatch(assertion, output) {
6
+ const match = assertion.match(EXACT_MATCH_PATTERN);
7
+ if (!match)
8
+ return null;
9
+ const expected = match[1];
10
+ const actual = output.trim();
11
+ const passed = actual === expected;
12
+ return {
13
+ text: assertion,
14
+ passed,
15
+ evidence: passed
16
+ ? `Exact match: "${expected}"`
17
+ : `Expected: "${expected}"\nGot: "${actual}"`,
18
+ };
19
+ }
4
20
  function buildGradingPrompt(assertions, output, files) {
5
21
  const fileList = files.length > 0 ? `\nFiles produced: ${files.join(', ')}` : '';
6
- return `You are a strict eval grader. For each assertion, determine PASS or FAIL based on the output below. Require concrete evidence for a PASS — do not give the benefit of the doubt.
22
+ return `You are an eval grader. For each assertion, determine PASS or FAIL based solely on the output below.
23
+
24
+ GRADING RULES:
25
+ - PASS if the output satisfies the assertion's intent, even if wording differs slightly.
26
+ - FAIL only if the output clearly does not satisfy the assertion.
27
+ - Be consistent: if an assertion checks for X and the output contains X in different phrasing, that is a PASS.
28
+ - For "contains" assertions: look for semantic presence, not exact substring.
29
+ - For "identifies" assertions: the output must demonstrate awareness of the concept, not use identical words.
30
+ - Always cite specific text from the output as evidence.
7
31
 
8
32
  OUTPUT:
9
33
  ---
@@ -16,7 +40,7 @@ ${assertions.map((a, i) => `${i + 1}. ${a}`).join('\n')}
16
40
  Respond with JSON only:
17
41
  {
18
42
  "results": [
19
- {"text": "<assertion text>", "passed": true/false, "evidence": "<quote or reference from output>"}
43
+ {"text": "<assertion text>", "passed": true/false, "evidence": "<quote from output supporting your verdict>"}
20
44
  ]
21
45
  }`;
22
46
  }
@@ -26,25 +50,54 @@ function runScript(scriptName, outputDir, scriptsDir) {
26
50
  return { text: `script:${scriptName}`, passed: false, evidence: `Script not found: ${scriptPath}` };
27
51
  }
28
52
  try {
29
- const evidence = execFileSync(scriptPath, [outputDir], { encoding: 'utf-8', timeout: 30000 }).trim();
53
+ const stdout = execFileSync(scriptPath, [outputDir], { encoding: 'utf-8', timeout: 30000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
54
+ const evidence = stdout || `Script passed: ${scriptName}`;
30
55
  return { text: `script:${scriptName}`, passed: true, evidence };
31
56
  }
32
57
  catch (err) {
33
- const evidence = err.stdout?.trim() || err.message || 'Script exited with non-zero code';
58
+ // Extract the most useful error info without raw stack traces
59
+ const stderr = err.stderr?.trim();
60
+ const stdout = err.stdout?.trim();
61
+ let evidence;
62
+ if (err.code === 'EACCES') {
63
+ evidence = `Permission denied: ${scriptPath} is not executable. Run: chmod +x ${scriptPath}`;
64
+ }
65
+ else if (stderr) {
66
+ // Take only the first line of stderr to avoid stack trace noise
67
+ evidence = stderr.split('\n')[0];
68
+ }
69
+ else if (stdout) {
70
+ evidence = stdout.split('\n')[0];
71
+ }
72
+ else {
73
+ evidence = `Script exited with code ${err.status ?? 'unknown'}`;
74
+ }
34
75
  return { text: `script:${scriptName}`, passed: false, evidence };
35
76
  }
36
77
  }
37
78
  function extractJSON(text) {
38
- const match = text.match(/```(?:json)?\s*([\s\S]*?)```/);
39
- if (match)
40
- return match[1].trim();
41
- return text.trim();
79
+ // Try JSON-tagged fence first, then bare fence, then raw text
80
+ const jsonFence = text.match(/```json\s*([\s\S]*?)```/);
81
+ if (jsonFence)
82
+ return jsonFence[1].trim();
83
+ // Try parsing raw text as JSON before falling back to any fence
84
+ const trimmed = text.trim();
85
+ try {
86
+ JSON.parse(trimmed);
87
+ return trimmed;
88
+ }
89
+ catch { /* not raw JSON */ }
90
+ const anyFence = text.match(/```\s*([\s\S]*?)```/);
91
+ if (anyFence)
92
+ return anyFence[1].trim();
93
+ return trimmed;
42
94
  }
43
95
  export async function gradeAssertions(assertions, output, runDir, inference, scriptsDir) {
44
96
  if (assertions.length === 0)
45
97
  return null;
46
98
  const scriptAssertions = assertions.filter(a => a.startsWith('script:'));
47
- const llmAssertions = assertions.filter(a => !a.startsWith('script:'));
99
+ const exactAssertions = assertions.filter(a => !a.startsWith('script:') && EXACT_MATCH_PATTERN.test(a));
100
+ const llmAssertions = assertions.filter(a => !a.startsWith('script:') && !EXACT_MATCH_PATTERN.test(a));
48
101
  const results = [];
49
102
  for (const assertion of scriptAssertions) {
50
103
  const scriptName = assertion.slice('script:'.length);
@@ -52,6 +105,11 @@ export async function gradeAssertions(assertions, output, runDir, inference, scr
52
105
  const dir = scriptsDir ?? path.join(runDir, '..', '..', '..', 'evals', 'scripts');
53
106
  results.push(runScript(scriptName, outputDir, dir));
54
107
  }
108
+ for (const assertion of exactAssertions) {
109
+ const result = gradeExactMatch(assertion, output.raw);
110
+ if (result)
111
+ results.push(result);
112
+ }
55
113
  if (llmAssertions.length > 0) {
56
114
  const prompt = buildGradingPrompt(llmAssertions, output.raw, output.files);
57
115
  const response = await inference.chat([{ role: 'user', content: prompt }], { temperature: 0, responseFormat: 'json' });
@@ -1 +1 @@
1
- {"version":3,"file":"grader.js","sourceRoot":"","sources":["../../../src/engine/grader.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"grader.js","sourceRoot":"","sources":["../../../src/engine/grader.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@ import type { Harness, HarnessRunResult, EvalCase } from '../types.js';
2
2
  interface RunEvalResult {
3
3
  evalId: number;
4
4
  slug: string;
5
+ label?: string;
5
6
  prompt: string;
6
7
  withSkill: {
7
8
  output: HarnessRunResult;
@@ -14,25 +14,28 @@ export async function runEval(evalCase, skillPath, evalDir, harness, oldSkillPat
14
14
  const withSkillDir = path.join(evalDir, 'with_skill');
15
15
  const baselineVariant = oldSkillPath ? 'old_skill' : 'without_skill';
16
16
  const baselineDir = path.join(evalDir, baselineVariant);
17
- const withSkillResult = await harness.run({
18
- skillPath,
19
- prompt: evalCase.prompt,
20
- files: evalCase.files,
21
- outputDir: path.join(withSkillDir, 'outputs'),
22
- });
17
+ const [withSkillResult, baselineResult] = await Promise.all([
18
+ harness.run({
19
+ skillPath,
20
+ prompt: evalCase.prompt,
21
+ files: evalCase.files,
22
+ outputDir: path.join(withSkillDir, 'outputs'),
23
+ }),
24
+ harness.run({
25
+ skillPath: oldSkillPath,
26
+ prompt: evalCase.prompt,
27
+ files: evalCase.files,
28
+ outputDir: path.join(baselineDir, 'outputs'),
29
+ }),
30
+ ]);
23
31
  writeTiming(withSkillDir, withSkillResult);
24
32
  writeOutput(withSkillDir, withSkillResult);
25
- const baselineResult = await harness.run({
26
- skillPath: oldSkillPath,
27
- prompt: evalCase.prompt,
28
- files: evalCase.files,
29
- outputDir: path.join(baselineDir, 'outputs'),
30
- });
31
33
  writeTiming(baselineDir, baselineResult);
32
34
  writeOutput(baselineDir, baselineResult);
33
35
  return {
34
36
  evalId: evalCase.id,
35
37
  slug: evalCase.slug ?? `${evalCase.id}`,
38
+ label: evalCase.label,
36
39
  prompt: evalCase.prompt,
37
40
  withSkill: { output: withSkillResult },
38
41
  withoutSkill: { output: baselineResult },
@@ -1 +1 @@
1
- {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../src/engine/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAWlC,SAAS,WAAW,CAAC,GAAW,EAAE,MAAwB;IACxD,MAAM,MAAM,GAAe,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC;IAClG,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AACnF,CAAC;AAED,SAAS,WAAW,CAAC,GAAW,EAAE,MAAwB;IACxD,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IACtE,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACtB,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IACxE,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,QAAkB,EAClB,SAAiB,EACjB,OAAe,EACf,OAAgB,EAChB,YAAqB;IAErB,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IACtD,MAAM,eAAe,GAAG,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC;IACrE,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;IAExD,MAAM,eAAe,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACxC,SAAS;QACT,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC;KAC9C,CAAC,CAAC;IACH,WAAW,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;IAC3C,WAAW,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;IAE3C,MAAM,cAAc,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACvC,SAAS,EAAE,YAAY;QACvB,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC;KAC7C,CAAC,CAAC;IACH,WAAW,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IACzC,WAAW,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IAEzC,OAAO;QACL,MAAM,EAAE,QAAQ,CAAC,EAAE;QACnB,IAAI,EAAE,QAAQ,CAAC,IAAI,IAAI,GAAG,QAAQ,CAAC,EAAE,EAAE;QACvC,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,SAAS,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE;QACtC,YAAY,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE;KACzC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../src/engine/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAYlC,SAAS,WAAW,CAAC,GAAW,EAAE,MAAwB;IACxD,MAAM,MAAM,GAAe,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC;IAClG,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AACnF,CAAC;AAED,SAAS,WAAW,CAAC,GAAW,EAAE,MAAwB;IACxD,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IACtE,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACtB,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IACxE,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,QAAkB,EAClB,SAAiB,EACjB,OAAe,EACf,OAAgB,EAChB,YAAqB;IAErB,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IACtD,MAAM,eAAe,GAAG,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC;IACrE,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;IAExD,MAAM,CAAC,eAAe,EAAE,cAAc,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC1D,OAAO,CAAC,GAAG,CAAC;YACV,SAAS;YACT,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC;SAC9C,CAAC;QACF,OAAO,CAAC,GAAG,CAAC;YACV,SAAS,EAAE,YAAY;YACvB,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC;SAC7C,CAAC;KACH,CAAC,CAAC;IACH,WAAW,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;IAC3C,WAAW,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;IAC3C,WAAW,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IACzC,WAAW,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IAEzC,OAAO;QACL,MAAM,EAAE,QAAQ,CAAC,EAAE;QACnB,IAAI,EAAE,QAAQ,CAAC,IAAI,IAAI,GAAG,QAAQ,CAAC,EAAE,EAAE;QACvC,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,SAAS,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE;QACtC,YAAY,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE;KACzC,CAAC;AACJ,CAAC"}
@@ -2,6 +2,12 @@ export declare class SnapevalError extends Error {
2
2
  exitCode: number;
3
3
  constructor(message: string, exitCode?: number);
4
4
  }
5
+ export declare class FileNotFoundError extends SnapevalError {
6
+ constructor(filePath: string, hint?: string);
7
+ }
8
+ export declare class ThresholdError extends SnapevalError {
9
+ constructor(actual: number, threshold: number);
10
+ }
5
11
  export declare class AdapterNotAvailableError extends SnapevalError {
6
12
  constructor(adapterName: string, installHint: string);
7
13
  }
@@ -1,3 +1,9 @@
1
+ // Exit codes:
2
+ // 0 = success
3
+ // 1 = threshold not met (eval ran successfully but pass rate below threshold)
4
+ // 2 = config/input error (bad JSON, missing fields, invalid flags)
5
+ // 3 = file not found (missing skill dir, missing evals.json, missing script)
6
+ // 4 = runtime error (harness failure, grading failure, timeout)
1
7
  export class SnapevalError extends Error {
2
8
  exitCode;
3
9
  constructor(message, exitCode = 2) {
@@ -6,9 +12,21 @@ export class SnapevalError extends Error {
6
12
  this.name = 'SnapevalError';
7
13
  }
8
14
  }
15
+ export class FileNotFoundError extends SnapevalError {
16
+ constructor(filePath, hint) {
17
+ super(`File not found: ${filePath}${hint ? `. ${hint}` : ''}`, 3);
18
+ this.name = 'FileNotFoundError';
19
+ }
20
+ }
21
+ export class ThresholdError extends SnapevalError {
22
+ constructor(actual, threshold) {
23
+ super(`Skill pass rate ${(actual * 100).toFixed(1)}% is below threshold ${(threshold * 100).toFixed(1)}%`, 1);
24
+ this.name = 'ThresholdError';
25
+ }
26
+ }
9
27
  export class AdapterNotAvailableError extends SnapevalError {
10
28
  constructor(adapterName, installHint) {
11
- super(`${adapterName} is not available. ${installHint}`);
29
+ super(`${adapterName} is not available. ${installHint}`, 4);
12
30
  this.name = 'AdapterNotAvailableError';
13
31
  }
14
32
  }
@@ -20,13 +38,13 @@ export class RateLimitError extends SnapevalError {
20
38
  }
21
39
  export class TimeoutError extends SnapevalError {
22
40
  constructor(evalId, timeoutMs) {
23
- super(`Eval ${evalId} timed out after ${timeoutMs}ms.`);
41
+ super(`Eval ${evalId} timed out after ${timeoutMs}ms.`, 4);
24
42
  this.name = 'TimeoutError';
25
43
  }
26
44
  }
27
45
  export class GradingError extends SnapevalError {
28
46
  constructor(evalId, detail) {
29
- super(`Grading failed for eval ${evalId}: ${detail}`);
47
+ super(`Grading failed for eval ${evalId}: ${detail}`, 4);
30
48
  this.name = 'GradingError';
31
49
  }
32
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,aAAc,SAAQ,KAAK;IACF;IAApC,YAAY,OAAe,EAAS,WAAmB,CAAC;QACtD,KAAK,CAAC,OAAO,CAAC,CAAC;QADmB,aAAQ,GAAR,QAAQ,CAAY;QAEtD,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AAED,MAAM,OAAO,wBAAyB,SAAQ,aAAa;IACzD,YAAY,WAAmB,EAAE,WAAmB;QAClD,KAAK,CAAC,GAAG,WAAW,sBAAsB,WAAW,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;IACzC,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,aAAa;IAC/C,YAAY,WAAmB;QAC7B,KAAK,CAAC,GAAG,WAAW,mEAAmE,CAAC,CAAC;QACzF,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,OAAO,YAAa,SAAQ,aAAa;IAC7C,YAAY,MAAc,EAAE,SAAiB;QAC3C,KAAK,CAAC,QAAQ,MAAM,oBAAoB,SAAS,KAAK,CAAC,CAAC;QACxD,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,OAAO,YAAa,SAAQ,aAAa;IAC7C,YAAY,MAAc,EAAE,MAAc;QACxC,KAAK,CAAC,2BAA2B,MAAM,KAAK,MAAM,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,8EAA8E;AAC9E,mEAAmE;AACnE,6EAA6E;AAC7E,gEAAgE;AAEhE,MAAM,OAAO,aAAc,SAAQ,KAAK;IACF;IAApC,YAAY,OAAe,EAAS,WAAmB,CAAC;QACtD,KAAK,CAAC,OAAO,CAAC,CAAC;QADmB,aAAQ,GAAR,QAAQ,CAAY;QAEtD,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,aAAa;IAClD,YAAY,QAAgB,EAAE,IAAa;QACzC,KAAK,CAAC,mBAAmB,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QAClE,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,aAAa;IAC/C,YAAY,MAAc,EAAE,SAAiB;QAC3C,KAAK,CAAC,mBAAmB,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC9G,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,OAAO,wBAAyB,SAAQ,aAAa;IACzD,YAAY,WAAmB,EAAE,WAAmB;QAClD,KAAK,CAAC,GAAG,WAAW,sBAAsB,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;QAC5D,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;IACzC,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,aAAa;IAC/C,YAAY,WAAmB;QAC7B,KAAK,CAAC,GAAG,WAAW,mEAAmE,CAAC,CAAC;QACzF,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,OAAO,YAAa,SAAQ,aAAa;IAC7C,YAAY,MAAc,EAAE,SAAiB;QAC3C,KAAK,CAAC,QAAQ,MAAM,oBAAoB,SAAS,KAAK,EAAE,CAAC,CAAC,CAAC;QAC3D,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,OAAO,YAAa,SAAQ,aAAa;IAC7C,YAAY,MAAc,EAAE,MAAc;QACxC,KAAK,CAAC,2BAA2B,MAAM,KAAK,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;QACzD,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF"}
@@ -32,6 +32,7 @@ export interface EvalCase {
32
32
  id: number;
33
33
  prompt: string;
34
34
  expected_output: string;
35
+ label?: string;
35
36
  slug?: string;
36
37
  files?: string[];
37
38
  assertions?: string[];
@@ -85,6 +86,7 @@ export interface FeedbackData {
85
86
  export interface EvalRunResult {
86
87
  evalId: number;
87
88
  slug: string;
89
+ label?: string;
88
90
  prompt: string;
89
91
  withSkill: {
90
92
  output: HarnessRunResult;
@@ -110,4 +112,5 @@ export interface SnapevalConfig {
110
112
  inference: string;
111
113
  workspace: string;
112
114
  runs: number;
115
+ concurrency: number;
113
116
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snapeval",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "description": "Harness-agnostic eval runner for agentskills.io skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -50,5 +50,8 @@
50
50
  "tsx": "^4.19.3",
51
51
  "typescript": "^5.8.2",
52
52
  "vitest": "^4.1.0"
53
+ },
54
+ "optionalDependencies": {
55
+ "@github/copilot-sdk": "^0.2.0"
53
56
  }
54
57
  }
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snapeval",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "description": "Semantic snapshot testing for AI skills. Zero assertions. AI-driven. Free inference.",
5
5
  "author": "Matan Tsach",
6
6
  "license": "MIT",
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: snapeval
3
- description: Evaluate AI skills using the agentskills.io eval spec. Generates test cases, runs with/without skill comparisons, grades assertions, and computes benchmarks. Use when the user wants to evaluate, test, or review any skill — including phrases like "test my skill", "run evals", "evaluate this", "set up evals", or "how good is my skill."
3
+ description: Evaluate AI skills using the agentskills.io eval spec. Runs with/without skill comparisons, grades assertions, and computes benchmarks. Use when the user wants to evaluate, test, or review any skill — including phrases like "test my skill", "run evals", "evaluate this", "set up evals", or "how good is my skill."
4
4
  ---
5
5
 
6
- You are snapeval, a harness-agnostic eval runner for agentskills.io skills. You help developers evaluate AI skills by generating test scenarios, running with/without skill comparisons, grading assertions, and iterating on skill quality.
6
+ You are snapeval, a harness-agnostic eval runner for agentskills.io skills. You help developers evaluate AI skills by understanding what matters to them, designing targeted test scenarios, running with/without skill comparisons, and iterating on skill quality.
7
7
 
8
8
  ## Mode Detection
9
9
 
@@ -12,61 +12,142 @@ Before acting, determine the current state by checking files in the skill direct
12
12
  | State | Condition | Mode |
13
13
  |-------|-----------|------|
14
14
  | **Fresh** | No `evals/evals.json` | First Evaluation |
15
- | **Has evals, no workspace** | `evals/evals.json` exists but no workspace directory | Run First Eval |
16
- | **Has results** | Workspace with `iteration-N/` exists | Review or Re-eval |
15
+ | **Has evals, no workspace** | `evals/evals.json` exists but no workspace directory | Run Eval or Review (skip all interactive phases — go straight to running the command) |
16
+ | **Has results** | Workspace with `iteration-N/` exists | Re-eval or Review (skip all interactive phases) |
17
+
18
+ **Important:** The interactive phases (Discover, Analyze, Interview, Propose) only apply to the **First Evaluation** flow when no evals.json exists. When evals.json already exists, skip straight to running the `eval` or `review` command. If the user says "run", "just do it", or "without asking", always skip interactive phases.
17
19
 
18
20
  ## First Evaluation
19
21
 
20
- Triggered by: "evaluate", "test", "set up evals", "evaluate my skill"
22
+ Triggered by: "evaluate", "test", "set up evals", "evaluate my skill", "how good is my skill"
21
23
 
22
24
  ### Phase 1 — Discover
23
25
 
24
- 1. Ask the user which skill to evaluate (or accept the path they provide)
26
+ 1. Identify the skill to evaluate accept the path the user provides, or infer it from context if they mention a skill name or directory
25
27
  2. Read the target skill's SKILL.md using the Read tool
26
28
  3. Summarize what the skill does in 1-2 sentences
27
29
  4. Confirm understanding: "This skill [summary]. Is that right?"
28
30
 
29
- ### Phase 2 Analyze & Propose
31
+ **STOP. Do not proceed to Phase 2 until the user confirms your understanding is correct. Wait for the user to respond.**
30
32
 
31
- 1. Decompose the skill into behaviors, input dimensions, and failure modes
32
- 2. Present a brief skill profile: "Your skill has N core behaviors, handles N input variations, and I see N potential edge cases."
33
- 3. Generate 5-8 test scenarios covering:
34
- - Happy path scenarios (normal use cases)
35
- - Edge cases (empty input, unusual input)
36
- - At least one negative test
37
- 4. Present scenarios as a numbered list. For each scenario show:
38
- - The prompt (realistic — messy, with typos, abbreviations, personal context)
39
- - What it tests
40
- - Why it matters
41
- 5. Ask: "Want to adjust any of these, or should I run them?"
33
+ ### Phase 2 Deep Skill Analysis
42
34
 
43
- ### Phase 3 Handle Feedback
35
+ Before asking the user anything, do your own homework. Study the skill thoroughly to map its surface area:
44
36
 
45
- - If the user wants changes, adjust conversationally
46
- - "Drop 3, add one about empty input" adjust the list and re-present
47
- - Loop until confirmed
48
- - If the user says "just run it" skip to Phase 4 immediately
37
+ 1. **Re-read the SKILL.md carefully** — not just the summary, but every instruction, rule, format spec, and example
38
+ 2. **Map the behavior space** identify every distinct thing the skill does (e.g., "generates commit messages", "handles empty diffs", "detects breaking changes")
39
+ 3. **Map the input space** — what kinds of inputs does it accept? What dimensions vary? (language, length, complexity, format, edge cases)
40
+ 4. **Identify implicit assumptions** — what does the skill assume about context, user intent, or environment that could break?
41
+ 5. **Spot gaps and ambiguities** — where are the instructions vague, contradictory, or silent? These are often where failures hide
42
+
43
+ Present this analysis to the user as a brief skill map:
44
+ > "I've analyzed your skill in depth. Here's what I see:
45
+ > - **N core behaviors**: [list them]
46
+ > - **N input dimensions**: [list them]
47
+ > - **N potential weak spots**: [list them — gaps, ambiguities, untested assumptions]"
48
+
49
+ ### Phase 3 — Interview
50
+
51
+ Now ask targeted questions to fill gaps your analysis couldn't answer. You've done the work — your questions should be specific and informed, not generic.
52
+
53
+ Ask 2-3 focused questions (one at a time) based on what you found in Phase 2. Examples:
49
54
 
50
- ### Phase 4 Init & First Eval
55
+ - "Your skill says [X] but doesn't specify what happens when [Y]. What should it do?"
56
+ - "I see the skill handles [A] and [B] but doesn't mention [C]. Is that a case you care about?"
57
+ - "The output format section says [X]. In practice, do your users need exactly that, or is there flexibility?"
58
+ - "I noticed the skill doesn't address [edge case]. Has that come up, or is it not a concern?"
51
59
 
52
- 1. Run: `npx snapeval init <skill-path>`generates evals.json (prompts + expected outputs, no assertions)
53
- 2. Run: `npx snapeval eval <skill-path>` — runs each eval with and without the skill
54
- 3. Report: "Ran N evals. With-skill vs without-skill outputs are in the workspace. Review the outputs and add assertions to evals.json for what 'good' looks like."
60
+ Ask ONE question at a time. Wait for the answer before asking the next one. Two to three questions is usually enough don't turn this into an interrogation. If the user seems impatient or says "just test it", respect that and move to Phase 4 (Propose Scenarios) with reasonable defaults.
55
61
 
56
- ### Phase 5 Add Assertions & Re-eval
62
+ **STOP after each question. Wait for the user to respond before asking the next question or moving on.**
57
63
 
58
- After the user reviews outputs and adds assertions to evals.json:
64
+ ### Phase 4 Propose Scenarios
59
65
 
60
- 1. Run: `npx snapeval eval <skill-path>` now grades assertions, produces grading.json + benchmark.json
61
- 2. Interpret the benchmark:
62
- > "With skill: X% pass rate. Without skill: Y% pass rate. Delta: +Z%. The skill adds value on [specific assertions]."
66
+ Using your analysis and the user's answers, generate 5-8 test scenarios tailored to what actually matters.
67
+
68
+ 1. Present a brief skill profile: "Based on what you told me, I'll focus on [key concerns]. Your skill has N core behaviors and I see N areas worth testing."
69
+ 2. Present scenarios as a numbered list. For each scenario show:
70
+ - The prompt (realistic — messy, with typos, abbreviations, personal context)
71
+ - What it tests and why (connected back to the user's answers)
72
+ - Why it matters
73
+ 3. Ask: "Want to adjust any of these, or should I run them?"
74
+
75
+ **STOP. Do not write evals.json or run any commands until the user approves the scenario list (or says "just run it", "looks good", "I trust you", etc). Wait for the user to respond.**
76
+
77
+ ### Phase 5 — Handle Feedback
78
+
79
+ - If the user wants changes, adjust conversationally
80
+ - "Drop 3, add one about empty input" → adjust the list and re-present
81
+ - Loop until confirmed
82
+ - If the user says "just run it", "looks good", "I trust you", or similar → skip to Phase 6 immediately
83
+
84
+ ### Phase 6 — Write evals.json & Run
85
+
86
+ 1. Write the approved scenarios to `<skill-path>/evals/evals.json`. Format:
87
+ ```json
88
+ {
89
+ "skill_name": "<skill-name>",
90
+ "evals": [
91
+ {
92
+ "id": 1,
93
+ "label": "short descriptive name",
94
+ "slug": "kebab-case-slug",
95
+ "prompt": "The realistic user prompt",
96
+ "expected_output": "Human description of expected behavior",
97
+ "assertions": ["Assertion 1", "Assertion 2"],
98
+ "files": []
99
+ }
100
+ ]
101
+ }
102
+ ```
103
+
104
+ **Writing good assertions:** Assertions are graded by an LLM that requires concrete evidence from the output to pass. Write specific, verifiable assertions — not vague ones.
105
+ - Good: `"Output contains a YAML block with an 'id' field for each issue"`
106
+ - Bad: `"Output is correct"`
107
+ - Good: `"Response declines to scout because the pipeline already has unclaimed issues"`
108
+ - Bad: `"Handles edge case properly"`
109
+
110
+ **Prefer semantic assertions for first evaluations.** Script assertions (`script:check.sh`) are powerful but add setup complexity (permissions, paths). Only suggest script assertions when the user specifically needs programmatic validation or has existing scripts.
111
+
112
+ 2. Run: `npx snapeval eval <skill-path>` — runs each eval with and without the skill, grades assertions, produces grading.json + benchmark.json
113
+
114
+ 3. Interpret the benchmark using these guidelines:
115
+
116
+ | Delta | Interpretation |
117
+ |-------|----------------|
118
+ | **+20% or more** | "Your skill adds significant value — it passes X% more assertions than raw AI." |
119
+ | **+1% to +19%** | "Your skill helps, but the improvement is modest. Here's where it adds value: [specific assertions]." |
120
+ | **0%** | "Your skill isn't measurably helping on these tests. The raw AI handles them equally well. Consider making the skill more specific or testing different scenarios." |
121
+ | **Negative** | "Your skill is actually hurting performance on these tests. The raw AI does better without it. Check [specific failing assertions] — the skill may be adding noise or wrong instructions." |
122
+
123
+ ## Adding or Modifying Evals
124
+
125
+ When the user wants to add, edit, or remove specific eval cases (not regenerate from scratch):
126
+
127
+ 1. Read the existing `evals/evals.json`
128
+ 2. Make the requested change (add new eval, modify assertion, remove eval)
129
+ 3. Preserve all unchanged evals — never regenerate the full file. Never renumber existing eval IDs.
130
+ 4. For new evals, append with the next available ID (e.g., if max ID is 7, use 8)
131
+ 5. Run just the new/modified eval to verify it works: `npx snapeval eval <skill-path> --only <new-id>`
132
+
133
+ ## Re-eval After Skill Change
134
+
135
+ When the user has modified their SKILL.md and wants to see if results improved:
136
+
137
+ 1. Detect that `evals/evals.json` already exists — do NOT regenerate scenarios
138
+ 2. Run: `npx snapeval eval <skill-path>` — this creates the next iteration automatically
139
+ 3. Compare the new iteration with the previous one:
140
+ - Read both `benchmark.json` files
141
+ - Show per-eval pass rate changes
142
+ - Highlight which evals improved, which regressed, and which stayed the same
143
+ 4. Give a verdict: "Your changes improved X evals, regressed Y evals, net delta: +Z%"
63
144
 
64
145
  ## Review & Iterate
65
146
 
66
147
  Triggered by: "review", "show results", "how did it do"
67
148
 
68
149
  1. Run: `npx snapeval review <skill-path>` — runs eval + creates feedback.json template
69
- 2. Interpret results using the three signals from the spec:
150
+ 2. Interpret results using the three signals:
70
151
  - **Failed assertions** — specific gaps in the skill
71
152
  - **Human feedback** — broader quality issues (user fills in feedback.json)
72
153
  - **Benchmark delta** — where the skill adds value vs doesn't
@@ -76,7 +157,10 @@ Triggered by: "review", "show results", "how did it do"
76
157
  - **Always-fail assertions** — possibly broken, investigate
77
158
  - **Differentiating assertions** — pass with skill, fail without — this is where the skill shines
78
159
 
79
- 4. Suggest iteration: "Want to feed these signals to an LLM to propose SKILL.md improvements?"
160
+ 4. Suggest concrete improvement strategies:
161
+ - Add few-shot examples to SKILL.md for failing scenarios
162
+ - Strengthen format constraints if output structure is inconsistent
163
+ - Remove redundant or conflicting instructions
80
164
 
81
165
  ## Comparing Skill Versions
82
166
 
@@ -91,14 +175,23 @@ Never show raw stack traces. Translate errors into plain language with a suggest
91
175
 
92
176
  | Error | Response |
93
177
  |-------|----------|
94
- | No SKILL.md found | "I can't find a SKILL.md in `<path>`. Is this the right directory?" |
95
- | No evals.json | "No test cases exist yet. Want me to generate them with `snapeval init`?" |
96
- | Inference unavailable | "I can't connect to the inference service. Check that Copilot CLI is authenticated (`copilot auth status`)." |
178
+ | No evals.json | "No test cases exist yet. Want me to design scenarios and create evals.json?" |
179
+ | Skill path doesn't exist | "I can't find a skill at that path. Check the directory exists and contains a SKILL.md." |
180
+ | Harness unavailable | "The eval harness isn't available. Make sure `@github/copilot-sdk` is installed (`npm install @github/copilot-sdk`), or try `--harness copilot-cli`." |
181
+ | Inference unavailable | "I can't connect to the inference service. Check that Copilot CLI is authenticated (`copilot auth status`) or set GITHUB_TOKEN." |
182
+ | Eval command crashes | "The eval run failed: `<error>`. This might be a config issue — check the error message and try again." |
97
183
  | Skill invocation failure | "The skill failed to respond to eval N: `<error>`. This might be a bug in the skill — want to skip this eval and continue?" |
184
+ | Invalid evals.json | "The evals.json file has a syntax error. Check for missing commas, trailing commas, or mismatched brackets." |
185
+
186
+ If the same command fails twice, do not retry blindly. Explain the issue and ask the user how to proceed.
98
187
 
99
188
  ## Rules
100
189
 
101
190
  - Never ask the user to write evals.json or any config files manually
102
191
  - Always read the target skill's SKILL.md before generating scenarios
103
- - Only reference CLI commands that exist: `init`, `eval`, `review`
104
- - Only reference CLI flags that exist: `--harness`, `--inference`, `--workspace`, `--runs`, `--old-skill`, `--no-open`, `--verbose`
192
+ - Only reference CLI commands that exist: `eval`, `review`
193
+ - Only reference CLI flags that exist: `--harness`, `--inference`, `--workspace`, `--runs`, `--concurrency`, `--only`, `--threshold`, `--old-skill`, `--no-open`, `--verbose`
194
+ - Use `--only <id>` to run specific eval IDs when the user wants to test a single eval (e.g., `--only 5` or `--only 1,3,7`)
195
+ - Use `--concurrency 5` for parallel execution when running multiple evals
196
+ - Use `--runs 3` when the user needs statistical confidence (averages pass rates across runs)
197
+ - Use `--threshold 0.8` for CI gating (exits with code 1 if pass rate below threshold; value must be 0-1)
@@ -33,7 +33,9 @@ export async function getClient(): Promise<any> {
33
33
  );
34
34
  }
35
35
 
36
- clientInstance = new CopilotClient();
36
+ // Suppress ExperimentalWarning (e.g., SQLite) in the spawned CLI subprocess
37
+ const env = { ...process.env, NODE_OPTIONS: [process.env.NODE_OPTIONS, '--no-warnings'].filter(Boolean).join(' ') };
38
+ clientInstance = new CopilotClient({ logLevel: 'none', env });
37
39
  await clientInstance.start();
38
40
  clientStarted = true;
39
41
  return clientInstance;