Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: CI
on:
push:
branches:
- "main"
pull_request:
branches:
- "main"

permissions: {}

jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Setup CUE
uses: cue-lang/setup-cue@a93fa358375740cd8b0078f76355512b9208acb1 # v1.0.1
with:
version: "v0.17.0"
- name: Install Task
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
with:
version: 3.x
- name: Run vet
run: task vet-examples
- name: Run test
run: task test
5 changes: 0 additions & 5 deletions .github/workflows/publish-cue.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ jobs:
uses: cue-lang/setup-cue@a93fa358375740cd8b0078f76355512b9208acb1 # v1.0.1
with:
version: "v0.17.0"
- name: Validate examples
run: |
for f in examples/*.yaml; do
cue vet prd.cue "$f" -d '#PRDDocument'
done
- name: Login to CUE Central Registry
uses: cue-labs/registry-login-action@66d40052b0206031343e17173425fa10508968d0 # v1.0.3
- name: Publish module
Expand Down
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,18 @@ node_modules/
.claude/
.superpowers/

# lola-eval harness: project-local venv + generated run artifacts
.venv/
.lola-eval/out/
.lola-eval/runs.db
.lola-eval/results/
.lola-eval/*.xml
# Provisioned into starters by provision.sh; regenerated, never committed
.lola-eval/tests/*/starter-clean/
.lola-eval/tests/*/starter/.gitconfig
.lola-eval/tests/*/starter/.opencode/

# Working artifacts (never committed)
docs/superpowers/
# Org-specific tracker scope for discovery (never committed; example is tracked)
/discovery-tracker.yaml
11 changes: 11 additions & 0 deletions .lola-eval/baseline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"_schema_version": 2,
"claude-code/claude-sonnet-4-6/case-001-unknown-executor/project/none": {
"composite": 0.925,
"rubric_pass_threshold": 0.6
},
"claude-code/claude-sonnet-4-6/case-002-discovery-seeds-prd/project/none": {
"composite": 0.99,
"rubric_pass_threshold": 0.6
}
}
39 changes: 39 additions & 0 deletions .lola-eval/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# .lola-eval/config.yaml — Mode 1 (in-repo) spike scaffold.
#
# The project provisions its own pack (see provision.sh) and the harness
# measures the agent's behaviour against that environment. Trimmed to a
# single test cell for the initial spike; widen the matrix once the
# harness has proven itself here.

# Cases live in `.lola-eval/tests/`, not the harness default `test_sets/`.
tests_dir: tests

# Pinned to a concrete model id so composite scores stay reproducible.
targets:
- cli: claude-code
models:
- claude-sonnet-4-6

# Bare-model "none" pass doubles run cost; off until we want a lift study.
calculate_baseline: false

threshold:
mode: absolute
tolerance: 0.15
timeout_is_failure: true

concurrency: 4

timeouts:
runner_seconds: 14400 # 4h; don't let the harness kill slow cells
agent_seconds: 1800 # 30m per agent (matches task.yaml timeout_seconds)

judges:
- {cli: claude-code, model: claude-sonnet-4-6} # single judge keeps drift comparable

aggregation: mean

ci:
junit_xml: true
github_summary: true
html_report: true
129 changes: 129 additions & 0 deletions .lola-eval/provision.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#!/usr/bin/env bash
# provision.sh — copy the current module/ into each starter repo so the
# agent under test runs /prd-review with THIS commit's version of the
# intake-kit module.
#
# Usage: provision.sh [module_dir]
# module_dir defaults to ../module (relative to this script).
#
# For each starter/ under .lola-eval/tests/:
# 1. Remove any prior .lola/ and CLI integration dirs inside the starter
# 2. Copy module/ into starter/.lola/modules/intake-kit/module/
# 3. Write starter/.lola/modules/intake-kit/.lola/source.yml
# 4. Copy top-level AGENTS.md, README.md, LICENSE into the module root
# 5. Copy CLI integration files directly (.claude/, .opencode/) instead
# of calling `lola install` (too slow for batch use)
# 6. Write a clean .gitconfig for eval isolation
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MODULE_DIR="${1:-$(cd "$SCRIPT_DIR/../module" && pwd)}"
TESTS_DIR="$SCRIPT_DIR/tests"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

