Glubean
Use this skill as a Glubean best-practice guide. Default to the real project workflow. Scratch mode is an extension quick demo path, not the normal agent path.
Modes
- Docs: product questions, concepts, comparison, migration, editor support, or cloud features.
- Onboarding: no Glubean project yet; the user needs extension, MCP, cookbook, or project init guidance.
- Project: the user is already in a Glubean project, or clearly wants to do real project test work, including migration from existing API assets.
Route first
Before choosing a mode:
- Check for
package.json,config/,schemas/,contracts/,tests/,explore/,.env,.env.secrets, andGLUBEAN.md. - Check whether
@glubean/sdkis already present in dependencies or devDependencies. - Check whether MCP tools are available.
- If
GLUBEAN.mdexists, read it first as the project's context file. - Users should keep key context there, including pointers to relevant code, docs, specs, and sibling workspaces.
- If the user explicitly provides additional context locations, read those too before guessing from the API alone.
Then route by intent plus environment:
- Docs: explanation only, with no active project task. Read references/docs-mode.md.
- Onboarding: no Glubean project yet, and the user wants to get set up properly. Read references/onboarding.md.
- Project: the repo already looks like a Glubean project, or the user wants real test work in one. Read references/project-mode.md.
- Implementation-derived verification: the behavior already exists and the user asks for tests, contracts, coverage, or verification. Read references/implementation-derived-verification.md before choosing
contract(),workflow(), ortest()output. - Design-first / contract-first: the user wants to define new behavior before implementation or plan a new API surface. Route to references/contract-first.md immediately, then read references/patterns/contract-first.md when writing the actual contracts.
Intent examples
- Docs
- "What is Glubean?"
- "How does Glubean compare to Postman?"
- "How do I run tests in CI?"
- "How do I migrate from Postman?" (answer from patterns/migration.md)
- Onboarding
- "Set up Glubean for my project"
- "I want to try Glubean"
- "Configure MCP for Cursor"
- Migration (cross-cutting — execution requests, ask before routing)
- "Migrate our Postman collection"
- "Convert our Apifox or OpenAPI export"
- "Port these old API tests into Glubean"
- → Ask: "Set up a new Glubean project for this, or add to the current one?"
- → New project → Onboarding → init → migration pattern
- → Current project → Project → test-after → migration pattern
- Project — simple mode (default, test-after)
- "Write smoke tests for /users"
- "Improve my test coverage"
- "Fix this failing test"
- "Add auth boundary tests"
- → For existing behavior, read
implementation-derived-verification.md; usetest()inexplore/ortests/for the resulting imperative evidence
- Cloud diagnosis
- "Why did this Glubean run fail?"
- "Diagnose this Cloud run: clr_..."
- "Fetch failures for this uploaded CI run"
- → Read references/patterns/cloud-diagnosis.md before pulling Cloud run data
- Project — implementation-derived verification
- "Write tests for this existing endpoint"
- "Generate contracts from this API implementation"
- "Improve coverage without making me list every case"
- → Inspect route/controller, validation, auth, domain/service branches, persistence constraints, error mapping, and existing tests
- → Derive a behavior/case matrix; do not default to a single happy-path or status-only case unless the user explicitly asks for smoke
- → Express stable promises as contracts, lifecycle promises as
workflow(), and imperative evidence astest()
- Project — contract-first mode (advanced, new structured spec)
- "I need a users API with CRUD"
- "Design the billing endpoint before I implement it"
- "Write contracts for an API we have not implemented"
- "What's my contract coverage?"
- "Generate a projection report"
- → Treat this as design-first only when defining new behavior; read
contract-first.mdbefore simple-mode defaults - → Use
contract.http.with()for endpoint promises andworkflow()for lifecycle promises - → Only if user explicitly asks for contracts, or project already has
contracts/
- Project — load testing (opt-in; performance under concurrency)
- "Load test /checkout at 100 concurrent users"
- "What's the p95 / throughput under load?"
- "Add a performance gate to CI"
- → Read references/load-testing.md; author a
*.load.tswithloadScenario()+loadRunner()+ thresholds, run withglubean load <file|dir|glob>and add--upload/--upload-targetonly when the user intentionally wants Cloud upload - → Opt-in only — it generates real concurrent traffic; never auto-write load tests during routine functional coverage
- Project — browser and Chrome extension testing
- "Test my unpacked extension's options page, content script, or Side Panel"
- "Prove the toolbar action opens and closes the native Side Panel"
- → Read references/patterns/browser.md; use
createExtensionTest()for extension-backed pages and keep direct document rendering proof separate from the native toolbar-action lifecycle
Global rules
Apply these unless project-specific instructions override them:
- Secrets go in
.env.secrets; public config goes in.env. For multi-environment setup, read references/patterns/multi-env.md. - Use
configure()to create shared HTTP clients, then use the exported client (e.g.api) in tests — notctx.http.ctx.httpis only for scratch demos. - Use
{{KEY}}for env and secret interpolation, bare strings for literals. - Put tags on every test.
- Use builder mode when a
test()needs teardown, multi-step state passing, runtime branching, or runtime polling. - Use kebab-case test IDs, unique across the project.
- Treat every human-readable test string as diagnostic surface area. Case titles, test names, step names, schema labels, assertion messages, warnings, and logs should explain the invariant or business rule being checked. Avoid generic labels like "works", "valid", "check response", or "should pass".
- When using
ctx.assert, do not omit the message. Make the message specific enough to diagnose the failure, and pass{ actual, expected }when the assertion compares runtime values. Preferctx.expect(...)matchers when they already produce structured actual/expected output. - In real projects, keep reusable response types in
types/. Inline them only for tiny throwaway examples the user explicitly asked for. - Do not use
.json<any>(). If response shape is still unknown, start with.json<unknown>(). UseRecord<string, unknown>only when you already know the top-level value is an object. Then narrow to a real type or Zod as soon as fields are known. - In real projects, keep reusable Zod schemas in
schemas/. Inline them only for tiny throwaway examples the user explicitly asked for. - Use
test.eachandtest.pickonly when every case exercises the same endpoint or the same operation pattern. If endpoints are unrelated, write separate exported tests. Test IDs must include a$fieldor$_pickplaceholder so each case gets a unique ID at runtime (e.g."search-$q","user-$_pick"). - The first time a target needs auth and none is configured yet, STOP and get the auth details from the owner before writing the base auth config. The trigger is the target has an auth boundary and no confirmed auth config covers it yet — a
401/403, anAuthorization/Cookieheader, a login/token endpoint, a protected route, or asecurityscheme in an OpenAPI/spec all signal the boundary. Do NOT guess a scheme, and do NOT author tests that skip auth just to "get something running". Present your reading — which strategy (bearer / apiKey / login-exchange), the header and secret names, where the credential comes from, and the evidence for each — then wait for the owner to confirm, correct, or supply the missing pieces. Once an auth config exists for the target (aconfigure()client is already set up, the secret is in.env.secrets, orGLUBEAN.mdrecords the auth decision), REUSE it silently — do not re-ask per contract or per test. Never silently configure auth from a guess. - Do not echo secret values back to the user. In generated code and configs, keep
{{KEY}}placeholders instead of resolved values; when quoting CLI output or response bodies back in chat, strip or maskAuthorizationheaders,Cookie/Set-Cookievalues, and any token-like fields (bearer tokens, API keys, session IDs). If the user shares a redacted-looking string (e.g.sk-****), do not try to un-redact it or guess the full value from context. - If core project structure is missing, do not hand-create the scaffold. Recommend
npx glubean@latest init --no-interactivewhen the target directory/base URL are known, ornpx glubean@latest initwhen the user wants the interactive prompt. - When the user confirms a project-level decision (auth strategy, context location, naming convention, business rule), suggest adding it to
GLUBEAN.mdso future sessions pick it up. - Use
GLUBEAN.mdfor project-level business rules, role/state semantics, and naming decisions. Do not hand-maintain endpoint matrices, status tables, request/response shapes, or case inventories there; those belong incontracts/and should be surfaced via projection or Cloud. - Author lifecycle specs with top-level
workflow().contract.http.with()defines endpoint behavior;workflow()composes existing contract cases withcall()/poll()and typed state;test()remains the imperative runtime escape hatch for cases that are not worth projecting. - Do not put
setup/teardowninside contract cases. Contract cases are semantic promises (description,given,needs,expect,verifyRules,runnability). Runtime setup/cleanup belongs incontract.bootstrap()overlays,defineSession(),workflow().setup/teardown(), ortest()builder setup/teardown depending on the problem. - Keep upload and sync separate.
glubean run --upload/glubean load ... --uploadsend target-scoped evidence.glubean syncsends project-scoped source projections for Specifications/OpenAPI/agent context and does not run tests. - Current Cloud code gates
glubean syncprojection writes withruns:writeeven though the token catalog includescontracts:writefor the intended contracts/sync scope. Teach the live behavior first, and call out the scope mismatch when precision matters. - A contract's authored source is published verbatim for review — never inline a secret in a contract declaration. glubean ≥ 0.10.2
synccaptures each contract'sexport const … = contract.…(…)span as-is (verify bodies included) and uploads it UNREDACTED to Cloud's Specs "Source" view — that's the point: reviewers read the real contract, so nothing inside the declaration is masked. Keep every credential OUT of the span: real secrets in.env.secretsreferenced via{{KEY}}/ctx.vars.require("KEY"), and concrete non-secret values (ids, hostnames) in top-of-file consts referenced by name — both project as symbols, never values. A literal token/cookie/password written inside the contract object WILL be published. - Every contract-case
verify()MUST be accompanied byverifyRules— one entry per assertion, each a complete business statement of what that assertion enforces (e.g."the returned project id matches the active dogfood project", not"id ok").verify()is a runtime closure: projection can only record THAT it exists (hasVerify), so withoutverifyRulesthe review surface shows an opaque "custom assertions run" note and the reviewer must context-switch to the source. Cloud's Specs case card rendersverifyRulesas the case's Verify step, right where the reviewer reads the promise — write them as the reviewer-facing spec of the verify body, and keep them in sync when the assertions change. Averify()withoutverifyRulesis a review-surface gap, same class as atest()without adescription(rule 25). Use the object form ({ id, description, severity }) when a rule needs stable identity or its own severity; bare strings are fine otherwise. - A contract id is the operation's TITLE, not a namespace. Write it as a short descriptive phrase naming what the operation does — the role an OpenAPI
summaryplays:list-projects,get-project,sign-in-with-email,validate-run-upload. NEVER a dot-namespaced keyword stack (platform.projects.list) — the scoped instance name,feature,tags, andendpointalready carry the hierarchy, so a dotted id just restates them and reads like machinery instead of meaning. Keep it kebab-case (rule 6) and unique across the project (when two services expose the same operation, disambiguate descriptively:platform-health-checkvsdashboard-health-check). The id is the contract's IDENTITY — it projects to OpenAPIoperationId, joins run evidence, and keys revision history — so renaming one later orphans its history: title it well the first time. - The review surface, in priority order: the test
description(#1, most important), then each assertionmessage(#2) — both must be complete, never shorthand. Cloud's Specs view projects a test for review WITHOUT running it: only the testdescription, the assertion messages, thectx.when/switch/whilebranchdescribelabels,tags, and endpoints are shown — runtime values are NOT projected (those live in run evidence, correlated bytestId). So:descriptionis the headline a reviewer reads first — give EVERY test one, a complete one-line statement of what the whole test verifies ("Authenticate; verify the session contract on success and the rejection contract on failure"), never thetestIdrestated, never "tests login", never empty.- Each assertion
messageis the per-check statement and the ONLY thing carrying what that check verifies. Every assertion must have a message that is a full, self-contained statement of the OUTCOME — reviewable on its own, with zero abbreviation: "a valid session token is returned", not "has token"; "a missing order returns 404, not a 500", not "404 ok". A terse label, a bare restatement of code, or an empty message makes the test un-reviewable. Preferctx.expect(...).matcher(expected, "message")over a barectx.assert(cond, "message")where a matcher fits (the matcher name itself —expect.toBe,expect.toContain,expect.toHaveProperty— projects as semantic shape). Usectx.when/switch/while(each with adescribe) over bareif/for/whileso EVERY outcome's intent is projected, not just the one arm execution happened to take.
.track("METHOD /path")rawctx.httpcalls so they review by ENDPOINT. Cloud's Specs view is organized by endpoint (the API surface): each endpoint groups its contract, the tests that hit it, and the workflows that call it — and flags coverage gaps. A test that runs a CONTRACT case is linked automatically (the case implies the endpoint). But a rawctx.http.get(/users/${id})projects its LITERAL url (/users/123), which fragments the coverage index away from the contract'sGET /users/:idand inflates the endpoint list with one-off paths. So whenever you use rawctx.http(the scratch/escape-hatch path — rule 2 prefers a configured client / a contract case), pin the canonical endpoint:await ctx.http.get(/users/${id}).track("GET /users/:id"). Use the SAMEMETHOD /pathstring the contract documents, so the test lands on the same endpoint entry. (If a literal URL is already canonical, e.g.GET /users,.track()is optional.)- Declare a contract's FULL request + response shape, not just the status. Cloud renders each contract as an OpenAPI operation (the published api-reference view): path/query/header parameters, request body, and responses — every field with its constraint pills (
format,enum,minLength/maximum,required) and a per-fielddescription. A contract that only declaresexpect: { status }projects an almost-empty operation — un-reviewable. So on a contract that writes or reads a body, declare the shape with CONSTRAINTS:- request body + request headers at the contract level:
request: { body: <SchemaLike>, headers: <SchemaLike>, contentType }(a barerequest: <SchemaLike>is body shorthand). The body schema is the source of the OpenAPIrequestBody. - query + path params per case:
query: { page: { value, schema, description, required } }andparams: { id: { value, schema, description } }(aParamValueobject — not a bare string — is what carries the schema + description; path params are auto-required). - response body + response headers per case:
expect: { status, schema: <SchemaLike>, headers: <SchemaLike> }. Use a realSchemaLike({ safeParse, toJSONSchema }returning a JSON Schema withformat/enum/bounds/description, or a zod schema) — a type-only placeholder ({} as SchemaLike<T>) projects NOTHING (emptyrequestBody). The schema's constraints ARE the contract a reviewer reads.
- request body + request headers at the contract level:
- Every OPAQUE workflow node needs a real
description.compute/actionnodes projectopaque(their transform/IO logic can't be statically shaped), so the nodedescriptionis the ONLY thing a reviewer can read to understand the step — and one opaque node makes the whole workflow a PARTIAL projection. Never leave an opaque node as a bare two-word name: pass{ id, description }instead of a string id —b.compute({ id: "mark-seed", description: "Mark the run as needing a seed user (sets state.seed=true) so a later step provisions the first account." }, (s) => ...). The description is the partial projection's way to recover the semantics the shape can't. - Gate every project on
typecheck+lint, not just a green run.glubean runexecutes viatsx— it does NOT typecheck, so a contract can run green whiletscrejects it. Andtschas NO deprecation diagnostic at all (@deprecatedis only an editor strikethrough) — the only CLI/CI way to catch it is ESLint's@typescript-eslint/no-deprecated. Glubean ships Zod 4, whose deprecated surface (.passthrough(),z.string().uuid()/email()/datetime()) is exactly what Zod-3-era code uses, so projects silently rot. So: ensure the project hasnpm run typecheck(tsc) ANDnpm run lint(eslint no-deprecated), and run BOTH before "done", reporting each exit 0 alongside the run. If a project lacks them — including olderglubean initoutput — retrofit the gates (tsconfig + eslint.config.js + scripts + devDeps). Writeverifyasasync verify(ctx, res: T)and use Zod 4 forms (z.email()/z.uuid()/z.iso.datetime(),.loose()). Full recipe + retrofit steps: references/patterns/type-safety.md. - For existing behavior, derive verification from the implementation by default. Read references/implementation-derived-verification.md before authoring tests or contracts. Inspect the accessible source and existing tests, derive meaningful success, validation, auth, state, conflict, and error cases, and map every discovered rule to executed evidence or an explicit gap. A request/response schema or OpenAPI projection documents a constraint but does not prove that the implementation enforces it. Only generate a reachability/status-only case when the user explicitly asks for smoke.
- Order a contract's
casesby ascending response status, happy path first. Cases render in SOURCE order everywhere they are reviewed — Cloud's Specs case card, MCPx-glubean-cases, and the projection — so with 10+ cases a scattered order buries the happy path and the reviewer has to hunt for it. Order cases by ascendingexpect.status(2xx success first, then 4xx climbing:400 → 401 → 403 → 404 → 409 → 422, then 5xx), and within one status, common → extreme (the everyday validation failure before the rare edge). The success case is 2xx, so this always puts it on top. Same review-surface discipline as rules 23–28: the reader should walk the operation's spine — reach it, succeed, then fail in a predictable progression — without scanning the whole block. (When cases have a lifecycle dependency — e.g. adeferredseed a later case reuses — keep the dependency readable; ordering is the default, not a reason to break a needed sequence.) - Use
discoverfor inventory anddoctorbefore Cloud publication. Prefer the package scripts generated byglubean init:npm run discoverwrites the asset catalog;npm run doctorperforms read-only asset plus token/project/scope/target readiness checks;npm run syncexplicitly uses.envand publishes project-scoped source projections;npm run uploadruns the local profile and uploads target-scoped evidence. A successful doctor check does NOT authorize sync/upload — it only proves readiness. Never collapse sync and upload into one operation or publish merely because doctor passed.
For detailed navigation, start with references/index.md.
For migration inside a real project, read references/patterns/migration.md before generating files.
If $ARGUMENTS is provided, treat it as the target endpoint, file, tag, or natural-language test request.