Orientation for an agent landing in this repository cold. CLAUDE.md is a
symlink to this file — edit this one.
webcodecs-census finds leaked WebCodecs objects in a browser app and hands
back the line of code that allocated them. WebCodecs objects hold resources
from a finite pool outside the JS heap; GC never reclaims them, only close()
does, and nothing in the platform tells you that you leaked one. The hard part
is that decoders live in Web Workers, which page-level monkey-patching cannot
reach — so the exact path drives Chrome over the DevTools Protocol and injects
into each worker at a beforeScriptExecution pause, before its first line.
Public repository, published to npm as three packages under @motionvector/.
| Path | What |
|---|---|
packages/core |
The instrumentation and the assertion API. No dependencies. Also builds the injectable IIFE shim. |
packages/cdp |
Injects the shim into a running Chrome — page, iframes, workers — over CDP. |
packages/mcp |
An MCP server wrapping the above, so an agent can use it. |
extension/ |
A Chrome MV3 extension. Patch mode (no debugger) and exact mode (chrome.debugger). |
docs/ |
The documentation site. Plain HTML, no build step. See docs/README.md. |
test/ |
Real-browser tests. No mocks. |
scripts/ |
Release plumbing: version.mjs, preflight.mjs, publish.mjs, release-notes.mjs, make-diagram.mjs. |
A change must not let this tool report "no leaks" for an app that is
leaking. Everything else is style. A leak detector that silently sees nothing
is worse than no leak detector, because it converts an open question into a
wrong answer. CONTRIBUTING.md has the long version.
Several things exist only to protect that property, and they are not refactoring targets:
installCensus()wraps each patch step separately and records failures inproblems[]instead of throwing.- Patch mode reports every worker it could not wrap, with the reason.
checkLeaks()never letstypesfilter awaycollectedUnclosed, and never prints an unqualified all-clear while an unenforced type holds live objects.test/platform-assumptions.test.mjsasserts the undocumented Chrome behaviour injection depends on, so a browser change is reported as a browser change.
The corollary matters too: the tool must not invent leaks either. A codec the platform closed after an error is not a leak, and reporting it as one would be the same class of failure pointed the other way.
If you add a path that can silently observe less than it appears to, add the
counter or the problems[] entry that makes it visible.
npm install
npm run build:all # core + cdp + mcp, then the extension, then test fixtures
npm test # node --test over test/*.test.mjs
npm run typecheck # tsc over all three packages; needs npm run build firstTests drive a real Chrome — there are no mocks, deliberately. A mocked
VideoDecoder would have hidden every bug worth finding here. They find a
Chrome for Testing in the Puppeteer cache, or you point at one:
CHROME_PATH="$HOME/.cache/puppeteer/chrome/mac_arm-151.0.7922.71/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing" npm testNever the user's own Chrome. Instrumenting that would touch their profile and their session.
Run npm ci inside the worktree before trusting a test or typecheck
result. A worktree with no node_modules of its own resolves
@motionvector/* upwards to the main checkout, so:
typecheckchecksmcpagainst whatever declarations are built over there — reporting errors against code that is fine, or missing errors in code that is not.- The tests import their entry points by relative path, but
packages/cdploads the shim by package name, so a run in a bare worktree tests the main checkout's shim, not yours.
ls -la node_modules/@motionvector should show symlinks pointing back into the
worktree you are standing in.
Maintainers only, and all three packages move together — -cdp and -mcp
depend on an exact version of the core, so bumping one alone publishes a
package whose dependency does not exist.
node scripts/version.mjs minor # or patch / major / an explicit version
git commit -am "release: v0.3.0" && git tag v0.3.0
git push origin main --tagsscripts/version.mjs moves the three package.json versions, rewrites the
cross-dependencies, rewrites the two version strings that live in source
(VERSION in packages/core/src/census.ts and the name the MCP server
announces in packages/mcp/src/index.ts), and promotes the Unreleased
section of CHANGELOG.md. It refuses a dirty tree, and it fails loudly rather
than silently if a version pattern stops matching — those two stamps shipped
two releases stale once because nothing rewrote them.
The v* tag triggers .github/workflows/release.yml, which refuses a tag that
disagrees with the packages, runs the full browser suite against pinned Chrome
before publishing, then publishes all three in dependency order over OIDC
trusted publishing with provenance, and opens a GitHub Release from that
version's changelog section with the packed extension attached. There is no npm
token.
Claims in this repository are measured. The overhead figures, the globals table, the comparison against heap snapshots — each came from a run, not from reasoning about what ought to happen. Hold new claims to that. If you cannot measure it, write down that you could not, or leave it out.
Short sentences, active voice, no filler. Comments explain why, particularly where the reason is a platform quirk that will look like a mistake to the next reader. Do not add comments that restate the code.
This is a public repository. Do not push, open a pull request, publish to npm, or enable GitHub Pages without the maintainer saying so in the conversation. Draft it, then hand it over.