diff --git a/README.md b/README.md index 0ea1ae575..1e6aa326f 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,39 @@ -# flows +# relay(Flows) -**We are taking prompting and making it reliable, with natural rails and gates.** +**Step functions for coding agent workflows** -This is the clean-slate build of Relayflows: a durable execution engine that is -competitive with Temporal and Inngest and agentic-leading where they are -structurally blind. A Relayflow is a deterministic script that composes agentic -primitives — an LLM call, an agent, a virtual filesystem, memory, identity, -authorization — into anything from a one-shot pipeline to a resident harness to -an entire application. +Agent Relay is building infrastructure for autonomous agents. A relayflow is a readable step function +that runs on the relay and produces a verifiable artifact or result that can be paused +for human input and resumed from any step wherever needed. It is an agentic pipeline +that can load in any model + harness along with deterministic gates to generate +reliable results. -The constitution is [`docs/RFC-0001-everything-is-a-relayflow.md`](docs/RFC-0001-everything-is-a-relayflow.md). -Nothing in this repo may contradict it; changing it is a human decision. -## Layout +```ts +import { flow } from "@relayflows/surface"; -``` -kernel/ relayflowd — Rust. Journal, scheduler, leases, timers, streams. One binary. -packages/sdk/ TypeScript-first authoring SDK. Compiles specs; speaks the journal protocol. -packages/surface/ @relayflows/surface — the TypeScript flow-authoring contract. -workflows/ The gates. Each gate is a relayflow; the build is orchestrated by relayflows. -docs/ RFC-0001 and design docs. -charter/ The Relayflow Lead. -``` - -## Method - -The rewrite is a program *of* relayflows: every capability ships as a relayflow, -and its acceptance gate is that it supports the real use case it exists for. -Nine gates, in `docs/RFC-0001` §3. Gate 1 first: a relayflow can run — the hello -ladder survives `kill -9` at every boundary. +export default flow("fix-failing-tests", async (f) => { + const result = await f + .run("npm test 2>&1; echo EXIT:$?") + .gate((out) => !out.includes("EXIT:0"), "tests are already green, nothing to fix"); -Private while we build. YC 2026-09-15 runs on this base. + const fix = await f + .agent("fixer", { + task: `The test suite is failing. Diagnose and fix it:\n${result}`, + workspace: "src/**: readwrite", + }) + .gate((r) => r.artifacts.length > 0, "the agent must actually change something"); -## Cloud review swarm - -Every pull request launches the cloud review swarm. Repository administrators -must configure three Actions secrets. The workflow fails during preflight, in -seconds and before submitting a run, when any of them is absent. - -| Secret | What it is | How to obtain it | -|---|---|---| -| `RELAY_WORKSPACE_KEY` | Selects the messaging workspace the swarm runs in. | `agent-relay workspace key --reveal-secrets` | -| `CLOUD_API_ACCESS_TOKEN` | The Cloud **user session** access token. | `agent-relay cloud session --json --reveal-token` after a login dedicated to CI | -| `CLOUD_API_REFRESH_TOKEN` | That session's refresh token. | `~/.agentworkforce/relay/cloud-auth.json`, field `refreshToken`, from the same login | + f.done("success"); +}); +``` -`CLOUD_API_URL` and `CLOUD_API_ACCESS_TOKEN_EXPIRES_AT` are not secret; the -workflow defaults them and either can be overridden with a repository variable -of the same name. +# Use Cases -A workspace key alone cannot run the swarm. `agent-relay cloud run` authenticates -to the Cloud API as a user session and as nothing else: the workspace key is read -only by the resolver that picks a messaging workspace, and `POST -/api/v1/workflows/prepare` — which `--sync-code` requires, and `--sync-code` is -how the swarm receives the PR diff — admits only a browser session or a token -carrying the `cli:auth` scope. Given no session, the CLI opens an interactive -device login that no runner can approve and exits after the grant expires. +Flows can be run locally or in production on our hosted cloud. We're built entire +applications using flows that are stacked to run in a sequence with review gates that +can run autonomously over days and weeks. Every agent session is observable and replayable. -**These tokens expire, and this is a stopgap.** A CLI login mints a 24-hour -access token backed by a 90-day refresh token, and every refresh rotates the -refresh token server-side — invalidating the copy held in the secret, which a -job cannot write back. Expect to re-mint `CLOUD_API_ACCESS_TOKEN` and -`CLOUD_API_REFRESH_TOKEN` roughly daily until Cloud can issue a long-lived, -non-refreshing CI token that carries `cli:auth` (the existing CI deployment -tokens carry only `deployments:ci:*` and cannot launch a workflow). +- Cloud pipeline to use agents to generate a social media post. The pipeline coordinates agents who do research, verify the post, check for authenticity, generate graphics, and gate on a human approval — [`examples/social-post-pipeline/`](examples/social-post-pipeline/) +- Pull request review pipeline with different agents looking at the pull request from different angles (security, optimization etc) and agents communicate when needed to reach consensus — [`examples/pr-review-pipeline/`](examples/pr-review-pipeline/) +- Dependency upgrade bot: deterministic check flags a dependency out of date which fires an agent who does the upgrade in a sandbox. This upgrade is gated on another agent verifying the entire application with computer use in another sandbox. If completely verified a pull request is opened up — [`examples/dependency-upgrade-bot/`](examples/dependency-upgrade-bot/) diff --git a/examples/README.md b/examples/README.md index 1bccde44b..271d986cc 100644 --- a/examples/README.md +++ b/examples/README.md @@ -8,3 +8,11 @@ consumers that tell gate-1 SDK work what `@relayflows/surface` must export. | Example | What it shows | |---|---| | [`research/`](research/) | Fan-out to three model lanes (Claude, Codex, Grok), two subagents each, one synthesis; postfix gates on the workspace; dynamic input. First real run: `research/runs/2026-09-02-agent-memory/`. | +| [`social-post-pipeline/`](social-post-pipeline/) | Research → draft → adversarial fact-check → graphic, gated on a human approval before anything publishes. Written directly against the real `@relayflows/surface` package. | +| [`pr-review-pipeline/`](pr-review-pipeline/) | Security/correctness/performance reviewer agents fan out in parallel, gated on writing their findings, then a consensus agent reconciles disagreement between them. | +| [`dependency-upgrade-bot/`](dependency-upgrade-bot/) | A deterministic check flags an outdated dependency; one agent upgrades it in a sandbox, a second, independent agent verifies the whole app with computer use in a separate sandbox before a PR opens. | + +The last three are written directly against the real `@relayflows/surface` +package (`npm --prefix packages/surface run typecheck:examples`) rather than +against local shims — they typecheck today but don't run yet; each one's +README says exactly what's real and what gate work it's waiting on. diff --git a/examples/dependency-upgrade-bot/README.md b/examples/dependency-upgrade-bot/README.md new file mode 100644 index 000000000..60e51ae07 --- /dev/null +++ b/examples/dependency-upgrade-bot/README.md @@ -0,0 +1,49 @@ +# dependency-upgrade-bot + +**Like I'm 5:** A checklist notices a library is out of date. A robot tries +upgrading it, but only in its own sandboxed corner where it can't break +anything real. A *second*, completely separate robot — in its own sandbox +that can't see the first robot's work except the summary — boots the +upgraded app and actually clicks through it like a real person would, to +make sure nothing broke. Only if that second robot is fully satisfied does +a pull request get opened. The upgrader saying "it works" counts for +nothing on its own. + +## The shape + +``` +npm outdated (deterministic, gated) → upgrade (agent, sandbox A) → verify with computer use (agent, sandbox B, gated) → open PR (deterministic, gated) → done +``` + +`dependency-upgrade-bot.flow.ts` is written against the real +`@relayflows/surface` package. The point of this example is the +**independence** of the two agent steps: + +- The deterministic first gate refuses to even start an upgrade if nothing + is actually outdated — this isn't an agent's judgment call, it's + `npm outdated`'s own exit data. +- `upgrader` and `verifier` are given **different workspaces** + (`sandbox/upgrade` vs `sandbox/verify`). The verifier is deliberately never + told to trust the upgrader's summary of what it did — its task tells it to + boot the app and drive it itself. +- Both agent gates check for a **file the agent wrote** + (`sandbox/upgrade/CHANGES.md`, `sandbox/verify/PASSED`), not a string in + its response — an agent that says "all good!" without writing the marker + fails closed, same lesson as the other two examples in this directory. +- The PR only opens after the verifier's gate passes, and the final gate + checks that a real PR URL came back — not just that the `gh` command + exited 0. + +## Status: typechecks, does not run yet + +```sh +cd packages/surface && npm run typecheck:examples +``` + +- `f.agent(...)` builds a real step but parks without a worker attached, + same as every other example in this repo today. +- **The sandbox isolation is declared, not enforced.** RFC-0001 Appendix A + rule 1 (workspace-scoped permissions) is gate-8 kernel work; today nothing + stops the `upgrader` step from reading `sandbox/verify/` if the underlying + CLI isn't sandboxed itself. This flow is written so that the moment gate 8 + lands, "separate sandbox" starts meaning it. diff --git a/examples/dependency-upgrade-bot/dependency-upgrade-bot.flow.ts b/examples/dependency-upgrade-bot/dependency-upgrade-bot.flow.ts new file mode 100644 index 000000000..cdaa4b406 --- /dev/null +++ b/examples/dependency-upgrade-bot/dependency-upgrade-bot.flow.ts @@ -0,0 +1,75 @@ +// dependency-upgrade-bot — a v2 relayflow (docs/SURFACE.md dialect). +// +// A deterministic check flags an out-of-date dependency, an agent performs +// the upgrade in its own sandboxed workspace, and a SECOND agent — in a +// SEPARATE sandbox, with no access to the upgrader's workspace — verifies +// the whole application still works using computer use (driving the app the +// way a person would, not just re-running unit tests) before a PR is ever +// opened. The upgrader's own claim of success is worth nothing on its own; +// only the independent verifier's passing artifact opens the PR. +// +// STATUS: typechecks against the real `@relayflows/surface` package (see +// ../tsconfig.json / `npm --prefix packages/surface run typecheck:examples`) +// but does not run yet — `f.agent` parks without an attached worker, and +// there is no kernel-enforced workspace isolation yet (RFC-0001 Appendix A +// rule 1 / gate 8), so today the "separate sandbox" boundary between +// upgrader and verifier is declared, not enforced. + +import { flow } from "@relayflows/surface"; + +export default flow( + "dependency-upgrade-bot", + { budget: "$4/run" }, + async (f) => { + const outdated = await f + .run("npm outdated --json 2>/dev/null || true") + .gate( + (out) => out.trim().length > 0 && out.trim() !== "{}", + "nothing is out of date — no upgrade to attempt", + ); + + const upgrade = await f + .agent("upgrader", { + task: + `One or more dependencies are out of date:\n${outdated}\n\n` + + `Upgrade them, run the test suite, and fix anything the upgrade breaks. ` + + `Write a one-paragraph summary of what changed to sandbox/upgrade/CHANGES.md.`, + workspace: "sandbox/upgrade: readwrite", + }) + .gate( + (r) => r.artifacts.includes("sandbox/upgrade/CHANGES.md"), + "the upgrader must document what it changed", + ); + + // A second agent, in a workspace the upgrader never touches, has to + // independently verify the result before anything ships. It gates on a + // file it wrote after actually driving the app, not on the upgrader's + // own summary. + const verification = await f + .agent("verifier", { + task: + `Read sandbox/upgrade/CHANGES.md. In this sandbox, install the ` + + `upgraded dependencies and boot the application. Using computer use, ` + + `click through the application's key flows the way a real user would. ` + + `If — and only if — everything works, write sandbox/verify/PASSED. ` + + `Otherwise write sandbox/verify/FAILED with exactly what broke.`, + workspace: "sandbox/verify: readwrite", + }) + .gate( + (r) => r.artifacts.includes("sandbox/verify/PASSED"), + "the upgrade only ships once an independent sandbox verifies it end to end", + ); + + const pr = await f + .run( + 'gh pr create --title "Dependency upgrade (verified)" ' + + "--body-file sandbox/verify/PASSED", + ) + .gate( + (out) => /https:\/\/github\.com\/.+\/pull\/\d+/.test(out), + "must actually open a PR, not just report success", + ); + + f.done("success"); + }, +); diff --git a/examples/pr-review-pipeline/README.md b/examples/pr-review-pipeline/README.md new file mode 100644 index 000000000..4860d4858 --- /dev/null +++ b/examples/pr-review-pipeline/README.md @@ -0,0 +1,44 @@ +# pr-review-pipeline + +**Like I'm 5:** Instead of one reviewer reading your whole pull request, +three little reviewers each look for one thing — one only checks for +security holes, one only checks for logic bugs, one only checks for slow +code. They all work at the same time. Then a fourth robot reads what all +three wrote and specifically looks for fights — like one saying "this is +fine" and another saying "this is dangerous" about the exact same line. It +doesn't just let one opinion quietly win; it writes down that they disagree. + +## The shape + +``` +git diff → [security, correctness, performance] reviewers (parallel, gated) → consensus (agent, gated) → done +``` + +`pr-review-pipeline.flow.ts` is written against the real +`@relayflows/surface` package. It mirrors a real, shipping product's actual +mechanism — My Senior Dev's multi-agent PR review — in two layers: + +- **Fan-out by lens, not by "review everything."** Each reviewer is told to + look at exactly one dimension and ignore the rest, and is gated on writing + its findings to its own file (`review/.json`) — even "no issues + found" has to be written down, so a reviewer can't pass by staying silent. +- **Reconciliation, not aggregation.** The `consensus` step's task is + explicitly to find *disagreement* between lenses on the same spot in the + diff and resolve or flag it — the same thing My Senior Dev's real + coordination engine does (`hasOppositeFixDirection`, + `looksLikeFalsePositiveDispute`) rather than just concatenating three + reports into one. + +Unlike the other two examples in this directory, this one never calls +`f.human` — nothing here needs it to make sense as a demonstration. + +## Status: typechecks, does not run yet + +```sh +cd packages/surface && npm run typecheck:examples +``` + +`f.agent(...)` builds a real step but parks without a worker attached, same +as every other example in this repo today. Everything else in this flow — +the fan-out, the gates, the reconciliation step — is otherwise ordinary use +of the shipped `@relayflows/surface` contract. diff --git a/examples/pr-review-pipeline/pr-review-pipeline.flow.ts b/examples/pr-review-pipeline/pr-review-pipeline.flow.ts new file mode 100644 index 000000000..7b41e7ac5 --- /dev/null +++ b/examples/pr-review-pipeline/pr-review-pipeline.flow.ts @@ -0,0 +1,77 @@ +// pr-review-pipeline — a v2 relayflow (docs/SURFACE.md dialect). +// +// Multiple review agents look at the same diff from different angles — +// security, correctness, performance — in parallel, each gated on actually +// writing findings rather than just talking. A separate reconciliation agent +// then reads every lens's findings and resolves (or flags) disagreement +// between them: the same two-layer shape My Senior Dev's real multi-agent +// PR review uses — cheap fan-out first, then a reconciliation pass that +// looks specifically for conflicting verdicts rather than just concatenating +// opinions. +// +// STATUS: typechecks against the real `@relayflows/surface` package (see +// ../tsconfig.json / `npm --prefix packages/surface run typecheck:examples`) +// but does not run yet — `f.agent` parks without an attached worker. Unlike +// the other two examples, this one never calls `f.human`, so nothing here +// depends on that verb being wired up. + +import { flow } from "@relayflows/surface"; + +const LENSES = ["security", "correctness", "performance"] as const; +type Lens = (typeof LENSES)[number]; + +export interface PrReviewInput { + /** e.g. "origin/main...HEAD", or a PR's merge-base range. */ + diffRange: string; +} + +function findingsPath(lens: Lens): string { + return `review/${lens}.json`; +} + +export default flow( + "pr-review-pipeline", + { budget: "$3/run" }, + async (f, input) => { + const diff = await f + .run(`git diff ${input.diffRange}`) + .gate((out) => out.trim().length > 0, "nothing to review — the diff is empty"); + + await Promise.all( + LENSES.map((lens) => + f + .agent(`${lens}-reviewer`, { + task: + `Review this diff for ${lens} issues ONLY — ignore everything else. ` + + `Write every finding, or an explicit "no issues found", to ` + + `${findingsPath(lens)}.\n\n${diff}`, + workspace: "review/: readwrite", + }) + .gate( + (r) => r.artifacts.includes(findingsPath(lens)), + `the ${lens} reviewer must write ${findingsPath(lens)}, even to report nothing`, + ), + ), + ); + + // Reconciliation, not aggregation: this agent's job is to find the + // places two lenses disagree about the same spot in the diff (one says + // "fine", another says "block") and resolve or flag it, rather than let + // whichever verdict is read first silently win. + const consensus = await f + .agent("consensus", { + task: + `Read ${LENSES.map(findingsPath).join(", ")}. Where two reviewers ` + + `reached opposite verdicts on the same spot in the diff, resolve it ` + + `or mark it UNRESOLVED with both positions. Write your reconciled ` + + `verdict to review/consensus.json.`, + workspace: "review/: readwrite", + }) + .gate( + (r) => r.artifacts.includes("review/consensus.json"), + "the consensus step must write review/consensus.json", + ); + + f.done("success"); + }, +); diff --git a/examples/social-post-pipeline/README.md b/examples/social-post-pipeline/README.md new file mode 100644 index 000000000..04ff0fa96 --- /dev/null +++ b/examples/social-post-pipeline/README.md @@ -0,0 +1,52 @@ +# social-post-pipeline + +**Like I'm 5:** A brand wants a social media post. One robot researches the +topic and writes down facts with sources. Another robot writes the post +using only those facts. A third robot's *only* job is to catch the writer +lying — it checks every claim against the research and refuses to say +"PASSED" unless every single one holds up. A fourth robot makes the picture +to go with it. Then a person looks at everything and decides yes or no +before it actually goes out. No robot gets to hit publish by itself. + +## The shape + +``` +research (agent) → write draft (agent) → fact-check (agent, gated) → make graphic (agent) → human approval → done +``` + +`social-post-pipeline.flow.ts` is written against the real +`@relayflows/surface` package (`docs/SURFACE.md` dialect). The interesting +part isn't the agents — it's what each `.gate()` checks: + +- Every gate reads a **file the agent wrote** (`research/notes.md`, + `drafts/post.md`, `drafts/fact-check.passed`, `drafts/graphic.png`), never + a substring of what the agent said. `examples/research/README.md` already + documents why: *"substring gates on model output are fail-open"* — an + agent that talks about doing the work isn't the same as an agent that did + it. +- The fact-checker is deliberately adversarial to the writer, the same + pattern `customer-agents/native`'s real "Autopilot" flow uses in + production: a second agent whose whole job is to distrust the first one, + gated on a file, so a hallucinated claim can't slip through just because + it reads smoothly. +- The human gate (`f.human`) is the last word. Everything upstream can pass + every gate and the flow still won't publish without a yes. + +## Status: typechecks, does not run yet + +```sh +cd packages/surface && npm run typecheck:examples +``` + +- `f.agent(...)` builds a real step, but parks without a worker attached — + same as every other example in this repo today. +- `f.human(...)` currently **throws** `unsupported_verb` in the SDK's + authored-flow executor (`packages/sdk/src/authored-flow-executor.ts`). The + human-approval gate is declared surface, not yet wired to any execution + path — it's sequenced behind gates 2-4 and the `f.human` channel-delivery + work tracked for gate 6 (`ops/BACKLOG.md`). + +This flow documents the intended shape so that when `f.human` and the agent +worker land, this example starts working with no rewrite — just like +`docs/SURFACE.md` §1 promises: *"a simple flow becomes a harness by +accretion, never a rewrite."* diff --git a/examples/social-post-pipeline/social-post-pipeline.flow.ts b/examples/social-post-pipeline/social-post-pipeline.flow.ts new file mode 100644 index 000000000..c0fb3532f --- /dev/null +++ b/examples/social-post-pipeline/social-post-pipeline.flow.ts @@ -0,0 +1,96 @@ +// social-post-pipeline — a v2 relayflow (docs/SURFACE.md dialect). +// +// The "generate a social post" use case: a small swarm of agents that +// research a topic, draft a post, fact-check it against the research (not +// against its own opinion), and design a graphic — then a human decides +// whether it actually goes out. No agent in this flow is trusted to publish +// on its own word; the fact-checker's gate reads a file it wrote, not a +// substring of what it said — the same lesson examples/research/README.md +// documents: "substring gates on model output are fail-open." +// +// STATUS: typechecks against the real `@relayflows/surface` package (see +// ../tsconfig.json / `npm --prefix packages/surface run typecheck:examples`) +// but does not run yet — `f.agent` parks without an attached worker, and +// `f.human` throws `unsupported_verb` in the current authored-flow executor +// (packages/sdk/src/authored-flow-executor.ts). This flow documents the +// intended shape; see README.md for exactly what's real today vs. what gate +// work this is waiting on. + +import { flow } from "@relayflows/surface"; + +export interface SocialPostInput { + brand: string; + topic: string; + /** Who approves before it publishes, e.g. a Slack handle. */ + approver: string; +} + +export default flow( + "social-post-pipeline", + { budget: "$5/run" }, + async (f, input) => { + const research = await f + .agent("researcher", { + task: + `Research current, verifiable facts about "${input.topic}" for ` + + `${input.brand}. Cite a source for every claim. Write your findings ` + + `to research/notes.md.`, + workspace: "research/: readwrite", + }) + .gate( + (r) => r.artifacts.includes("research/notes.md"), + "the researcher must write research/notes.md, not just summarize in chat", + ); + + const draft = await f + .agent("writer", { + task: + `Read research/notes.md and draft one social post for ${input.brand} ` + + `about "${input.topic}". Do not state anything the research does not ` + + `support. Write the draft to drafts/post.md.`, + workspace: "drafts/: readwrite", + }) + .gate( + (r) => r.artifacts.includes("drafts/post.md"), + "the writer must write drafts/post.md", + ); + + // The fact-checker's only job is to distrust the writer. It gates on a + // file it wrote, never on parsing its own prose: an agent that "says" + // PASSED without writing the marker fails closed. + const factCheck = await f + .agent("fact-checker", { + task: + `Check every factual claim in drafts/post.md against research/notes.md. ` + + `If — and only if — every claim is directly supported, write ` + + `drafts/fact-check.passed containing the word PASSED. Otherwise write ` + + `drafts/fact-check.rejected explaining exactly which claim is unsupported.`, + workspace: "drafts/: readwrite", + }) + .gate( + (r) => r.artifacts.includes("drafts/fact-check.passed"), + "the post must pass fact-check before a graphic is even worth making", + ); + + const graphic = await f + .agent("designer", { + task: + `Read drafts/post.md and generate one on-brand graphic for ${input.brand} ` + + `to accompany it. Write it to drafts/graphic.png.`, + workspace: "drafts/: readwrite", + }) + .gate( + (r) => r.artifacts.includes("drafts/graphic.png"), + "the designer must actually produce drafts/graphic.png", + ); + + const approved = await f.human( + `Ready to publish for ${input.brand}:\n\n${draft.summary}\n\n` + + `Fact-check: ${factCheck.summary}\nGraphic: drafts/graphic.png\n\nPublish?`, + { to: input.approver }, + ); + if (!approved) return f.done("canceled"); + + f.done("success"); + }, +); diff --git a/examples/tsconfig.json b/examples/tsconfig.json new file mode 100644 index 000000000..a0acc16ec --- /dev/null +++ b/examples/tsconfig.json @@ -0,0 +1,24 @@ +{ + "//": "OPT-IN typecheck for the examples authored directly against the real @relayflows/surface package (social-post-pipeline/, pr-review-pipeline/, dependency-upgrade-bot/). Not part of `npm test`. Run: cd packages/surface && npm run typecheck:examples. Mirrors regressions/tsconfig.json's path-alias approach.", + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "baseUrl": ".", + "paths": { + "@relayflows/surface": ["../packages/surface/src/index.ts"] + }, + "lib": ["ES2022"], + "strict": true, + "noUncheckedIndexedAccess": true, + "noEmit": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "types": [] + }, + "include": [ + "social-post-pipeline/*.ts", + "pr-review-pipeline/*.ts", + "dependency-upgrade-bot/*.ts" + ] +} diff --git a/packages/surface/package.json b/packages/surface/package.json index 2a6c4acbb..9a8bb900c 100644 --- a/packages/surface/package.json +++ b/packages/surface/package.json @@ -24,6 +24,7 @@ "prepare": "bun run build", "typecheck": "tsc --noEmit", "typecheck:regressions": "tsc -p ../../regressions/tsconfig.json", + "typecheck:examples": "tsc -p ../../examples/tsconfig.json", "test": "bun run build && tsc -p tsconfig.test.json && vitest run" }, "license": "Apache-2.0",