crewhaus 0.1.8 → 0.2.0
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 +10 -3
- package/dist/advice-apply.d.ts +182 -0
- package/dist/advice-apply.js +286 -0
- package/dist/advise-rules.d.ts +348 -0
- package/dist/advise-rules.js +905 -0
- package/dist/alert-sink.d.ts +48 -0
- package/dist/alert-sink.js +86 -0
- package/dist/approval-gate.d.ts +127 -0
- package/dist/approval-gate.js +254 -0
- package/dist/audit-verify.d.ts +69 -0
- package/dist/audit-verify.js +97 -0
- package/dist/autodistill.d.ts +113 -0
- package/dist/autodistill.js +256 -0
- package/dist/channel-provision.d.ts +360 -0
- package/dist/channel-provision.js +881 -0
- package/dist/ci-scaffold.d.ts +31 -0
- package/dist/ci-scaffold.js +343 -0
- package/dist/compile-check.d.ts +90 -0
- package/dist/compile-check.js +285 -0
- package/dist/compliance-schedule.d.ts +35 -0
- package/dist/compliance-schedule.js +36 -0
- package/dist/context-pressure.d.ts +80 -0
- package/dist/context-pressure.js +166 -0
- package/dist/dataset-mine.d.ts +172 -0
- package/dist/dataset-mine.js +403 -0
- package/dist/datasets.d.ts +124 -0
- package/dist/datasets.js +260 -0
- package/dist/deploy-canary.d.ts +83 -0
- package/dist/deploy-canary.js +87 -0
- package/dist/doctor-checks.d.ts +33 -0
- package/dist/doctor-checks.js +92 -0
- package/dist/doctor-detect.d.ts +108 -0
- package/dist/doctor-detect.js +214 -0
- package/dist/doctor-fix.d.ts +81 -0
- package/dist/doctor-fix.js +164 -0
- package/dist/egress-triage.d.ts +121 -0
- package/dist/egress-triage.js +261 -0
- package/dist/eval-bridge.d.ts +114 -0
- package/dist/eval-bridge.js +158 -0
- package/dist/eval-coverage.d.ts +140 -0
- package/dist/eval-coverage.js +428 -0
- package/dist/eval-history.d.ts +48 -0
- package/dist/eval-history.js +157 -0
- package/dist/eval-matrix.d.ts +80 -0
- package/dist/eval-matrix.js +182 -0
- package/dist/eval-sentinel.d.ts +65 -0
- package/dist/eval-sentinel.js +132 -0
- package/dist/faq.d.ts +68 -0
- package/dist/faq.js +168 -0
- package/dist/feedback.d.ts +9 -2
- package/dist/feedback.js +17 -7
- package/dist/fewshot.d.ts +83 -0
- package/dist/fewshot.js +158 -0
- package/dist/fleet.d.ts +207 -0
- package/dist/fleet.js +488 -0
- package/dist/flywheel.d.ts +193 -0
- package/dist/flywheel.js +519 -0
- package/dist/graders-suggest.d.ts +186 -0
- package/dist/graders-suggest.js +658 -0
- package/dist/incident.d.ts +99 -0
- package/dist/incident.js +217 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +11601 -964
- package/dist/init-interactive.d.ts +105 -0
- package/dist/init-interactive.js +208 -0
- package/dist/intents.d.ts +105 -0
- package/dist/intents.js +292 -0
- package/dist/judge-calibrate.d.ts +137 -0
- package/dist/judge-calibrate.js +247 -0
- package/dist/justification-calibrate.d.ts +150 -0
- package/dist/justification-calibrate.js +262 -0
- package/dist/justification-gate.d.ts +27 -6
- package/dist/justification-gate.js +30 -6
- package/dist/knowledge-sync.d.ts +179 -0
- package/dist/knowledge-sync.js +551 -0
- package/dist/lessons.d.ts +87 -0
- package/dist/lessons.js +207 -0
- package/dist/lint.d.ts +127 -0
- package/dist/lint.js +226 -0
- package/dist/loadtest.d.ts +114 -0
- package/dist/loadtest.js +196 -0
- package/dist/marketplace-cli.d.ts +110 -0
- package/dist/marketplace-cli.js +250 -0
- package/dist/mcp-doctor.d.ts +121 -0
- package/dist/mcp-doctor.js +249 -0
- package/dist/model-scan.d.ts +116 -0
- package/dist/model-scan.js +226 -0
- package/dist/onchain-tune.d.ts +164 -0
- package/dist/onchain-tune.js +346 -0
- package/dist/permissions-suggest.d.ts +126 -0
- package/dist/permissions-suggest.js +333 -0
- package/dist/pii-tune.d.ts +107 -0
- package/dist/pii-tune.js +122 -0
- package/dist/propose.d.ts +117 -0
- package/dist/propose.js +184 -0
- package/dist/refresh-goldens.d.ts +82 -0
- package/dist/refresh-goldens.js +221 -0
- package/dist/regression-pin.d.ts +160 -0
- package/dist/regression-pin.js +281 -0
- package/dist/retention.d.ts +193 -0
- package/dist/retention.js +607 -0
- package/dist/retire.d.ts +118 -0
- package/dist/retire.js +291 -0
- package/dist/right-size.d.ts +100 -0
- package/dist/right-size.js +123 -0
- package/dist/scaffold-evals.d.ts +138 -0
- package/dist/scaffold-evals.js +410 -0
- package/dist/scope-audit-drift.d.ts +139 -0
- package/dist/scope-audit-drift.js +260 -0
- package/dist/security-corpus.d.ts +237 -0
- package/dist/security-corpus.js +516 -0
- package/dist/security-digest.d.ts +173 -0
- package/dist/security-digest.js +650 -0
- package/dist/sessions-index.d.ts +27 -0
- package/dist/sessions-index.js +51 -0
- package/dist/slo-doctor.d.ts +67 -0
- package/dist/slo-doctor.js +119 -0
- package/dist/slo-sink.d.ts +96 -0
- package/dist/slo-sink.js +107 -0
- package/dist/spec-changelog.d.ts +102 -0
- package/dist/spec-changelog.js +237 -0
- package/dist/state-backup.d.ts +223 -0
- package/dist/state-backup.js +648 -0
- package/dist/tools-cli.d.ts +170 -0
- package/dist/tools-cli.js +298 -0
- package/dist/triage.d.ts +202 -0
- package/dist/triage.js +403 -0
- package/dist/upgrade.d.ts +57 -0
- package/dist/upgrade.js +113 -0
- package/dist/version.d.ts +6 -0
- package/dist/version.js +27 -0
- package/dist/voice-eval.d.ts +138 -0
- package/dist/voice-eval.js +309 -0
- package/dist/watch.d.ts +58 -0
- package/dist/watch.js +97 -0
- package/package.json +89 -65
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const EVAL_CI_WORKFLOW_RELPATH: string;
|
|
2
|
+
export declare const SENTINEL_WORKFLOW_RELPATH: string;
|
|
3
|
+
/**
|
|
4
|
+
* Item 30 — the nightly model-drift sentinel workflow, scaffolded by
|
|
5
|
+
* `crewhaus init --sentinel`. Mirrors the shipped
|
|
6
|
+
* `@crewhaus/eval-report/templates/sentinel-drift.yml` template but adapts the
|
|
7
|
+
* paths to a nested harness (finding 7: GitHub only reads `.github/workflows`
|
|
8
|
+
* at the repo root, so a nested harness's spec/dataset paths are prefixed).
|
|
9
|
+
*
|
|
10
|
+
* It re-runs a seed-pinned sentinel dataset against the UNCHANGED spec and
|
|
11
|
+
* diffs against a FROZEN baseline run committed at `<harness>/eval/
|
|
12
|
+
* sentinel-baseline`. `crewhaus eval --sentinel` exits non-zero on provider
|
|
13
|
+
* drift (or a not-comparable spec/dataset-hash mismatch), failing this job so
|
|
14
|
+
* GitHub's scheduled-workflow-failure notification alerts.
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildSentinelDriftWorkflowYaml(opts?: {
|
|
17
|
+
readonly harnessDir?: string;
|
|
18
|
+
}): string;
|
|
19
|
+
/**
|
|
20
|
+
* The eval-gated-PR workflow. Model-budget-conscious by default:
|
|
21
|
+
* `--concurrency 1` (the 30k-TPM default provider tier) and a pinned seed
|
|
22
|
+
* for determinism.
|
|
23
|
+
*
|
|
24
|
+
* `harnessDir` (repo-root-relative, "" = root) adapts the scaffold to a
|
|
25
|
+
* nested harness: the job's working-directory, the `paths:` trigger filter,
|
|
26
|
+
* and the base-branch checkout `path:` are all repo-root-anchored, so every
|
|
27
|
+
* one of them needs the prefix (finding 7).
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildEvalCiWorkflowYaml(opts?: {
|
|
30
|
+
readonly harnessDir?: string;
|
|
31
|
+
}): string;
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Item 44 — `crewhaus init --ci`: the eval-on-PR CI gate as a scaffold.
|
|
3
|
+
* Emits `.github/workflows/crewhaus-eval.yml`, which turns the shipped
|
|
4
|
+
* `eval --gate` exit-code plumbing into a required PR check: every pull
|
|
5
|
+
* request touching the spec, dataset, or graders is compiled, evaled, and
|
|
6
|
+
* gated against the base branch — with the score-delta table posted as a
|
|
7
|
+
* PR comment.
|
|
8
|
+
*
|
|
9
|
+
* Baseline strategy (the design decision, also documented in the workflow
|
|
10
|
+
* comments): the job runs TWO fresh evals — the base branch's spec and the
|
|
11
|
+
* PR's spec, both on the PR's dataset+graders, same pinned seed — instead
|
|
12
|
+
* of diffing against a baseline persisted in the repo. Committing
|
|
13
|
+
* `.crewhaus/evals/baselines.json` plus the pinned run directory would
|
|
14
|
+
* save one eval per PR, but the stored transcripts go stale against every
|
|
15
|
+
* provider/model change, force contributors to re-commit run artifacts on
|
|
16
|
+
* every accepted change, and make the gate's "before" unreproducible. The
|
|
17
|
+
* two-run flow needs no persisted state and isolates exactly the PR's
|
|
18
|
+
* change (only the spec differs between the runs); the trade-off is one
|
|
19
|
+
* extra eval per PR and exposure to judge nondeterminism, which the pinned
|
|
20
|
+
* `--seed` and `--concurrency 1` bound as far as the graders allow.
|
|
21
|
+
*
|
|
22
|
+
* Mechanically the gate reuses the item-3 run-history machinery inside the
|
|
23
|
+
* job's FRESH workspace: the first `crewhaus eval` (base spec) pins the
|
|
24
|
+
* (spec, dataset) baseline; the second (PR spec) runs with `--gate`, which
|
|
25
|
+
* diffs against that pin and exits non-zero on any pass-rate drop or
|
|
26
|
+
* sample-level pass→fail flip. A PR that renames the spec or changes the
|
|
27
|
+
* dataset keyset starts a new lineage — the gate passes vacuously and the
|
|
28
|
+
* comment says so.
|
|
29
|
+
*
|
|
30
|
+
* Kept in a side-effect-free module (the CLI entry file runs an argv
|
|
31
|
+
* switch on import) mirroring `flywheel.ts`, which owns the shared
|
|
32
|
+
* `scaffoldWorkflowFile` writer.
|
|
33
|
+
*/
|
|
34
|
+
import { join } from "node:path";
|
|
35
|
+
import { normalizeHarnessDir } from "./flywheel";
|
|
36
|
+
export const EVAL_CI_WORKFLOW_RELPATH = join(".github", "workflows", "crewhaus-eval.yml");
|
|
37
|
+
export const SENTINEL_WORKFLOW_RELPATH = join(".github", "workflows", "sentinel-drift.yml");
|
|
38
|
+
/**
|
|
39
|
+
* Item 30 — the nightly model-drift sentinel workflow, scaffolded by
|
|
40
|
+
* `crewhaus init --sentinel`. Mirrors the shipped
|
|
41
|
+
* `@crewhaus/eval-report/templates/sentinel-drift.yml` template but adapts the
|
|
42
|
+
* paths to a nested harness (finding 7: GitHub only reads `.github/workflows`
|
|
43
|
+
* at the repo root, so a nested harness's spec/dataset paths are prefixed).
|
|
44
|
+
*
|
|
45
|
+
* It re-runs a seed-pinned sentinel dataset against the UNCHANGED spec and
|
|
46
|
+
* diffs against a FROZEN baseline run committed at `<harness>/eval/
|
|
47
|
+
* sentinel-baseline`. `crewhaus eval --sentinel` exits non-zero on provider
|
|
48
|
+
* drift (or a not-comparable spec/dataset-hash mismatch), failing this job so
|
|
49
|
+
* GitHub's scheduled-workflow-failure notification alerts.
|
|
50
|
+
*/
|
|
51
|
+
export function buildSentinelDriftWorkflowYaml(opts = {}) {
|
|
52
|
+
const sub = normalizeHarnessDir(opts.harnessDir);
|
|
53
|
+
const prefix = sub === "" ? "" : `${sub}/`;
|
|
54
|
+
const workingDirLine = sub === "" ? "" : `\n working-directory: ${sub}`;
|
|
55
|
+
const subdirNote = sub === ""
|
|
56
|
+
? ""
|
|
57
|
+
: `# - Scaffolded for the harness at ${sub}/ — the job's working-directory is
|
|
58
|
+
# set so the spec/dataset/baseline paths resolve inside it.\n`;
|
|
59
|
+
return `# sentinel-drift.yml — scaffolded by \`crewhaus init --sentinel\` (item 30).
|
|
60
|
+
#
|
|
61
|
+
# Nightly model-drift sentinel: re-runs a SEED-PINNED sentinel dataset against
|
|
62
|
+
# your UNCHANGED spec and diffs the fresh run against a FROZEN baseline run
|
|
63
|
+
# committed to the repo. When the spec's specHash AND the sentinel dataset's
|
|
64
|
+
# content hash are BOTH identical to the baseline's, any pass/fail flip or
|
|
65
|
+
# score shift can ONLY be the provider silently changing model behaviour.
|
|
66
|
+
# \`crewhaus eval --sentinel\` exits non-zero on drift (and on a not-comparable
|
|
67
|
+
# spec/dataset-hash mismatch — a mis-pointed sentinel is loud, never silently
|
|
68
|
+
# green), so this job goes red and GitHub's scheduled-failure email alerts.
|
|
69
|
+
#
|
|
70
|
+
# WHY A COMMITTED BASELINE (unlike the eval-on-PR gate's two fresh runs): a
|
|
71
|
+
# drift probe needs a fixed "before" that survives across nights and is NOT
|
|
72
|
+
# re-derived from the current provider — that is the whole point.
|
|
73
|
+
#
|
|
74
|
+
# BEFORE THIS RUNS, freeze the baseline once against the current spec+dataset:
|
|
75
|
+
# crewhaus eval ${prefix}crewhaus.yaml \\
|
|
76
|
+
# --dataset ${prefix}eval/sentinel.jsonl --graders ${prefix}eval/graders.yaml \\
|
|
77
|
+
# --seed 1 -o ${prefix}eval/sentinel-baseline
|
|
78
|
+
# git add ${prefix}eval/sentinel-baseline && git commit
|
|
79
|
+
# Re-freeze it (same command) whenever you INTENTIONALLY change the spec or
|
|
80
|
+
# sentinel data, or knowingly adopt a new provider baseline.
|
|
81
|
+
${subdirNote}#
|
|
82
|
+
# Required repo secret: ANTHROPIC_API_KEY (billed by the nightly eval run).
|
|
83
|
+
|
|
84
|
+
name: sentinel-drift
|
|
85
|
+
|
|
86
|
+
on:
|
|
87
|
+
schedule:
|
|
88
|
+
# Nightly at 03:17 UTC (off the hour to dodge cron scheduling backlog).
|
|
89
|
+
- cron: "17 3 * * *"
|
|
90
|
+
workflow_dispatch: {}
|
|
91
|
+
|
|
92
|
+
permissions:
|
|
93
|
+
contents: read
|
|
94
|
+
|
|
95
|
+
env:
|
|
96
|
+
SENTINEL_SEED: "1" # pin the sample order so only the PROVIDER can vary
|
|
97
|
+
|
|
98
|
+
jobs:
|
|
99
|
+
sentinel:
|
|
100
|
+
runs-on: ubuntu-latest
|
|
101
|
+
timeout-minutes: 20
|
|
102
|
+
defaults:
|
|
103
|
+
run:
|
|
104
|
+
shell: bash${workingDirLine}
|
|
105
|
+
steps:
|
|
106
|
+
- name: Checkout
|
|
107
|
+
uses: actions/checkout@v4
|
|
108
|
+
|
|
109
|
+
- name: Install Bun
|
|
110
|
+
uses: oven-sh/setup-bun@v2
|
|
111
|
+
|
|
112
|
+
- name: Install crewhaus CLI
|
|
113
|
+
run: |
|
|
114
|
+
bun add -g crewhaus
|
|
115
|
+
echo "$HOME/.bun/bin" >> "$GITHUB_PATH"
|
|
116
|
+
|
|
117
|
+
- name: Run the drift sentinel
|
|
118
|
+
env:
|
|
119
|
+
ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
120
|
+
run: |
|
|
121
|
+
crewhaus eval crewhaus.yaml \\
|
|
122
|
+
--dataset eval/sentinel.jsonl \\
|
|
123
|
+
--graders eval/graders.yaml \\
|
|
124
|
+
--seed "$SENTINEL_SEED" \\
|
|
125
|
+
--sentinel --baseline eval/sentinel-baseline \\
|
|
126
|
+
-o .crewhaus/evals/sentinel-\$(date -u +%Y%m%d)
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The eval-gated-PR workflow. Model-budget-conscious by default:
|
|
131
|
+
* `--concurrency 1` (the 30k-TPM default provider tier) and a pinned seed
|
|
132
|
+
* for determinism.
|
|
133
|
+
*
|
|
134
|
+
* `harnessDir` (repo-root-relative, "" = root) adapts the scaffold to a
|
|
135
|
+
* nested harness: the job's working-directory, the `paths:` trigger filter,
|
|
136
|
+
* and the base-branch checkout `path:` are all repo-root-anchored, so every
|
|
137
|
+
* one of them needs the prefix (finding 7).
|
|
138
|
+
*/
|
|
139
|
+
export function buildEvalCiWorkflowYaml(opts = {}) {
|
|
140
|
+
const sub = normalizeHarnessDir(opts.harnessDir);
|
|
141
|
+
const prefix = sub === "" ? "" : `${sub}/`;
|
|
142
|
+
const workingDirLine = sub === "" ? "" : `\n working-directory: ${sub}`;
|
|
143
|
+
// Working-dir-relative path of the base branch's spec: the base checkout
|
|
144
|
+
// lands at <harness>/.crewhaus-ci/base (a FULL repo copy), so a nested
|
|
145
|
+
// harness's spec sits at .crewhaus-ci/base/<harness>/crewhaus.yaml.
|
|
146
|
+
const baseSpec = `.crewhaus-ci/base/${prefix}crewhaus.yaml`;
|
|
147
|
+
const subdirNote = sub === ""
|
|
148
|
+
? `# - If your harness lives in a subdirectory, re-run \`crewhaus init --ci\`
|
|
149
|
+
# from the repo (the CLI detects the git root and prefixes everything),
|
|
150
|
+
# or hand-set jobs.eval-gate.defaults.run.working-directory AND prefix
|
|
151
|
+
# the paths: filter and the base-checkout path: — both resolve from the
|
|
152
|
+
# repo root, not the working directory.`
|
|
153
|
+
: `# - Scaffolded for the harness at ${sub}/ — the job's
|
|
154
|
+
# working-directory, the paths: filter, and the base-checkout path are
|
|
155
|
+
# already prefixed.`;
|
|
156
|
+
return `# crewhaus-eval.yml — scaffolded by \`crewhaus init --ci\`.
|
|
157
|
+
#
|
|
158
|
+
# Eval-gated spec PRs (the exact chain EP01 proves by hand): on every pull
|
|
159
|
+
# request touching the spec, dataset, or graders, this workflow
|
|
160
|
+
# 1. compiles the PR's spec (offline gate — a spec that doesn't compile
|
|
161
|
+
# never spends eval budget),
|
|
162
|
+
# 2. evals the BASE branch's spec on the PR's dataset+graders (this pins
|
|
163
|
+
# the run-history baseline in the job's fresh workspace),
|
|
164
|
+
# 3. evals the PR's spec with --gate, which diffs against that baseline
|
|
165
|
+
# and exits non-zero on any pass-rate drop or per-sample pass→fail
|
|
166
|
+
# flip (the strict gate from apps/cli/src/eval-history.ts),
|
|
167
|
+
# 4. posts the score-delta table as a PR comment, and
|
|
168
|
+
# 5. fails the check when the gate failed.
|
|
169
|
+
#
|
|
170
|
+
# BASELINE STRATEGY — why two fresh runs instead of a persisted baseline:
|
|
171
|
+
# committing .crewhaus/evals/baselines.json + the pinned run dir to the repo
|
|
172
|
+
# would save one eval per PR, but stored run artifacts go stale against
|
|
173
|
+
# every provider/model change, must be re-committed after every accepted
|
|
174
|
+
# PR, and make the "before" side unreproducible. Two fresh runs need no
|
|
175
|
+
# persisted state and isolate exactly the PR's change — only the SPEC
|
|
176
|
+
# differs between the runs (both use the PR's dataset+graders). Trade-off:
|
|
177
|
+
# one extra eval per PR + judge nondeterminism, bounded by the pinned
|
|
178
|
+
# EVAL_SEED and EVAL_CONCURRENCY=1 below.
|
|
179
|
+
#
|
|
180
|
+
# BRANCH-PROTECTION CAVEAT — read before marking this check REQUIRED:
|
|
181
|
+
# this workflow only runs on PRs that touch the paths: filter below. A PR
|
|
182
|
+
# that does NOT touch those paths never starts the workflow, so a REQUIRED
|
|
183
|
+
# crewhaus-eval check deadlocks it — stuck forever on "Expected — waiting
|
|
184
|
+
# for status". Leave the check optional, or enforce it only through
|
|
185
|
+
# paths-aware tooling (e.g. a merge queue that understands path filters).
|
|
186
|
+
# COST: every push to a matching PR re-runs the job and bills TWO evals
|
|
187
|
+
# (base spec + PR spec) against ANTHROPIC_API_KEY.
|
|
188
|
+
#
|
|
189
|
+
# Notes:
|
|
190
|
+
# - First PR introducing the spec: the base branch has no crewhaus.yaml,
|
|
191
|
+
# so there is no baseline — the base eval and the gate are SKIPPED, the
|
|
192
|
+
# comment says so, and the check goes green; future PRs will gate.
|
|
193
|
+
# - Fork PRs: the whole job is skipped (see jobs.eval-gate.if) — GitHub
|
|
194
|
+
# does not expose the ANTHROPIC_API_KEY secret to fork-triggered runs.
|
|
195
|
+
# - A PR that renames the spec or changes the dataset's sample keyset
|
|
196
|
+
# starts a NEW baseline lineage: the gate passes vacuously (nothing
|
|
197
|
+
# comparable to regress against) and the diff step says so.
|
|
198
|
+
# - EVAL_CONCURRENCY=1 keeps the job under a low provider rate-limit
|
|
199
|
+
# tier (the 30k-TPM default tier); raise it if your key allows.
|
|
200
|
+
${subdirNote}
|
|
201
|
+
#
|
|
202
|
+
# Required repo secret: ANTHROPIC_API_KEY (billed by the two eval runs).
|
|
203
|
+
|
|
204
|
+
name: crewhaus-eval
|
|
205
|
+
|
|
206
|
+
on:
|
|
207
|
+
pull_request:
|
|
208
|
+
paths:
|
|
209
|
+
- "${prefix}crewhaus.yaml"
|
|
210
|
+
- "${prefix}eval/**"
|
|
211
|
+
|
|
212
|
+
permissions:
|
|
213
|
+
contents: read
|
|
214
|
+
pull-requests: write # the score-delta comment
|
|
215
|
+
|
|
216
|
+
# One eval gate per PR head; a force-push cancels the stale run.
|
|
217
|
+
concurrency:
|
|
218
|
+
group: crewhaus-eval-\${{ github.ref }}
|
|
219
|
+
cancel-in-progress: true
|
|
220
|
+
|
|
221
|
+
env:
|
|
222
|
+
EVAL_SEED: "1" # pinned for determinism across the base/PR runs
|
|
223
|
+
EVAL_CONCURRENCY: "1" # 30k-TPM-tier default; raise if your key allows
|
|
224
|
+
|
|
225
|
+
jobs:
|
|
226
|
+
eval-gate:
|
|
227
|
+
# Fork PRs cannot read secrets.ANTHROPIC_API_KEY (GitHub withholds
|
|
228
|
+
# secrets from fork-triggered runs), so the evals would fail red at
|
|
229
|
+
# model-client construction — skip the whole job instead (a skipped
|
|
230
|
+
# check is neutral, not a red X). Maintainers can gate a fork's change
|
|
231
|
+
# by pushing its commits to an in-repo branch (same-repo PRs get
|
|
232
|
+
# secrets), by re-running the eval after merge, or via a manual
|
|
233
|
+
# workflow_dispatch wrapper of their own.
|
|
234
|
+
if: github.event.pull_request.head.repo.full_name == github.repository
|
|
235
|
+
runs-on: ubuntu-latest
|
|
236
|
+
timeout-minutes: 30
|
|
237
|
+
defaults:
|
|
238
|
+
run:
|
|
239
|
+
shell: bash${workingDirLine}
|
|
240
|
+
steps:
|
|
241
|
+
- name: Checkout PR
|
|
242
|
+
uses: actions/checkout@v4
|
|
243
|
+
|
|
244
|
+
- name: Checkout base branch (the gate's "before" spec)
|
|
245
|
+
uses: actions/checkout@v4
|
|
246
|
+
with:
|
|
247
|
+
ref: \${{ github.base_ref }}
|
|
248
|
+
# checkout paths resolve from the repo root (working-directory
|
|
249
|
+
# only applies to run steps).
|
|
250
|
+
path: ${prefix}.crewhaus-ci/base
|
|
251
|
+
|
|
252
|
+
- name: Install Bun
|
|
253
|
+
uses: oven-sh/setup-bun@v2
|
|
254
|
+
|
|
255
|
+
- name: Install crewhaus CLI
|
|
256
|
+
run: |
|
|
257
|
+
bun add -g crewhaus
|
|
258
|
+
echo "$HOME/.bun/bin" >> "$GITHUB_PATH"
|
|
259
|
+
|
|
260
|
+
- name: Compile gate (offline)
|
|
261
|
+
run: crewhaus compile crewhaus.yaml --emit-ir > /dev/null
|
|
262
|
+
|
|
263
|
+
- name: Detect the baseline spec
|
|
264
|
+
id: baseline
|
|
265
|
+
run: |
|
|
266
|
+
# First PR introducing the spec: the base branch has nothing to
|
|
267
|
+
# gate against. Skip the base eval + the gate (green check) — the
|
|
268
|
+
# comment step reports it; future PRs will gate.
|
|
269
|
+
if [ -f "${baseSpec}" ]; then
|
|
270
|
+
echo "exists=true" >> "$GITHUB_OUTPUT"
|
|
271
|
+
else
|
|
272
|
+
echo "exists=false" >> "$GITHUB_OUTPUT"
|
|
273
|
+
echo "first PR introducing the spec — no baseline; future PRs will gate"
|
|
274
|
+
fi
|
|
275
|
+
|
|
276
|
+
- name: Eval base spec (pins the baseline)
|
|
277
|
+
if: steps.baseline.outputs.exists == 'true'
|
|
278
|
+
env:
|
|
279
|
+
ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
280
|
+
run: |
|
|
281
|
+
# Base SPEC, PR dataset+graders: only the spec differs between the
|
|
282
|
+
# two runs, so the gate isolates exactly the PR's spec change.
|
|
283
|
+
crewhaus eval ${baseSpec} \\
|
|
284
|
+
--dataset eval/dataset.jsonl \\
|
|
285
|
+
--graders eval/graders.yaml \\
|
|
286
|
+
--concurrency "$EVAL_CONCURRENCY" --seed "$EVAL_SEED" \\
|
|
287
|
+
-o .crewhaus-ci/runs/base
|
|
288
|
+
|
|
289
|
+
- name: Eval PR spec (gated against the base run)
|
|
290
|
+
id: gate
|
|
291
|
+
if: steps.baseline.outputs.exists == 'true'
|
|
292
|
+
# The comment must post either way; the last step re-fails the check.
|
|
293
|
+
continue-on-error: true
|
|
294
|
+
env:
|
|
295
|
+
ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
296
|
+
run: |
|
|
297
|
+
crewhaus eval crewhaus.yaml \\
|
|
298
|
+
--dataset eval/dataset.jsonl \\
|
|
299
|
+
--graders eval/graders.yaml \\
|
|
300
|
+
--concurrency "$EVAL_CONCURRENCY" --seed "$EVAL_SEED" \\
|
|
301
|
+
--gate \\
|
|
302
|
+
-o .crewhaus-ci/runs/pr
|
|
303
|
+
|
|
304
|
+
- name: Comment the score delta on the PR
|
|
305
|
+
if: always()
|
|
306
|
+
env:
|
|
307
|
+
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
308
|
+
run: |
|
|
309
|
+
read_row() { # $1 = run dir → "pass_rate | mean_score" (or n/a)
|
|
310
|
+
bun -e "const r=JSON.parse(require('fs').readFileSync('$1/results.json','utf8'));process.stdout.write((r.aggregates.passRate*100).toFixed(1)+'% | '+r.aggregates.meanScore.toFixed(3))" 2>/dev/null \\
|
|
311
|
+
|| printf 'n/a | n/a'
|
|
312
|
+
}
|
|
313
|
+
BASE_ROW="$(read_row .crewhaus-ci/runs/base)"
|
|
314
|
+
PR_ROW="$(read_row .crewhaus-ci/runs/pr)"
|
|
315
|
+
DIFF_LINE="$(crewhaus eval-report diff .crewhaus-ci/runs/base .crewhaus-ci/runs/pr -o .crewhaus-ci/runs/diff 2>/dev/null | tail -n 1 \\
|
|
316
|
+
|| echo 'diff unavailable — new baseline lineage (spec renamed / dataset keyset changed) or a run failed')"
|
|
317
|
+
# Only a FAILING GATE STEP is a regression. A gate that never ran
|
|
318
|
+
# (no baseline, or an earlier step blew up) must not be labelled
|
|
319
|
+
# one — its outcome is "skipped", not "failure".
|
|
320
|
+
BASELINE="\${{ steps.baseline.outputs.exists }}"
|
|
321
|
+
GATE_STATE="\${{ steps.gate.outcome }}"
|
|
322
|
+
if [ "$BASELINE" = "false" ]; then
|
|
323
|
+
VERDICT="NO BASELINE ✅ — first PR introducing the spec; future PRs will gate"
|
|
324
|
+
elif [ "$GATE_STATE" = "success" ]; then
|
|
325
|
+
VERDICT="PASS ✅"
|
|
326
|
+
elif [ "$GATE_STATE" = "failure" ]; then
|
|
327
|
+
VERDICT="FAIL ❌ (regression vs base)"
|
|
328
|
+
else
|
|
329
|
+
VERDICT="NOT RUN ⚠️ — an earlier step failed before the gate (infra, not a regression)"
|
|
330
|
+
fi
|
|
331
|
+
BODY="$(printf '## crewhaus eval gate: %s\\n\\n| run | pass_rate | mean_score |\\n| --- | --- | --- |\\n| base (%s) | %s |\\n| PR | %s |\\n\\n%s\\n\\n_seed %s · concurrency %s · gate: any pass-rate drop or per-sample pass→fail flip fails_\\n' \\
|
|
332
|
+
"$VERDICT" "\${{ github.base_ref }}" "$BASE_ROW" "$PR_ROW" "$DIFF_LINE" "$EVAL_SEED" "$EVAL_CONCURRENCY")"
|
|
333
|
+
gh api "repos/\${{ github.repository }}/issues/\${{ github.event.pull_request.number }}/comments" \\
|
|
334
|
+
-f body="$BODY" \\
|
|
335
|
+
|| echo "comment skipped (read-only token — fork PR?)"
|
|
336
|
+
|
|
337
|
+
- name: Fail the check on gate failure
|
|
338
|
+
if: steps.gate.outcome == 'failure'
|
|
339
|
+
run: |
|
|
340
|
+
echo "eval gate failed — the PR spec regressed against \${{ github.base_ref }}"
|
|
341
|
+
exit 1
|
|
342
|
+
`;
|
|
343
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Bundle } from "@crewhaus/ir";
|
|
2
|
+
import { type ShapeAssertion } from "@crewhaus/smoke-harness";
|
|
3
|
+
export type CheckStepName = "assertion" | "install" | "boot";
|
|
4
|
+
export type CheckStepStatus = "ok" | "gated" | "skipped" | "failed";
|
|
5
|
+
export type CheckStep = {
|
|
6
|
+
readonly step: CheckStepName;
|
|
7
|
+
readonly status: CheckStepStatus;
|
|
8
|
+
readonly detail?: string;
|
|
9
|
+
};
|
|
10
|
+
export type CheckRunResult = {
|
|
11
|
+
readonly exitCode: number;
|
|
12
|
+
/** True when the liveness window elapsed and we killed the process (daemons). */
|
|
13
|
+
readonly timedOut: boolean;
|
|
14
|
+
readonly stdout: string;
|
|
15
|
+
readonly stderr: string;
|
|
16
|
+
};
|
|
17
|
+
export type CheckStepRunner = (opts: {
|
|
18
|
+
readonly argv: readonly string[];
|
|
19
|
+
readonly cwd: string;
|
|
20
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
21
|
+
readonly timeoutMs?: number;
|
|
22
|
+
}) => Promise<CheckRunResult>;
|
|
23
|
+
/** Credential-free child env: only {@link SCRUB_PASSTHROUGH_VARS} survive. */
|
|
24
|
+
export declare function scrubbedEnv(baseEnv: NodeJS.ProcessEnv): Record<string, string>;
|
|
25
|
+
export declare function emptyEnvFile(): string;
|
|
26
|
+
/**
|
|
27
|
+
* The exact argv `compile --check` boots a bundle entrypoint with. Exported
|
|
28
|
+
* so the F4 regression tests exercise the REAL construction (flag presence
|
|
29
|
+
* with an injected runner, and .env invisibility with a live subprocess).
|
|
30
|
+
*/
|
|
31
|
+
export declare function bootArgvFor(entry: string): readonly string[];
|
|
32
|
+
/** Collect the @crewhaus/* packages the emitted bundle imports (sorted, deduped). */
|
|
33
|
+
export declare function collectCrewhausDeps(files: Bundle["files"]): readonly string[];
|
|
34
|
+
/**
|
|
35
|
+
* Minimal manifest for bundles whose emitter ships no package.json.
|
|
36
|
+
* Versions pin to the CLI's OWN version: the @crewhaus/* packages publish in
|
|
37
|
+
* lockstep (scripts/publish-workspace.ts stamps them all with one version),
|
|
38
|
+
* so `<cliVersion>` is exactly the published contract this CLI's emitters
|
|
39
|
+
* were released against — where "latest" would silently verify against
|
|
40
|
+
* whatever shipped since, making the check's verdict depend on the day it
|
|
41
|
+
* runs. "latest" remains only as the fallback when no version is resolvable
|
|
42
|
+
* (a broken installation). (In a dev checkout the pinned publish can lag
|
|
43
|
+
* unreleased emitter features; the boot step then reports the mismatch,
|
|
44
|
+
* which is a real signal, not a false positive.)
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildBundlePackageJson(deps: readonly string[], version?: string): string;
|
|
47
|
+
/** daemon.ts (daemon shapes) beats agent.ts; undefined = nothing bootable here. */
|
|
48
|
+
export declare function resolveBootEntry(files: Bundle["files"]): string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Known first-boot gates: the message a shape prints when it boots far
|
|
51
|
+
* enough to demand its live inputs. Reaching a gate is the liveness signal
|
|
52
|
+
* (`doctor --liveness` semantics — "booting far enough to parse argv IS the
|
|
53
|
+
* signal"). Derived empirically from booting every fixture bundle with a
|
|
54
|
+
* scrubbed env; keep patterns narrow so structural breakage stays red.
|
|
55
|
+
*/
|
|
56
|
+
export declare const BOOT_GATE_PATTERNS: ReadonlyArray<{
|
|
57
|
+
readonly pattern: RegExp;
|
|
58
|
+
readonly gate: string;
|
|
59
|
+
}>;
|
|
60
|
+
export declare function classifyBootOutcome(result: CheckRunResult): {
|
|
61
|
+
status: "ok" | "gated" | "failed";
|
|
62
|
+
detail: string;
|
|
63
|
+
};
|
|
64
|
+
/** Fold the steps into the single green/red verdict line. Only "failed" is red. */
|
|
65
|
+
export declare function buildVerdict(steps: readonly CheckStep[]): {
|
|
66
|
+
green: boolean;
|
|
67
|
+
line: string;
|
|
68
|
+
};
|
|
69
|
+
export declare const defaultCheckRunner: CheckStepRunner;
|
|
70
|
+
export type CompileCheckOptions = {
|
|
71
|
+
readonly target: string;
|
|
72
|
+
readonly bundle: Bundle;
|
|
73
|
+
/** Absolute out-dir the bundle was just written to. */
|
|
74
|
+
readonly outDir: string;
|
|
75
|
+
/** Test seam — defaults to the shipped SHAPE_ASSERTIONS. */
|
|
76
|
+
readonly assertions?: readonly ShapeAssertion[];
|
|
77
|
+
/** Test seam — defaults to a real Bun.spawn runner. */
|
|
78
|
+
readonly runner?: CheckStepRunner;
|
|
79
|
+
readonly bootTimeoutMs?: number;
|
|
80
|
+
/** Upper bound for the `bun install` step (default 120s) — see F5a. */
|
|
81
|
+
readonly installTimeoutMs?: number;
|
|
82
|
+
/** Base env the scrubbed child env (PATH/HOME + proxy/CA vars) is drawn from. */
|
|
83
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
84
|
+
};
|
|
85
|
+
export type CompileCheckResult = {
|
|
86
|
+
readonly green: boolean;
|
|
87
|
+
readonly line: string;
|
|
88
|
+
readonly steps: readonly CheckStep[];
|
|
89
|
+
};
|
|
90
|
+
export declare function runCompileCheck(opts: CompileCheckOptions): Promise<CompileCheckResult>;
|