* Add should-flag-change advisory skill + eval suite
New read-only skill that decides whether a code change should ship behind a
LaunchDarkly feature flag. Invoked ad hoc or in CI on a PR: it reads the diff
and surrounding code, weighs a decision framework (favoring flags for
user-facing and risky changes, weighting false negatives over false positives),
and ends with a structured recommend-flag verdict. It never creates or
modifies flags.
- skills/feature-flags/should-flag-change: SKILL.md (read-only scope boundary,
confusion-matrix decision framework, recommend-flag verdict contract) plus
README and marketplace.json
- evals: recommend-flag tool definition + mock, git_diff provider var injected
as a <git_diff> tag, and a two-tier suite (judgment vs agentic providers,
targeted per fixture) with 5 labeled fixtures
- evals/scripts/build-pr-fixture.js: build a fixture from a real PR via
gh pr diff + refs/pull/N/head, bootstrapping the label from LD SDK call sites
- wiring: package.json scripts, _manifest.js, README skill list, skills.json
* Add already-gated fixtures, PR-fixture tooling, and templating fix
Extends the should-flag-change eval suite and its fixture tooling based on
testing against real pull requests.
- promptfooconfig.yaml: add three synthetic fixtures for the "already gated"
case the suite didn't cover — a change that ships behind a flag should get
recommend: false ("already handled"), and its ungated mirror should get
recommend: true. Uses public SDK idioms; no proprietary source.
- provider: fix a Nunjucks var-render crash on diffs containing `{{ ... }}`
(JSX props, Go templates). Fixtures may wrap such content in a `{% raw %}`
block; stripRawWrapper removes it before the agent sees the diff.
- build-pr-fixture.js: read changed files via the GitHub contents API instead
of fetching refs/pull/N/head (avoids cloning a large external repo); add
--flag-pattern / --flag-token / --flag-file-pattern to teach it a codebase's
flag conventions at call time, and --counterfactual to build positive-recall
fixtures by stripping the flag gate. Kept repo-agnostic — no baked-in
conventions.
- tests: unit-test stripRawWrapper and the builder's pure functions
(addedLines, detectLdUsage, splitDiffByFile, dropFilesFromDiff,
extractFlagTokens, stripFlagGate).
* Combine should-flag-change and flag-and-release-change onto one branch
Bring the flag-and-release-change skill (originally PR #102) onto the
should-flag-change branch (#101) so the advisory "decide" step and the
"apply" step ship together.
Only the content of #102's two flag-and-release commits is included --
not that branch's unrelated observability skills (which sit on an
unmerged #99 commit). README skill list updated and skills.json
regenerated from the catalog script.
* Improve both flag skills with ideas from auto-factory and gonfalon
should-flag-change:
- Ancestor-gate analysis: detect an enclosing flag the change already
sits behind, and judge on its rollout state.
- Prerequisite/dependency signal as a reason to flag.
- Optional risk/blast-radius field on the verdict, orthogonal to
confidence (added to the eval tool schema, additive/non-breaking).
- Refactor-that-changes-a-contract case; net-new-vs-incremental
measurability nuance; unverified-claim confidence discipline.
flag-and-release-change:
- Capture and honor human release intent (release / hold / notBefore /
segment / prerequisite), fail-closed; three-layer precedence
(intent -> override -> policy -> default).
- "The deploy is not the release" framing.
- Fail-closed on non-409 flag-creation errors.
- Paired flag-on/flag-off tests run to green before push.
- Metric-adequacy check for guarded policies; prerequisite coupling.
- Off-path-invokes-no-new-code invariant; edge cases for duplicate
config, registering before the PR exists, and double-toggling.
Sources: launchdarkly-labs/launchdarkly-auto-factory (AI-config prompts,
ADRs) and launchdarkly/gonfalon .agents/skills. Portable-safe only --
repo-specific specifics (env matrix, release tags, context kinds) left
out as the per-repo customization surface.
* Extract flag-release skill; slim flag-and-release-change to an orchestrator
Decompose the "apply" half into composable, harness-callable units,
matching gonfalon's planning/add-flag/rollout shape while staying generic:
- New skill feature-flags/flag-release: records a flag's automated rollout
for a PR (match-release-policies preview, release intent + fail-closed
hold, precedence, prerequisites, metric-adequacy). Operates on an
existing flag; creates no flags and edits no code. auto-release.md moves
here as its core reference.
- flag-and-release-change becomes a thin portable orchestrator that
composes should-flag-change -> launchdarkly-flag-create -> flag-release,
owning only the PR workflow (clone/diff/push) and plan->implement
sequencing. Notes that an automation harness can bypass it and invoke
the three atoms directly.
Repo-specific values (env matrix, release tags, targeting context) remain
out of these generic skills -- that is the AgentControl customization seam.
* Add eval coverage for the new flag decision/release logic
should-flag-change:
- New fixtures: ancestor-gate (agentic; explores to an enclosing flag not
in the diff) and prerequisite-dependency (judgment).
- risk-calibration asserts folded into the auth (expect high) and
docs-only (expect low/absent) fixtures. All pass.
New flag-release suite (adds match-release-policies / list-release-policies
/ create-automated-rollout-config tool defs + mocks; environmentKey mock
replacement):
- Release-on-merge happy path: previews the policy, records both envs. PASS.
- Hold intent: KNOWN-RED tracked signal. sonnet-4-6 records the held env as
`policy` (wrongly believing policy holds); opus omits it correctly. The
assertion is correct (a held env must be omitted); do not weaken it.
New flag-and-release-change suite (both PASS):
- Plan phase is side-effect-free (no mutating MCP calls during planning).
- Fails closed: a create-flag 403 (via the restricted-project mock hook)
stops the run before any release is recorded.
Wiring: manifest + package.json scripts for both new suites; READMEs for
flag-release and flag-and-release-change. Sharpened flag-release's
hold-honoring wording (helps stronger models; sonnet gap tracked above).
90/90 unit tests pass.
* Address review feedback on the should-flag-change PR
1. Catalog leaked third-party skills from evals/node_modules (dotenv,
dotenvx, playwright) into the public skills.json, and CI's
generate_catalog.py --check failed on the mismatch. Add node_modules to
the validator's EXCLUDED_DIRS (generate_catalog reuses it) and regenerate
skills.json — back to 44 skills, --check clean.
2. Enforce the recommend-flag contract. A new suite-wide verdict_contract
assertion requires the tool to be called EXACTLY once and as the final
tool call; a run that calls it early, twice, or keeps working afterward
now fails instead of passing on the first call's boolean.
3. Guard the read-only safety contract. A new read_only_guard assertion bans
Edit/Write and mutating shell commands (redirects, rm/mv/cp, git
commit/push, package installs) across the suite, so the agentic tier can't
modify code or state and still pass. Previously only flag-mutating MCP
tools were checked.
Both new assertions pass on all 10 should-flag-change fixtures.
* Declare js-yaml as a direct dependency of the eval tooling
build-pr-fixture.js required js-yaml via an explicit ../node_modules path,
but js-yaml was only present transitively (via promptfoo). A promptfoo bump
that dropped or relocated it would break the fixture builder and its unit
test. Add js-yaml (^4.1.1, already the resolved version) to evals
devDependencies, update the lockfile, and use a plain require. Also drop the
now-unused node:path import.
* Enrich should-flag-change + add targeting context-availability reference (#108)
* feat(feature-flags): enrich should-flag-change and add context-availability reference
Layer portable, de-LaunchDarkly-internal lessons from our flag-planning
skills onto the public feature-flag skills.
should-flag-change:
- Add an explicit, named user-observability test as a gate before any
`recommend: false` verdict.
- Add a `verdict` field (suggested | already-flagged | not-suited) to the
recommend-flag output, keeping already-flagged (protected by an existing or
ancestor gate) distinct from not-suited (nothing to flag). `recommend` stays
the boolean a CI check keys on.
- Generalize a decision-posture tie-breaker (conservative vs. low-overhead)
for genuinely balanced calls, without internal "dogfood" wording.
Add a new SDK-agnostic targeting context-availability reference: match the
context kind to the surface where the flag is read (server/client/anonymous),
key vs attribute, and rollout bucketing. Wire it into flag-targeting and
flag-create, and reference it from should-flag-change.
Bump versions and regenerate skills.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(feature-flags): cover verdict taxonomy and targeting-context reasoning
- should-flag-change suite: assert the verdict field across the already-flagged,
not-suited, and suggested fixtures, and validate the enum plus recommend/verdict
agreement in the ambiguous fixture.
- flag-create suite: add a fixture where a client-side flag is asked to target a
server-only signal, asserting the agent flags that browser context can't carry
it and suggests an available approach.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* Let the eval gate tolerate documented known-red fixtures
The flag-release suite carries an intentionally-red fixture (Sonnet mishandles
a held production environment) that must stay red until the skill or model
closes the gap. Previously any suite below 75% failed the CI job, so this
tracked signal blocked merge.
Declare such fixtures via a knownRed allowlist in _manifest.js (matched by the
fixture description, which promptfoo stores under testCase.description).
aggregate.js now keeps the honest score/badge red while gating on a gateScore
that excludes known-red fixtures, so only unexpected failures fail CI. A
known-red fixture that starts passing is surfaced so its entry can be removed.
* Revert "Let the eval gate tolerate documented known-red fixtures"
This reverts commit 5e870937e4.
* Restore known-red eval gate tolerance for flag-release hold fixture
Sonnet still records held production as `policy` (auto-releases on merge),
so the intentionally-red hold-intent fixture keeps failing the 75% suite
gate. Re-apply the gateScore allowlist so the tracked signal stays visible
without blocking CI.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(flag-release): make held-environment handling a mechanical step
The hold-intent fixture failed because the model maps "hold production" to
`releaseType: policy` — the tool describes `policy` as "defer to the
environment's release policy on merge", and "defer" reads like "hold". The
skill fought this with prose caveats, which the authors noted hadn't closed
the gap.
Restructure the Implement phase around a forced RELEASE-vs-HOLD bucket sort:
the environments array is built only from the RELEASE bucket, HOLD means
absence from the array, and the "policy defers to the policy, not to you"
misreading is called out at the point of the call. Remove the known-red gate
allowlist so the fixture counts again and the skill fix carries the suite.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(flag-release): de-trap the rollout tool description; name the hold-field hallucination
Root-cause dig on the hold-intent fixture: the model doesn't fail from a bare
reasoning gap — the eval's mock tool description diverged from the real hosted
tool and fed it a trap. The real create-automated-rollout-config says policy
"waits until merge, then performs the release"; the mock said policy "defers to
the release policy", and "defer" reads as "hold", steering the model to keep a
held env as policy. With louder omit-prose the model instead fabricated a
holdUntil field — which the mock silently accepts (.passthrough), so it never
learns the field is invalid.
- Make the mock tool description faithful to the real tool: drop "defer", say
"waits then performs", and state explicitly there is NO hold/notBefore/date
field (omit the env to hold it; unknown fields are rejected).
- Skill: name the exact tell — wanting holdUntil/notBefore/hold (or keeping an
env as policy "so it waits") means the env is HOLD; drop the entry, don't
invent a field. Date/reason go in the report.
- Keep the known-red gate as a backstop since the 2-test suite is a binary gate
that can flake even when the fix lands; the honest score still shows red if it
regresses.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(evals): drop known-red backstop now flag-release hold fixture passes
The de-trapped tool description + skill hallucination-naming flips Sonnet to the
correct behavior: it omits the held environment (recorded [{staging,simple}],
production absent) and scores 0.83 > 0.75. With a real fix in place the known-red
gate tolerance would only mask a future regression, so remove it and let the
fixture gate on its own.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(flag-release): broaden hold coverage and lock in the no-fabricated-field fix
Expand the flag-release suite from 2 to 5 fixtures, all exercising the
release-vs-hold sort the earlier fix addressed:
- add a no_fabricated_fields assertion (rejects holdUntil/notBefore/hold on any
recorded env) to the hold fixtures, locking in the regression fix
- multi-env mixed intent (dev+staging release, production hold) — the sort must
scale past two envs
- single-target date hold (the only env is held, so nothing releases)
- fail closed on ambiguous intent (release staging, hold unconfirmed production)
Retire the stale KNOWN-RED comment on the original hold fixture (now a passing
regression guard). More fixtures also stabilize the 75% gate: a single stochastic
miss on a 5-test suite stays green, where on 2 tests it went red.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(flag-and-release-change): cover the orchestrator's decide gate and create step
The orchestrator suite only had plan-side-effect-free and fail-closed fixtures.
Add two more targeting its own distinct, sandbox-reachable decisions (the record
step needs a git push the MCP-only provider can't do, and is covered directly by
the flag-release suite):
- decide gate: a docs-only change is judged not flag-worthy and creates/records
nothing, even when told to "flag and release if warranted"
- create step: an approved change produces exactly one boolean kill-switch,
created OFF, never toggled on by hand (over-flag + created-OFF guards)
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Ramon Niebla <rniebla@launchdarkly.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Skill Evaluations
Automated evaluations for LaunchDarkly agent skills using promptfoo.
Each skill gets a set of test cases that verify an agent follows the skill's workflow correctly when given realistic user requests. The evals run Claude through the Claude Agent SDK so SKILL.md is loaded the way a real Claude Code session loads it (off disk, via .claude/skills/<slug>/), present it with mocked LaunchDarkly MCP tools, and assert on both the tool-call trajectory and response quality.
Setup
cd evals
npm install
cp .env.example .env # then fill in ANTHROPIC_API_KEY (and optionally AGENT_MODEL / RUBRIC_MODEL)
Unit Tests
The harness utility modules (_mock.js, _jsonschema-to-zod.js, transform.js, output-valid.js, assertions.js) have unit tests that run without any API calls or environment variables:
npm test
Run these after any changes to the provider, mock, or shared utilities to catch regressions before running full evals.
Running Evals
# From evals/
# Run a single suite (all test cases)
npm run eval:configs-create # agentcontrol/configs-create
npm run eval:configs-update # agentcontrol/configs-update
npm run eval:agentcontrol-tools # agentcontrol/tools
npm run eval:configs-variations # agentcontrol/configs-variations
npm run eval:flag-create # feature-flags/launchdarkly-flag-create
# Quick smoke check — first test case only (~15-20s, ~$0.05)
npm run eval:configs-create:single
npm run eval:configs-update:single
npm run eval:agentcontrol-tools:single
npm run eval:configs-variations:single
npm run eval:flag-create:single
# Aggregate and CI operations
npm run eval:all # Run every suite and rebuild ../eval-scores.json
npm run eval:aggregate # Rebuild eval-scores.json from existing results (no API calls)
npm run eval:diff # Show which suites need re-running since last eval:all
npm run eval:badges # Rewrite eval score badges in skill READMEs
npm run eval:view # Open results UI at localhost:15500
# Cross-model evaluation
npm run eval:haiku # Run all suites against claude-haiku-4-5
npm run eval:sonnet # Run all suites against claude-sonnet-4-6
npm run eval:opus # Run all suites against claude-opus-4-6
npm run eval:matrix # Run all suites against haiku + sonnet + opus
# From repo root
npm run eval # Alias for eval:all
All scripts pass --no-cache so dev iterations always reflect the current SKILL.md and provider.
Environment Variables
| Variable | Required | Purpose |
|---|---|---|
ANTHROPIC_API_KEY |
Yes | Authenticates the agent with Anthropic. Also used by the rubric grader if RUBRIC_MODEL is an Anthropic model (the default). |
AGENT_MODEL |
No | Override the system-under-test model (default: claude-sonnet-4-6). |
RUBRIC_MODEL |
Yes | Rubric grader model for llm-rubric assertions (set in .env.example). |
OPENAI_API_KEY |
If using OpenAI for RUBRIC_MODEL |
Authenticates the rubric grader. |
SKILL_EVAL_DEBUG=1 |
No | Dump every SDK message to a temp JSON file for inspection. |
Architecture
evals/
package.json # npm scripts + dependencies
.env.example # Environment variable template
shared/
defaults.yaml # defaultTest block merged into every suite
transform.js # Parses agent output once; assertions skip JSON.parse
output-valid.js # Weight-0 sanity assertion for the parse step
assertions.js # Trajectory helper functions (for scripts, not inline assertions)
providers/
claude-skill-agent-sdk.js # The agent loop: loads skill via SDK, mocks LD tools
_mock.js # Template renderer + stateful write/read overlay
_jsonschema-to-zod.js # JSON Schema -> Zod shape converter
tools/
definitions.json # Anthropic-format tool definitions for all LD MCP tools
mocks/
tool-responses.json # Canned responses for mocked tool calls
scripts/
_manifest.js # Maps suites to skills (single source of truth)
_models.js # Model aliases: haiku, sonnet, opus
aggregate.js # Runs suites and writes ../eval-scores.json
diff-changed-skills.js # Lists suites that need re-running based on git changes
render-badges.js # Rewrites eval score badges in skill READMEs
run-models.js # Cross-model runner (haiku/sonnet/opus matrix)
<skill-name>/
promptfooconfig.yaml # One per skill; test cases + assertions
How a test case runs
- Promptfoo loads the suite config merged with
shared/defaults.yaml. - The provider creates an isolated temp directory containing only a symlink to the target skill at
.claude/skills/<slug>/so the SDK only discovers the one skill being evaluated. - LaunchDarkly MCP tools are exposed through an in-process mock server. Responses come from
mocks/tool-responses.jsonwith{{placeholder}}substitution from the tool's input arguments. - The agent runs. Every tool call is recorded into a
trajectory. When done, the provider returns:{ "response": "The agent's final text", "first_assistant_text": "The agent's first non-empty text turn", "kickoff_text": "All assistant prose up to and including the first user-observable tool call", "assistant_turns": [{ "turn": 1, "text": "..." }], "trajectory": [{ "tool": "setup-ai-config", "arguments": {...}, "turn": 2 }], "tools_called": ["list-ai-configs", "setup-ai-config"], "turn_count": 3, "terminated": null } shared/transform.jsparses the JSON before assertions run. Every assertion receivesoutputas an object — do not callJSON.parse(output)inside assertions.
Shared defaults (shared/defaults.yaml)
Every suite is run with two -c flags:
promptfoo eval -c shared/defaults.yaml -c <skill>/promptfooconfig.yaml
The shared defaults supply:
defaultTest.threshold: 0.75— minimum weighted-average score per test to count as passing.defaultTest.options.transform: file://./transform.js— parses the agent's JSON output once.defaultTest.assert— sanity check (output_valid, weight 0) and latency assertion (180s cap).
Trajectory ordering convention
Use FIRST occurrence for prerequisites and LAST occurrence for verifiers when checking tool ordering:
const aIdx = tools.indexOf('list-ai-configs'); // first: prerequisite
const bIdx = tools.lastIndexOf('setup-ai-config'); // last: verifier
const pass = aIdx >= 0 && bIdx > aIdx;
This handles agents that call get-foo before AND after mutation; using indexOf for both would silently pass against the pre-mutation call.
Cross-model evaluation (run-models.js)
The cross-model runner evaluates all suites against one or more model aliases without touching the canonical eval-scores.json. Results are written to <suite>/results.<alias>.json (e.g., configs-create/results.haiku.json).
npm run eval:haiku # claude-haiku-4-5-20251001
npm run eval:sonnet # claude-sonnet-4-6
npm run eval:opus # claude-opus-4-6
npm run eval:matrix # all three in sequence
After running, a summary matrix is printed to stdout. These files are gitignored — they're for local comparison only.
Diff-gated re-runs (diff-changed-skills.js)
eval:diff compares the current HEAD against the lastCommit recorded in eval-scores.json and reports which suites need re-running:
npm run eval:diff # prints affected suites
npm run eval:diff -- --json # machine-readable JSON array
npm run eval:diff -- --verbose # show changed file paths
npm run eval:diff -- --base=abc1234 # compare against a specific commit
Global triggers — changes to evals/providers, evals/shared, evals/tools, or evals/mocks invalidate every suite, because those files affect all test runs.
Typical CI workflow:
npm run eval:diff -- --json # determine which suites changed
# then run only the affected suite(s)
npm run eval:<suite>
npm run eval:aggregate # rebuild eval-scores.json from results
README badge rendering (render-badges.js)
eval:badges rewrites the eval score block in each skill's README between <!-- eval-score:start --> and <!-- eval-score:end --> markers:
npm run eval:badges
If a skill README doesn't have the markers, render-badges.js appends a new ## Eval Score section. If no README exists, a stub is created.
Badge format example:
**Eval score:** 100/100 (4/4 passing, passing) — last run 2026-05-19
Run this after eval:all (or eval:aggregate) to keep README badges in sync.
shared/assertions.js
This module exports trajectory helper functions for use in scripts and Node.js files. It cannot be require()'d inside inline YAML assertions (promptfoo evaluates those as isolated new Function contexts).
const { called, calledNone, expectAfter } = require('../shared/assertions');
called(output, 'setup-ai-config') // → boolean
calledNone(output, ['delete-ai-config']) // → boolean
expectAfter(output, 'list-ai-configs', 'setup-ai-config') // → boolean
For inline YAML assertions, implement the same logic directly — see the Trajectory ordering convention section above.
Adding Evals for a New Skill
Step 1: Check tool coverage
Read the SKILL.md and note every MCP tool it references. Verify each tool exists in tools/definitions.json and has a mock response in mocks/tool-responses.json. Add them if missing.
Step 2: Create the eval directory and config
mkdir <skill-name>
Use the same name as the skill directory (e.g., configs-create). Create promptfooconfig.yaml:
# yaml-language-server: $schema=https://promptfoo.dev/config-schema.json
description: "End-to-end evaluation of the <skill-name> skill"
prompts:
- file://../../skills/<domain>/<skill-name>/SKILL.md
providers:
- id: file://../providers/claude-skill-agent-sdk.js
label: claude-skill-agent-sdk
config:
skill_slug: <skill-name>
tests:
- description: "<scenario description>"
vars:
user_request: >
<what the user asks>
codebase_context: >
<simulated project info, or "" for exploration scenarios>
assert:
# ... assertions
Step 3: Add npm scripts
Add two entries to evals/package.json scripts. The full-suite script must include -o <skill>/results.json so aggregate.js can read it:
"eval:<skill>": "promptfoo eval -c shared/defaults.yaml -c <skill>/promptfooconfig.yaml --env-file .env --no-cache -o <skill>/results.json",
"eval:<skill>:single": "promptfoo eval -c shared/defaults.yaml -c <skill>/promptfooconfig.yaml --env-file .env --no-cache --filter-first-n 1",
Step 4: Register in the manifest
Add an entry to scripts/_manifest.js:
{
suite: "<skill-name>",
skillKey: "<domain>/<skill-name>", // e.g. "agentcontrol/configs-create"
skillDir: "skills/<domain>/<skill-name>",
readme: "skills/<domain>/<skill-name>/README.md",
},
Step 5: Write test cases (3-5 per skill)
| Scenario | Purpose |
|---|---|
| Happy path | Most common use case |
| Variant input | A different mode/intent that exercises a decision branch |
| Exploration | User provides minimal context; agent must investigate |
| Edge case | Specific inputs (tags, descriptions) are passed correctly |
| Safety | Agent avoids tools the skill warns against |
Important: mock data for list-* tools must NOT contain items whose key/name matches what the test asks to create. If list-flags returns a flag named new-checkout and the test asks to create a checkout flag, the agent will skip creation.
Step 6: Write assertions
Every type: javascript assertion must return { pass: boolean, score: number, reason: string }. Promptfoo rejects objects missing score.
Tool presence:
- type: javascript
value: |
const tools = output.tools_called || [];
const pass = tools.includes('setup-ai-config');
return { pass, score: pass ? 1 : 0, reason: 'Tools: ' + tools.join(' -> ') };
metric: calls_setup_ai_config
weight: 3
Tool ordering (FIRST prerequisite, LAST verifier):
- type: javascript
value: |
const tools = output.tools_called || [];
const aIdx = tools.indexOf('list-ai-configs');
const bIdx = tools.lastIndexOf('setup-ai-config');
const pass = aIdx >= 0 && bIdx > aIdx;
return { pass, score: pass ? 1 : 0, reason: 'list@' + aIdx + ' setup@' + bIdx };
metric: explores_before_creating
weight: 3
Tool arguments:
- type: javascript
value: |
const call = (output.trajectory || []).find(t => t.tool === 'setup-ai-config');
if (!call) return { pass: false, score: 0, reason: 'No setup-ai-config call' };
const a = call.arguments;
const hasKey = typeof a.key === 'string' && /^[a-z][a-z0-9-]*$/.test(a.key);
return { pass: hasKey, score: hasKey ? 1 : 0, reason: 'key=' + (a.key || '?') };
metric: key_is_kebab_case
weight: 2
LLM rubric (semantic quality):
- type: llm-rubric
value: |
Evaluate whether the agent followed the skill workflow correctly.
Score 1.0 if all criteria are met, deduct proportionally for each miss.
1. <criterion from SKILL.md>
2. <criterion>
metric: workflow_quality
weight: 2
Weight guidelines
| Weight | Use for |
|---|---|
| 3 | Core behavior — the tool call that IS the skill |
| 2 | Important supporting behavior — verification, safety checks, workflow quality |
| 1 | Nice-to-have — metadata, formatting, optional steps |
| 0 | Sanity checks that should not affect score (e.g., output_valid) |
Provider Config Options
| Option | Default | Effect |
|---|---|---|
skill_slug |
(required) | Folder name of the skill under skills/ |
allow_builtins |
false |
When true, expose Claude Code's built-in tools (Read/Grep/Glob/Bash/Edit/Write). Use for skills that scan the codebase. |
expose_mcp_tools |
true |
When false, do not expose LaunchDarkly mock MCP tools. Use for routing/advisory skills. |
force_skill_invocation |
false |
When true, set initialPrompt to /<skill_slug> so the skill is invoked via slash command. Use for skills whose description-based activation is unreliable. |
expose_ask_question |
false |
When true, expose an ask-question MCP tool for testing blocking decision points. |
Aggregated Quality Artifact (eval-scores.json)
Running npm run eval:all writes a summary at the repo root:
{
"schemaVersion": 1,
"updatedAt": "2026-05-19T00:00:00Z",
"lastCommit": "fc69376",
"skills": {
"agentcontrol/configs-create": {
"score": 100,
"passed": 4,
"total": 4,
"status": "passing",
"lastRun": "2026-05-19T00:00:00Z",
"perTest": [{ "description": "...", "pass": true, "score": 1.0 }]
}
}
}
lastCommit— the short git SHA at the time of the lasteval:allrun. Used byeval:diffto determine which suites have changed since scores were recorded.skillKey— the canonical key is<domain>/<skill-name>(e.g.,agentcontrol/configs-create).
Run node scripts/aggregate.js (without --run) to rebuild this file from existing <suite>/results.json files without making any API calls.