npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@arocnies/fitgherkin

v0.1.0

Published

Check an implementation against a Gherkin feature contract

Readme

FitGherkin

Fit the code to the features: check an implementation against a Gherkin feature contract.

The feature files are a user-defined contract for how the system behaves. fitgherkin binds every scenario step to its step definition with the matching engine the Cucumber runner uses, reads the glue statically, and reports every disagreement, deterministically, with no user code executed.

  • A deterministic gate. Same inputs, same findings, exit 1 on an error. Undefined, ambiguous and stub steps, unused glue, broken contracts. It needs no dependency install and loads no glue, so it runs in milliseconds in a bare sandbox.
  • A way in for brownfield code. A truthful contract fails on day one. draft tags the gap @drift, counts it in every report, and proposes @draft scenarios from the tests and docs a project already has.
  • A contract agents are held to. Agents write more of the glue now. fitgherkin holds them to the features through the same CLI, ships the norms that keep "edit the feature to silence the finding" off the table, and, where configured, asks an agent whether the code truly does what each scenario says. See For AI agents.

Quickstart

npm install --save-dev @arocnies/fitgherkin
npx fitgherkin init
npx fitgherkin

init writes a config, a starter feature with its glue, and an AGENTS.md block carrying the norms. It never overwrites an existing file. In a directory that already has feature files it writes only the config and the norms, in plain cucumber-js style, and points you at draft.

A report names the contract's size, each finding with a hint, and the verdict:

  1 feature · 3 scenarios · 4/5 steps bound · 3 step definitions

  error  static-fit/undefined-step/features/cart.feature:6
    "I should see 1 item" has no matching step definition
    hint: run `fitgherkin glue` for a snippet
    at features/cart.feature:6

✖ 1 error
  providers: scan gherkin-features, typescript-glue · resolve cucumber-expressions · validate static-fit

On a brownfield repository the path to green is three commands:

npx fitgherkin init --agent claude   # or codex; plain init scaffolds the gate without agents
npx fitgherkin draft                 # tags not-yet-fit scenarios @drift, proposes @draft from tests and docs
npx fitgherkin

With --agent claude or --agent codex, the scaffolded config also wires the agent providers around the CLI you are already logged into: agentBehaviorScan mines docs for behavior, agentBehaviorFit judges each gated scenario against the code it runs through, agentSteps turns drafted TODO steps into real Given/When/Then. Every call uses that CLI's login and billing; see agent providers before enabling it in CI.

Requirements. Node >= 22.22.3, because the CLI loads .mts configs with Node's native type stripping. The package depends on typescript for the compiler API that reads glue, which is most of the install weight.

Supported stacks

The deterministic tier reads glue written for cucumber-js (TypeScript and JavaScript), jest-cucumber (ESM or CommonJS) and Serenity/JS Screenplay. Feature files from any stack parse and are checked for structure.

On glue in another ecosystem (godog, behave, pytest-bdd, cucumber-jvm, SpecFlow/Reqnroll, Behat) the deterministic tier says exactly that: one glue-unreadable error naming the stack, and no per-step verdict, because "this step is undefined" would be a guess about a file it never read. The agent tier carries fit on those repositories: init --agent detects the framework, points the behavior scan at that language's step files, and agentBehaviorFit judges each scenario against the Go, Python or Java glue directly, attesting per feature (agent-foreign-glue) that the verdict is a reading rather than verified binding.

A project with a Gherkin contract and no BDD suite at all is served too. The deterministic tier reports one no-glue-found error, and agentBehaviorFit judges each scenario against the implementation it runs through, answering in three states: fits, does not fit, or behavior-unverified. "I could not find the deciding code" and "the code disagrees" are opposite findings, and a tool that collapses them is worth nothing. Verdicts from source are attested agent-source-evidence: nothing executes those scenarios, so the verdict is a reading of the code, not a test result.

Two audiences get the most out of it: teams whose agents write glue, who need a fast gate that executes nothing and norms that stop the agent weakening the features; and jest-cucumber projects, which have no dry-run at all. jest-cucumber's real semantics are modelled statically: per-test-block scoping, exact-match strings, scenarios nobody wrote a test block for.