if [[ ! -d "$MODULE_DIR" ]]; then
echo "provision.sh: module dir not found: $MODULE_DIR" >&2
exit 1
fi

if [[ ! -d "$TESTS_DIR" ]]; then
echo "provision.sh: tests dir not found: $TESTS_DIR" >&2
exit 1
fi

provisioned=0

for starter in "$TESTS_DIR"/*/starter; do
[[ -d "$starter" ]] || continue
case_name="$(basename "$(dirname "$starter")")"

# Clean prior provisioning (module files + CLI integration dirs)
rm -rf "$starter/.lola" "$starter/.claude" "$starter/.opencode"

# Create lola module structure
mod_dest="$starter/.lola/modules/intake-kit"
mkdir -p "$mod_dest/module"
mkdir -p "$mod_dest/.lola"

# Copy the module contents (exclude .git to avoid embedded repo warnings)
cp -a "$MODULE_DIR/." "$mod_dest/module/"
find "$mod_dest" -name .git -type d -exec rm -rf {} + 2>/dev/null || true

# Copy top-level files that lola expects at the module root
for f in AGENTS.md README.md LICENSE CHANGELOG.md; do
if [[ -f "$PROJECT_ROOT/$f" ]]; then
cp "$PROJECT_ROOT/$f" "$mod_dest/$f"
fi
done

# Write lola source metadata
cat > "$mod_dest/.lola/source.yml" <<'YAML'
source: local://provision
type: local
YAML

# Install CLI integration files directly by copying from module/. This
# replaces `lola install` (15-30s per invocation). The output is
# deterministic: agents/, commands/, skills/, references/ are straight
# copies of the module source files.
for target_dir in .claude .opencode; do
mkdir -p "$starter/$target_dir/agents"

if [[ -d "$MODULE_DIR/agents" ]]; then
cp "$MODULE_DIR/agents/"*.md "$starter/$target_dir/agents/" 2>/dev/null || true
fi

if [[ -d "$MODULE_DIR/commands" ]]; then
mkdir -p "$starter/$target_dir/commands"
cp "$MODULE_DIR/commands/"*.md "$starter/$target_dir/commands/" 2>/dev/null || true
fi

# Skills copy is recursive — carries the nested references/ under each skill.
if [[ -d "$MODULE_DIR/skills" ]]; then
cp -a "$MODULE_DIR/skills/." "$starter/$target_dir/skills/"
fi

if [[ -d "$MODULE_DIR/references" ]]; then
cp -a "$MODULE_DIR/references/." "$starter/$target_dir/references/"
fi
done

# Write clean git config for eval isolation
cat > "$starter/.gitconfig" <<'GIT'
[user]
name = lola-eval
email = eval@localhost
[commit]
gpgsign = false
[init]
defaultBranch = main
GIT

# Create starter-clean/ — same source, no module artifacts. Used by
# pack_id=none baseline runs for genuine bare-model comparison.
clean="$TESTS_DIR/$case_name/starter-clean"
rm -rf "$clean"
cp -a "$starter" "$clean"
rm -rf "$clean/.lola" "$clean/.claude" "$clean/.opencode"
for f in AGENTS.md CLAUDE.md; do
if [[ -f "$clean/$f" ]]; then
sed -i '/<!-- lola:module:.*:start -->/,/<!-- lola:module:.*:end -->/d' "$clean/$f"
sed -i '/<!-- lola:skills:start -->/,/<!-- lola:skills:end -->/d' "$clean/$f"
sed -i '/<!-- lola:instructions:start -->/d; /<!-- lola:instructions:end -->/d' "$clean/$f"
sed -i '/^## Lola Skills$/,/^<!-- lola:skills:start -->/d' "$clean/$f" 2>/dev/null || true
if [[ ! -s "$clean/$f" ]] || ! grep -q '[^[:space:]]' "$clean/$f" 2>/dev/null; then
rm -f "$clean/$f"
fi
fi
done

provisioned=$((provisioned + 1))
echo "provision.sh: provisioned $case_name (+ starter-clean)"
done

if [[ $provisioned -eq 0 ]]; then
echo "provision.sh: no starter dirs found under $TESTS_DIR" >&2
exit 1
fi

echo "provision.sh: $provisioned case(s) provisioned"
11 changes: 11 additions & 0 deletions .lola-eval/tests/case-001-unknown-executor/prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Review this PRD family using the intake-kit prd-review skill.

The initiative has a parent PRD (`parent.yaml`) and one phase PRD
(`phase1.yaml`). The `prd-review` skill and its agents are pre-installed
in this project's `.lola/` directory. Run the review with the local
schema as fallback:

/prd-review --schema prd.cue parent.yaml phase1.yaml

Report all findings from the review, including their severity and the
specific location (file and id) each finding refers to.
83 changes: 83 additions & 0 deletions .lola-eval/tests/case-001-unknown-executor/rubric.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
rubric_version: "1"
pass_threshold: 0.6
weights:
detection: 0.5
evidence: 0.25
false_positives: 0.15
no_flapping: 0.10
---

# Rubric: case-001-unknown-executor

The agent was asked to run `/prd-review` on a two-file PRD family
(`parent.yaml` + `phase1.yaml`) that is schema-valid but contains one
seeded cross-file defect:

- The phase journey "Create a short link" names
`executor: ghost-admin`, but the parent's `job-executors` list
declares only `end-user`. This is the **UNKNOWN_EXECUTOR** condition —
a journey referencing an executor id that no parent job-executor
defines. It is a BLOCKER and is only catchable by the Guard agent's
cross-file check, since each file passes `cue vet` in isolation.

Score each component in [0.0, 1.0].

## detection (weight 0.5)

Did the review identify the unknown executor `ghost-admin` (the journey
referencing an executor absent from the parent's job-executors list)?

- 1.0 — the review explicitly flags `ghost-admin` as an executor not
declared in the parent (UNKNOWN_EXECUTOR or an equivalent description),
at BLOCKER severity.
- 0.5 — the review notes an executor/journey problem but is vague about
which executor is undefined or does not treat it as blocking.
- 0.0 — the defect is not reported.

## evidence (weight 0.25)

Does the finding cite the specific location — the phase file's journey and
the executor id `ghost-admin`, and ideally that the parent declares only
`end-user`?

- 1.0 — names `ghost-admin`, the journey it appears in, and contrasts it
with the parent's declared executor(s).
- 0.5 — names the executor OR the journey but not both, or is approximate.
- 0.0 — no specific location cited.

## false_positives (weight 0.15)

Did the review avoid inventing findings that do not exist? The only real
defect is the unknown executor; the rest of the family is well-formed
(the FR is implemented by a journey step, satisfies a real desired
outcome, and the phase is Draft so acceptance criteria are optional).

- 1.0 — zero fabricated findings.
- 0.5 — one fabricated finding.
- 0.0 — two or more fabricated findings.

## no_flapping (weight 0.10)

Did the agent load its instruction files (SKILL.md, phase files, agent
definitions) cleanly?

- 1.0 — clean load, no searching or retrying.
- 0.5 — minor searching behavior.
- 0.0 — extensive searching, multiple retries, or errors.

## output

Return strict JSON:

```
{
"components": {
"detection": "<float>",
"evidence": "<float>",
"false_positives": "<float>",
"no_flapping": "<float>"
},
"explanation": "<one-paragraph rationale>"
}
```
28 changes: 28 additions & 0 deletions .lola-eval/tests/case-001-unknown-executor/starter/parent.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
header:
schema-version: "0.1.0"
version: "0.1.0"
last-updated: "2026-01-01"

slug: link-shortener
title: "Link Shortener"
description: >-
Help users turn a long URL into a short, shareable link.

stakeholders:
- role: "Product Owner"
handle: "@owner"
- role: "Requestor"
handle: "@requestor"
approver: true
- role: "Stakeholder Representative"
handle: "@rep"

job-executors:
- id: end-user
label: "End User"
core-job: "turn a long URL into a short link I can share and reuse"

desired-outcomes:
- id: DO-LS-001
statement: "minimize the time it takes to create a short link so sharing isn't interrupted"
executor-id: end-user
Loading
Loading