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

bounded

v3.3.0

Published

Bounded: guardrails for AI coding agents. Typed packs and extension points, the protected-paths pack, and hooks for Claude Code and pi, with the bounded command to set a project up

Readme

bounded

Guardrails for AI coding agents. Bounded judges every tool call an agent makes (Claude Code or pi), before it runs, against the packs a project selects. A refusal names the rule, the pack that contributed it, and what to do instead. Every decision is recorded in .bounded/log.jsonl. The protected-paths pack refuses reads, listings and writes of the paths a project protects, including those a shell command names, and puts back what a shell command changed in them.

It runs on Node 22.18 or later, with npm, pnpm, yarn or bun.

Start

In your project (it needs a package.json):

npx bounded init

This adds bounded as a devDependency. It writes bounded.config.ts, selecting the protected-paths pack, which brings in the core, with default rules that keep agents off this configuration, .bounded/, Claude Code's settings, pi's loader, Bounded's installed code and git's hooks and config. It installs the hooks of the agent hosts the project uses (.claude/, .pi/, or --host claude-code, --host pi). Restart (or start) the hosts' sessions afterwards, as init says, so they load the hooks. After bounded update, the CLI says, per host, whether sessions must restart: Claude Code's only when its settings changed, pi's every time.

Then add your own rules beside the defaults:

// bounded.config.ts
import { contribution, defineConfig } from "bounded/domain";
import { protectedPathsPack } from "bounded/protected-paths";

export default defineConfig({
  packs: [protectedPathsPack],
  contributes: [
    contribution(protectedPathsPack.points.protectedPaths, [
      // ...init's default rules...
      { match: "secrets/**", deny: ["read", "create", "modify", "delete"], why: "secrets are kept by people", redirect: "Ask a maintainer for the value you need" },
    ]),
  ],
});

Run npx bounded update to upgrade Bounded and refresh the hooks.

What it is not

Bounded discourages agents and keeps a record; it is not a security boundary. An agent running as your user can always get round it. For enforcement, pair it with your host's operating-system sandbox, such as Claude Code's sandbox mode. Known ways round it:

  • drift does not watch node_modules or .git, so what a command changes there is not put back;
  • commands the protected-paths pack does not recognise as writing, such as sed -i, perl -i, node -e and python -c;
  • git config … and git -c core.hooksPath=…, which the protected-paths pack sees as reads, so they get past the rules on git's hooks and config;
  • paths the protected-paths pack cannot resolve (variables, globs), which it allows;
  • user-level settings outside the project, such as ~/.claude/settings.json;
  • writes delayed into the background, after the tool call is judged;
  • drift's snapshots, kept in a state directory the user (and so the agent) can write.

Export paths

A configuration imports bounded/domain and the packs it selects, such as bounded/protected-paths and bounded/prereqs. The adapter export paths, bounded/adapters, bounded/protected-paths/adapters and bounded/prereqs/adapters, are internal: they serve the hooks for Claude Code and pi that this package carries and its bounded command, and may change in any release.

3.1.0 deliberately breaks what 3.0.0 published, in a minor release because 3.0.0 was about an hour old with no users: it replaced 3.0.0's bounded/adapters/{file-system,in-memory,system} and bounded/path-gate/adapters/{file-system,in-memory,tree-sitter}, removed their in-memory doubles and pathGateInMemory, and made the clock and decision-id ports give DecisionTime and DecisionId (the repository's ADR 2026-017). 3.2.0, also a minor release by the maintainer's choice, deliberately breaks the types 3.0.0 and 3.1.0 published: a selection now brings in every pack its listed packs depend on, so Config.selectedPacks is Config.listedPacks, compose-packs' input field selectedPackIds is listedPackIds, and SelectedPacks.packs holds every selected pack, listed or brought in, beside the new listedPacks (the repository's ADR 2026-018). It also reads shell commands once, in the core's judge (the repository's ADR 2026-020): openProject requires a shellCommandReader, pathGatePortProvisions() gives two provisions, and bounded/path-gate no longer exports PathKind, PathKinds, ShellCheck, ShellParser, pathKindsPort or shellParserPort (nor its adapters TreeSitterShellParser and FileSystemPathKinds). 3.3.0 renames the path gate the protected-paths pack (the repository's ADR 2026-021): bounded/path-gate and bounded/path-gate/adapters are now bounded/protected-paths and bounded/protected-paths/adapters, and pathGate and pathGatePortProvisions are protectedPathsPack and protectedPathsPortProvisions; the old names are removed, with nothing kept for them. 3.3.0 also renames the guard log the Bounded log (the repository's ADR 2026-022), breaking what 3.2.0 published, with no aliases: bounded/application no longer exports GuardLog and ProjectGuardLogs (now BoundedLog and ProjectBoundedLogs), openProject's guardLog option is boundedLog, bounded/adapters no longer exports FileSystemGuardLog and FileSystemProjectGuardLogs (now FileSystemBoundedLog and FileSystemProjectBoundedLogs), and decisions go to .bounded/log.jsonl; an existing .bounded/guard-log.jsonl is left as it was. 3.3.0 also corrects how shell commands are read (the repository's ADR 2026-020, corrected): the host adapter reads each one, not the core's judge, breaking what 3.2.0 published, with no aliases: bounded/application no longer exports ShellCommandReader (now bounded/shell-command-reader's), openProject and OpenProjectHandler take no shellCommandReader, JudgeEventHandler takes no shellCommandReader, projectRoot or readWithinMs and has no DEFAULT_READ_WITHIN_MS, ExecuteEffect.reading is never null and ExecuteEffectJSON.reading is required, bounded/hosts/pi's translate gives the call before its readings, and its piExtension (ExtensionOptions) requires a readShellCommand. A host that sends an execute effect without a reading, as every 3.2.0 host did, has each such call refused as an event that cannot be read and recorded as invalid; a tool result's execute effects must now carry a reading too.

A host other than the two this package carries opens a project with the ports its packs need, and reads each shell command with the reader bounded publishes, putting the reading on the execute effect it builds:

import { openProject } from "bounded/open-project";
import { protectedPathsPortProvisions } from "bounded/protected-paths/adapters";
import { prereqsPortProvisions } from "bounded/prereqs/adapters";
import { openShellCommandReading } from "bounded/shell-command-reader";

const project = await openProject(root, { ports: [...protectedPathsPortProvisions(), ...prereqsPortProvisions()] });
const shellCommandReading = openShellCommandReading();
void shellCommandReading.prepare();
const reading = await shellCommandReading.read({ projectRoot: root, command, cwd: null });
const verdict = await project.judge({ kind: "tool-use", role: null, tool: "shell", effects: [{ kind: "execute", command, cwd: null, reading }] });

An execute effect without a reading is refused; one whose command could not be read carries why, and the protected-paths pack refuses it. A host with a shell of its own may write its own reader of the ShellCommandReader port (bounded/shell-command-reader, which gives ReadShellCommandHandler to read with it); its tests run the suite every reader runs, bounded/testing/shell-command-reader-conformance (under bun's test runner).

More

The design, the configuration, the packs and the decisions behind them are in the repository: https://github.com/bounded-dev/bounded.

MIT licence.