Commands

npx fitgherkin                    # check (default command); exit 0 fit, 1 errors, 2 usage or config problem
npx fitgherkin --json             # machine-readable PipelineResult; --json=findings for findings + summary only
npx fitgherkin init               # scaffold: config, starter feature, glue, AGENTS.md norms
npx fitgherkin init --agent claude  # + wire the agent providers, install the skill at .claude/skills/fitgherkin
npx fitgherkin draft              # day one: tag not-yet-fit scenarios @drift, propose @draft from tests and docs
npx fitgherkin glue               # a step-definition snippet for every unbound step
npx fitgherkin steps              # the cast and the step vocabulary (reuse before inventing)

Other flags: --config <path>, --style screenplay|plain (init, glue), --out <dir> (draft), --verbose (progress narration on stderr; quiet by default), --version, --help. Unknown flags are hard errors.

glue, steps and draft work with no config at all, on the defaults init pins; only the check requires one, because what green certifies is a declared contract. glue speaks the project's dialect: when jest-cucumber is the only glue present, snippets come as exact-match strings for the right test(...) block. --json carries a schemaVersion; --json=findings drops the large observation and association arrays for consumers that only gate.

Every finding in the report is the fact on one line and, where the fix is not obvious, a hint: line under it, the way a compiler's help text follows an error. The hint is a pointer, not a diagnosis. Errors print first, then warnings, then infos; each rule prints at most 50 errors or warnings and 5 infos before a count line, and --json always carries everything.

Configuration

fitgherkin.config.mts is an ES module, because providers are functions:

import {
  behaviorCoverage,
  cucumberExpressionBinding,
  defineConfig,
  gherkinFeatures,
  implementationSlice,
  importScan,
  staticFitRules,
  testEvidence,
  testTitles,
  typescriptGlue,
} from '@arocnies/fitgherkin'

export default defineConfig({
  version: 1,
  repositoryRoot: '.',
  glueStyle: 'plain',
  scan: [gherkinFeatures(), typescriptGlue(), importScan(), testTitles()],
  resolve: [cucumberExpressionBinding(), implementationSlice(), testEvidence()],
  validate: [staticFitRules(), behaviorCoverage()],
})

All three phase arrays are required; there are no hidden defaults, so the file says exactly what the gate runs. Deterministic and agent providers mix freely in every phase, and each phase's output feeds the next.

| Phase | Question | Built-in providers | | --- | --- | --- | | scan | What features, glue, code and tests exist? | gherkinFeatures, typescriptGlue, jestCucumberGlue, importScan, testTitles, agentBehaviorScan | | resolve | Which glue binds each step, which code does each scenario run through, what proves it? | cucumberExpressionBinding, implementationSlice, agentImplements, testEvidence | | validate | Does the code fit the contract? | staticFitRules, screenplayStepShape, behaviorCoverage, cucumberRunner, agentBehaviorFit |

resolve is a pipeline, and its order is load-bearing. It produces four kinds of association:

  • binding, a step to the definition that will match it. Always deterministic: it must predict what the runner does.
  • implements, a scenario to the source files it runs through, walked along the dependency graph out of the bound glue. This is the scenario's evidence slice, and it is what the semantic tier reads.
  • proves, a test to the scenario it demonstrates, graded executed > bound > reachable > vocabulary. A verdict resting on a test is a stronger claim than one resting on a reading of the implementation, and the report says which it had.
  • covers, an observed behavior to the scenario whose text accounts for it. A claim about the contract's wording, not about the code.

implementationSlice() walks out of what cucumberExpressionBinding() bound; agentImplements() covers what the walk could not reach; testEvidence() comes last, because it grades a test reachable exactly when the test exercises code inside the slice.

