Skip to content

Commit e5d87bf

Browse files
feat(skills): runnable determinism gate + fix broken gate providers + plugin leak (#62)
make determinism [SKILL=<name>] runs the loop's regression gate. Fix the 4 non-trace-review determinism configs whose exec provider path never resolved (promptfoo runs exec from the config dir; give each skill a local eval-run.sh wrapper). Exclude dev-only eval-run.sh from the shipped plugin.
1 parent 26b144a commit e5d87bf

12 files changed

Lines changed: 64 additions & 20 deletions

File tree

CLAUDE.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ computable work, on two axes: **offload** an AI decide/parse/score step to bash/
108108
(assert with promptfoo `is-json`/`regex`/no-fences on stdout). Leave genuinely-creative
109109
steps model-driven. The `determinize-skill` (proof-of-skill) automates this audit.
110110

111+
Each converted skill carries a `skills/<name>/determinism.promptfooconfig.yaml` (dev-only, not
112+
shipped in the plugin). Run the gate with **`make determinism`** (all skills) or
113+
**`make determinism SKILL=<name>`** (one). It needs API keys (the skill-spawning configs) and a
114+
reachable Langfuse (trace-review), so it is deliberately NOT part of `make test` / CI. promptfoo
115+
runs each config's `exec:` provider from that config's own directory, so provider paths are
116+
config-dir-relative (`../../eval/lib/run-skill.sh` for the claude-spawning skills, a local
117+
`eval-run.sh` for trace-review). This is the loop's pre-edit regression gate.
118+
111119
## Hooks Architecture
112120

113121
Hook scripts live in `lib/hooks/`, registered in `~/.claude/settings.json` via `devflow init` (step 5d).

Makefile

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ LIBDIR := $(PREFIX)/share/devflow
44
VERSION := 0.13.0
55
TARBALL := devflow-$(VERSION).tar.gz
66

7-
.PHONY: install uninstall link test test-unit brew-local release help plugin-dev plugin-unlink plugin-install check-version check-formula version-bump flows flows-check skills-sync skills-check skills-guard
7+
.PHONY: install uninstall link test test-unit brew-local release help plugin-dev plugin-unlink plugin-install check-version check-formula version-bump flows flows-check skills-sync skills-check skills-guard determinism
88

99
help: ## Show this help
1010
@grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*## "}; {printf " %-14s %s\n", $$1, $$2}'
@@ -64,6 +64,21 @@ skills-check: ## Fail if generated plugin skills/commands drift from repo-root s
6464
skills-guard: ## Detect skill edits made in the WRONG (generated) tree and fold them into the source
6565
@bash scripts/skills-guard.sh
6666

67+
determinism: ## Run the promptfoo determinism gate for skills with a config (needs API keys + Langfuse; not in CI). One skill: make determinism SKILL=<name>
68+
@command -v npx >/dev/null 2>&1 || { echo "determinism: npx (Node) is required"; exit 1; }
69+
@[ -f "$(HOME)/.config/zsh/secrets" ] && . "$(HOME)/.config/zsh/secrets" 2>/dev/null || true; \
70+
fail=0; ran=0; \
71+
for cfg in skills/*/determinism.promptfooconfig.yaml; do \
72+
[ -f "$$cfg" ] || continue; \
73+
name="$$(basename "$$(dirname "$$cfg")")"; \
74+
if [ -n "$(SKILL)" ] && [ "$(SKILL)" != "$$name" ]; then continue; fi; \
75+
echo "=== determinism gate: $$name ==="; ran=1; \
76+
npx -y promptfoo@latest eval -c "$$cfg" --no-cache || fail=1; \
77+
done; \
78+
[ "$$ran" = 1 ] || { echo "determinism: no config matched$(if $(SKILL), for SKILL=$(SKILL),)"; exit 1; }; \
79+
[ "$$fail" = 0 ] || { echo "determinism gate FAILED"; exit 1; }; \
80+
echo "determinism gate passed"
81+
6782
flows: ## Regenerate flow mini-plugins from canonical sources
6883
@bash scripts/build-flows.sh
6984

devflow-plugin/skills/trace-review/eval-run.sh

Lines changed: 0 additions & 14 deletions
This file was deleted.

scripts/build-skills.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,13 @@ command -v jq >/dev/null 2>&1 || { echo "build-skills: jq is required" >&2; exit
2828
# VERSION always comes from the real plugin manifest, even when rendering to a temp OUT.
2929
VERSION="$(jq -r .version "${ROOT}/devflow-plugin/.claude-plugin/plugin.json")"
3030

31-
# Files that live beside a skill at repo root for dev/CI but must NOT ship in the plugin.
31+
# Files that live beside a skill at repo root for dev/CI but must NOT ship in the plugin:
32+
# the promptfoo determinism config and its exec-provider harness (references bin/devflow via a
33+
# repo-relative path that does not exist in a plugin install).
3234
_is_dev_only() {
3335
case "$(basename "$1")" in
3436
determinism.promptfooconfig.yaml) return 0 ;;
37+
eval-run.sh) return 0 ;;
3538
*) return 1 ;;
3639
esac
3740
}

skills/finish-feature/determinism.promptfooconfig.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ prompts:
99
- '{{input}}'
1010

1111
providers:
12-
- id: 'exec: bash eval/lib/run-skill.sh'
12+
- id: 'exec: bash eval-run.sh'
1313
label: finish-feature@head
1414

1515
tests:

skills/finish-feature/eval-run.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env bash
2+
# promptfoo `exec:` provider for this skill's determinism asserts.
3+
# promptfoo runs the exec command from THIS config's directory and validates the file
4+
# locally, so it must be a local file — it then execs the shared headless runner with an
5+
# absolute path (no CWD assumptions). Dev-only: excluded from the shipped plugin.
6+
set -euo pipefail
7+
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8+
exec bash "${here}/../../eval/lib/run-skill.sh" "$@"

skills/new-feature/determinism.promptfooconfig.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ prompts:
88

99
providers:
1010
# run-skill.sh clears CLAUDECODE so the nested claude launches (sdk#573). Run from repo root.
11-
- id: 'exec: bash eval/lib/run-skill.sh'
11+
- id: 'exec: bash eval-run.sh'
1212
label: new-feature@head
1313

1414
defaultTest:

skills/new-feature/eval-run.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env bash
2+
# promptfoo `exec:` provider for this skill's determinism asserts.
3+
# promptfoo runs the exec command from THIS config's directory and validates the file
4+
# locally, so it must be a local file — it then execs the shared headless runner with an
5+
# absolute path (no CWD assumptions). Dev-only: excluded from the shipped plugin.
6+
set -euo pipefail
7+
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8+
exec bash "${here}/../../eval/lib/run-skill.sh" "$@"

skills/resolve-repo/determinism.promptfooconfig.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ prompts:
88
- '{{input}}'
99

1010
providers:
11-
- id: 'exec: bash eval/lib/run-skill.sh'
11+
- id: 'exec: bash eval-run.sh'
1212
label: resolve-repo@head
1313

1414
tests:

skills/resolve-repo/eval-run.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env bash
2+
# promptfoo `exec:` provider for this skill's determinism asserts.
3+
# promptfoo runs the exec command from THIS config's directory and validates the file
4+
# locally, so it must be a local file — it then execs the shared headless runner with an
5+
# absolute path (no CWD assumptions). Dev-only: excluded from the shipped plugin.
6+
set -euo pipefail
7+
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8+
exec bash "${here}/../../eval/lib/run-skill.sh" "$@"

0 commit comments

Comments
 (0)