Skip to content
Merged
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
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ If setup reports the org is "already set up locally", do not delete anything to
| Push with new resources | `npm run push -- <org> --allow-new-files` — bypass orphan-YAML gate. **AI agents**: do NOT auto-pass this flag; confirm with the human first (see push section below) |
| Test a call | `npm run call -- <org> -a <assistant-name>` or `-s <squad-name>` |
| Run a simulation suite | `npm run sim -- <org> --suite <name> --target <assistant-name>` |
| Build PR-check payloads offline | `npm run check -- <check> --dry-run` (or `--all`); `--print-payload` writes the JSON |
| Run PR simulation checks | `npm run check -- <check>` (or `--all`); `--dry-run` builds the payloads offline, `--print-payload` writes them |
| Migrate a legacy state file | `npm run migrate` — one-shot, all orgs; required once after upgrading to the hash-store engine |

---
Expand Down Expand Up @@ -904,7 +904,8 @@ npm run validate -- <org> # Lint resources locally (fai
npm run audit -- <org> # Read-only drift detector: orphan YAML, state ghosts, content-identical clusters, sibling base-slugs, dashboard orphans, inline model.tools. Exit 1 on findings.
npm run audit -- <org> --type assistants # Scope audit to a single resource type
npm run sim -- <org> --suite <name> --target <name> # Run a simulation suite against an assistant/squad (exit 0 pass, 1 fail, 3 incomplete; --timeout <min>)
npm run check -- <check> --dry-run # Build a vapi-checks.yml check's inline run payload offline (exit 0 built, 2 config/build error; --print-payload [dir])
npm run check -- <check> # Run a vapi-checks.yml check inline against the local files (exit 0 pass, 1 fail, 2 config/build error, 3 incomplete; --changed-since <ref>, --budget-minutes <n>, --json <path>)
npm run check -- <check> --dry-run # Build the check's inline run payloads offline, no key needed (--print-payload [dir])
npm run rollback -- <org> --to <ISO-timestamp> # Re-apply a snapshot taken before a push
npm run rollback -- <org> --list # List available snapshots

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ Every command works in two modes:
| `npm run rollback` | — | `npm run rollback -- <org> --list` or `--to <ISO>` | Restore from a snapshot in `.vapi-state.<org>.snapshots/` (one is written before every push/apply). |
| `npm run call` | ✅ | `npm run call -- <org> -a <name>` or `-s <squad>` | Start an interactive WebSocket call against an assistant or squad. |
| `npm run sim` | — | `npm run sim -- <org> --suite <name> --target <name> [--timeout <min>]` | Run a simulation suite (or specific simulations) against a deployed assistant/squad. Prints the run link; exits 0 passed, 1 failed, 3 incomplete (timeout, Ctrl-C, missing results). |
| `npm run check` | — | `npm run check -- <check>\|--all --dry-run [--print-payload [dir]]` | Build each `vapi-checks.yml` check target's inline simulation-run payload from the files on disk, with its tools, handoffs, judges and personalities. Offline: no API key, nothing sent. `--print-payload` writes the JSON (default `tmp/check-payloads/`). Exits 0 when every payload builds, 2 on a config or build error. |
| `npm run check` | — | `npm run check -- <check>\|--all [--dry-run] [--changed-since <ref>] [--budget-minutes <n>] [--json <path>]` | Run the `vapi-checks.yml` simulation checks against the files on disk: each target is built inline (tools, handoffs, judges, personalities; tools mocked fail-closed, servers dead-ended) and run in one simulation run per target, so nothing is deployed. Posts `Vapi Evals` commit statuses when run by the PR workflow. `--dry-run` builds the payloads offline (no key, nothing sent; `--print-payload` writes them). Exits 0 passed, 1 failed, 2 config or build error, 3 incomplete (timeout, interrupt, budget, billing). |
| `npm run migrate` | — | `npm run migrate` | One-time, all orgs at once: slim legacy state files to pure `name → uuid` and seed the per-developer `.vapi-state-hash/` baseline store from the old hashes. Required once after upgrading to the hash-store engine — `pull`/`push`/`apply` refuse legacy-shaped state until it runs. Idempotent. |
| `npm run build` | — | — | Type-check the codebase (`tsc --noEmit`). |
| `npm test` | — | — | Run regression tests (`node:test`). |
Expand Down
80 changes: 80 additions & 0 deletions src/check-build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Build every target payload of one check from the files under rootDir.
// Shared by the dry run and the live run, so both test the same body.

import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import type { CheckDefinition, CheckTarget } from "./check-config.ts";
import type { CheckPayloadResult } from "./check-payload.ts";
import { checkPayloadBuild } from "./check-payload.ts";
import { promotionBindingsResolve, promotionStateParse } from "./promotion.ts";
import { orgResourcesRead } from "./resource-parse.ts";
import type { StateFile } from "./types.ts";

export interface CheckJob {
check: CheckDefinition;
target: CheckTarget;
// `<check> / <type>/<id>`
label: string;
result: CheckPayloadResult;
}

export function checkTargetLabel(target: CheckTarget): string {
return `${target.type}/${target.id}`;
}

function stateRead(root: string, org: string, warnings: string[]): StateFile {
const path = join(root, `.vapi-state.${org}.json`);
if (!existsSync(path)) {
warnings.push(
`no .vapi-state.${org}.json: references by UUID and credential bindings can't resolve`,
);
return promotionStateParse("{}");
}
return promotionStateParse(readFileSync(path, "utf8"));
}

export async function checkJobsBuild(
root: string,
check: CheckDefinition,
): Promise<CheckJob[]> {
const job = (target: CheckTarget, result: CheckPayloadResult): CheckJob => ({
check,
target,
label: `${check.name} / ${checkTargetLabel(target)}`,
result,
});
if (!existsSync(join(root, "resources", check.org))) {
return check.targets.map((target) =>
job(target, {
errors: [`resources/${check.org}/ does not exist`],
warnings: [],
bytes: 0,
}),
);
}
const warnings: string[] = [];
const resources = await orgResourcesRead(root, check.org);
const sourceState = stateRead(root, check.org, warnings);
const runState =
check.runOrg === check.org
? sourceState
: stateRead(root, check.runOrg, warnings);
const bindingsResolved = await promotionBindingsResolve(
root,
check.org,
check.runOrg,
sourceState,
);
return check.targets.map((target) => {
const result = checkPayloadBuild({
check,
target,
resources,
sourceState,
runState,
bindingsResolved,
});
result.warnings.unshift(...warnings);
return job(target, result);
});
}
Loading
Loading