Scan providers

  • gherkinFeatures({ include }): feature globs, default **/*.feature anywhere in the tree minus node_modules, vendor and build output. When a narrower glob misses the contract, no-features-found names the files it can see and the glob that would catch them.
  • typescriptGlue({ include }): glue globs, default features/**, any steps/ or step_definitions/ directory, *.steps.ts, and the test/, tests/, e2e/ and cypress/ trees, TS and JS.
  • jestCucumberGlue({ include }): step definitions in jest-cucumber's defineFeature/given/when/then style, with jest-cucumber's own semantics: exact-match strings with <placeholder> captures, definitions scoped to their loadFeature target and their test('…') block, so a scenario re-defining shared steps is idiom rather than ambiguity, and a scenario with no test block is the error the runner would raise.
  • importScan({ roots, ignore }): a deterministic multi-language import crawler (Python, JS/TS, Go, Rust, Java/Kotlin, Ruby, C/C++). Lexical, sub-second on thousands of files. It emits the dependency edges everything else walks; without it there is no evidence slice.
  • testTitles({ include }): one behavior-candidate per test title, traceable to its file:line. Default globs cover .test, .spec, _test, _spec, NestJS's .e2e-spec and .int-spec, and the test/, tests/, spec/ and __tests__/ directories.
  • agentBehaviorScan({ exec, include }) (from @arocnies/fitgherkin/agent): reads docs and code for behavior (default: top-level *.md except changelogs, licences and contribution guides, plus doc/, docs/, spec/, specs/). A spec is behavior even when phrased as instructions to implementers; development-workflow guidance and descriptions of code organization are not, and the prompt says so. The block init wrote into AGENTS.md is blanked before the scan reads the file, because every model lists "exits 1 when the code does not fit" as a behavior of the project otherwise; the rest of the file is scanned as any document. Its candidates feed behaviorCoverage() in scan and the compose chain in draft.discover.

Resolve providers

  • cucumberExpressionBinding({ serenityParameterTypes }): registers approximations of Serenity/JS's {actor} and {pronoun} unless the project defines its own (default true).
  • implementationSlice({ maxDistance, maxFiles }): the implements associations, breadth-first out of the bound glue (defaults 3 hops, 40 files per scenario).
  • agentImplements({ exec }) (agent): maps scenarios onto implementation where no deterministic resolver can reach them. One routing question per feature, answered from paths rather than contents, only for scenarios the walk did not reach, including bound scenarios whose slice never left the glue.
  • testEvidence(): the proves and covers associations.

Validate providers

  • staticFitRules(): the core rules below.
  • behaviorCoverage(): reports what covers and proves say in both directions. uncovered-behavior groups behaviors no scenario accounts for, one finding per file; scenario-unproven names gated scenarios nothing executes. A scenario with glue behind it is a test the runner executes, so the rule speaks only of glue-less scenarios, and stays silent when it could not see what would prove one.
  • screenplayStepShape(): warns when a step body is more than a one-line actor.attemptsTo(...) delegation, and when a Then re-runs a Task (then-step-acts). Screenplay projects only.
  • cucumberRunner({ command, args, tags, timeout }): execution fit. Runs the gated scenarios with the project's own cucumber-js and reads the Cucumber Messages back. Opt-in, because it executes real code; a runner that cannot run is an error, never a silent green.
  • agentBehaviorFit({ exec }) (agent): semantic fit. Asks the agent whether the code does what each gated scenario says, one call per group of scenarios sharing seed files, each group against its own evidence pack: nearest slice files first, oversized files shown as the definitions most relevant to the group, a quarter of the budget reserved for tests, callers of each seed one hop out. An unknown that names what it could not see gets one ask-back with exactly that. Findings carry data.observedGherkin when the agent can state how reality differs.

Agent providers

import { agentBehaviorFit, agentBehaviorScan, agentImplements, agentSteps, claudeCli, codexCli } from '@arocnies/fitgherkin/agent'.

claudeCli() runs claude -p with no user settings, MCP servers or tools; codexCli() runs codex exec ephemeral, user config and rules ignored, read-only sandbox, from a scratch directory so the project's own AGENTS.md never becomes the judge's instructions. Both feed the prompt on stdin. claudeCli({ explore: true }) lets the judge search the repository with read-only tools at the cost of exact reproducibility. Every provider degrades visibly: a CLI that fails is an agent-unavailable finding, and whatever a byte budget kept out of the agent's view is an agent-truncated finding, never a silent thinning of evidence.

Calls run pooled, four in flight by default (concurrency), each announced on stderr before it starts with a count and an estimate. Two environment variables make the tier replayable: FITGHERKIN_AGENT_CACHE=<file> wraps every exec in record/replay through that JSON file, and FITGHERKIN_AGENT_MODEL=<id> pins the model when the config did not. The external evals run the config init --agent writes, unedited, through exactly these.

Drafting a contract from an existing project

fitgherkin draft does two things. It tags every not-yet-fit chosen scenario @drift, so existing Gherkin starts green with the gap in the burn-down. And it proposes new @draft scenarios from observed behavior through a staged pipeline, discover → compose → emit, where any stage can be deterministic or agent-backed:

  • testTitles() and agentBehaviorScan() (discover) turn test titles and documented behavior into evidence-linked candidates.
  • featureSkeletons() (compose) groups them into features and @draft scenarios with a TODO step pointing at the evidence.
  • agentSteps({ exec }) (compose) rewrites the TODO steps into Given/When/Then from a cast and reusable task phrasings it names first. A reply that skips scenarios gets one second ask; what stays unfilled keeps its TODO and is named in DRAFT.md.

Every drafted scenario is @draft with a # drafted from file:line comment anchored to the sentence or heading it came from. A draft restates the implementation, so it is a proposal: promote it by deleting the tag, or swap it to @drift for accepted-not-built. Keep the comment; the check reads it back, and when it names a test file the scenario gets a drafted proves link to that test, in any language. Drafted features land in features/ by default (draft.outputDir, or --out <dir>); the tag marks them, not a folder.

The Cast: declaring Screenplay actors

Gherkin has no syntax for actors, so fitgherkin declares them in the feature description, plain text every parser preserves:

Feature: Payment processing

  Cast:
    Operator — runs payment operations for the store
    Payment Processor — the third-party service settling charges

One actor per line, Name — role. fitgherkin steps lists the cast above the step vocabulary, agentSteps writes the cast it names into drafted features, and two gentle rules follow: unused-actor (declared, never acts) and uncast-actor (acts, not declared). Task.where and Question.about bindings are tracked as the When and Then vocabulary; abilities and interactions stay code-side, because they never appear in the contract.

Rules

| rule | severity | meaning | | --- | --- | --- | | undefined-step | error | a step no definition matches | | pending-step-definition | error | stub glue (empty, throw, return 'pending', description-only Task.where) bound to a gated scenario | | ambiguous-step | error | a step more than one definition matches | | duplicate-step-definition | error | two definitions with the same pattern | | duplicate-parameter-type | error | defineParameterType registered twice for one name | | uncompilable-step-definition | error | a pattern references an unknown {parameterType} | | feature-parse-error | error | invalid Gherkin | | no-features-found | error | the scan matched zero feature files, so nothing was checked; the hint names the files it can see | | no-glue-found | error | steps are undefined and nothing readable defines any; ONE finding, not one per step | | glue-unreadable | error | nothing readable defines any step, but BDD glue for a stack this tier cannot parse is present; ONE finding, no per-step verdict guessed | | scenario-missing-test | error | a scenario in a jest-cucumber feature has no test('…') block | | test-without-scenario | error | a jest-cucumber test('…') block names no scenario | | scenario-failing | error | a gated scenario fails when executed (cucumberRunner) | | runner-unavailable | error | cucumberRunner could not run; never fails open | | unused-step-definition | warning | glue no step in the contract uses | | opaque-step-definition | warning | a non-literal pattern; cannot be checked | | opaque-parameter-type | warning | a non-literal defineParameterType | | unrecognized-glue | warning | a file imports @cucumber/cucumber but registers steps dynamically, or could not be scanned | | no-scenarios | warning | a feature compiles to zero scenarios (the classic Scenario missing its colon) | | screenplay-step-shape | warning | a step body holds logic instead of delegating to a Task (screenplayStepShape) | | then-step-acts | warning | a Then step's glue re-runs a Task; outcomes should observe via a Question (screenplayStepShape) | | scenario-pending | warning | a gated scenario is pending when executed (cucumberRunner) | | behavior-drift | warning | the agent judges a gated scenario untrue of the code (agentBehaviorFit) | | behavior-unverified | warning | the agent could not find the code that decides a scenario; neither drift nor a pass | | step-keyword-mismatch | info | a Given/When/Then step binds to glue registered under a different keyword; runners match on text alone, so it passes silently | | unused-actor | info | the Cast declares an actor no scenario mentions | | uncast-actor | info | an actor acts in a feature whose Cast does not declare them | | drift-ready | info | a @drift scenario now binds fully | | uncovered-behavior | info | behaviors no scenario describes, one finding per file (behaviorCoverage) | | scenario-unproven | info | a glue-less gated scenario with no test behind it at any strength | | agent-test-evidence | info | the semantic tier had a test in view, with the strength of the proves link | | agent-source-evidence | info | the verdict rests on implementation source; a reading of the code, not a test result | | agent-foreign-glue | info | the agent judged a feature by reading glue the deterministic tier cannot parse | | agent-unavailable | info | an agent provider degraded; scenarios left unjudged, never a silent pass | | agent-truncated | info | a byte budget kept part of the evidence out of an agent's view; raise the provider's maxChars |

Tags: @drift and @draft

  • @drift: chosen behavior reality does not match yet. The gate tolerates it, tracks it in a burn-down line (drift: N scenarios declared, M ready to un-tag), and says drift-ready when the glue catches up. There are no baseline files; drift lives in the contract, visibly.
  • @draft: proposed behavior awaiting review, from draft or an agent. The gate ignores it; only the user promotes it, by deleting the tag or swapping it to @drift.

What green means

Exit 0 with the default config certifies that every gated scenario step binds to exactly one step definition that is not a stub. It does not certify that the scenarios pass: add cucumberRunner() for execution fit, and agentBehaviorFit() for the semantic check on top.

Versus cucumber-js --dry-run, which also finds undefined and ambiguous steps: fitgherkin needs no dependency install and executes no user code, so it runs in milliseconds in a bare sandbox, and it adds stub detection, @drift accounting, fail-closed reporting of what it could not analyze, and deterministic JSON. If you already run dry-run in CI and have no agents, the delta is smaller.

For AI agents

fitgherkin's agent interface is the CLI itself: run it, read the report, fix what it names. Each finding's hint: says where to look; --json=findings gives the same as typed data. There is deliberately no MCP server.

init writes a short block into AGENTS.md, or appends it once under its own heading when the file already exists. It says three things: the tool exists, when to run it, and where the guide is. The one norm it carries is the one an agent cannot infer from the CLI: the features are the contract, and editing them to match whatever the code does is the cheapest path to a green build. Everything else lives in the skill, so the block describes no behavior of fitgherkin's own for a behavior scan to mistake for the project's.

## Feature contract (fitgherkin)

- The Gherkin features under `features/` are this project's behavior
  contract. Run `npx fitgherkin` before handing off any change to code,
  `features/` or step definitions.
- Fix the code, not the features, to get to green. Editing a feature is a
  product decision to call out; never weaken, delete or tag a scenario to
  silence a finding.
- How to read findings, tags and write steps that fit: `node_modules/@arocnies/fitgherkin/skills/fitgherkin/SKILL.md`
  (Claude Code: install it as a skill with `mkdir -p .claude/skills && cp -R node_modules/@arocnies/fitgherkin/skills/fitgherkin .claude/skills/fitgherkin`).

The package also ships a Claude Code skill at skills/fitgherkin/ covering the fuller workflow: reading findings, what a verdict rests on, drift etiquette, writing steps that fit. init --agent claude installs it at .claude/skills/fitgherkin/; otherwise copy it there, or reference it in place from node_modules/@arocnies/fitgherkin/skills/fitgherkin/.

License

MIT