New:Microsoft Teams Notifications Are Now Available in Socket.Learn more
Get Started

@storybloq/lenses

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@storybloq/lenses

Multi-lens code review MCP server — 8 specialized reviewers in parallel

latest
npmnpm
Version
0.6.0
Version published
Weekly downloads
365
42.58%
Maintainers
1
Weekly downloads
 
Created
Source

@storybloq/lenses

Multi-lens code review MCP server — 8 specialized reviewers run in parallel, with findings deduplicated, confidence-filtered, and rolled into a single verdict.

Installation

npm install -g @storybloq/lenses

Register with Claude Code

claude mcp add lenses -s user -- lenses --mcp

After registration, three tools become available in your Claude Code session:

  • lens_review_start -- Returns {reviewId, agents: [{id, model, promptHash, expiresAt}], cached}. Refs-not-prompts shape keeps the hop-1 payload small; fetch the actual prompt for each agent via lens_review_get_prompt before spawning. The agents[].expiresAt is provisional: the prompt fetch anchors the authoritative deadline.
  • lens_review_get_prompt -- Looks up the full prompt for one lens in an active review (stateless per (reviewId, lensId)) and returns the lens's authoritative expiresAt: the first attempt-1 fetch starts the lens's timeout clock, once per attempt.
  • lens_review_complete -- Accepts the subagent outputs (with optional attempt for retry) incrementally (partial batches and empty polls are fine) and returns the merged verdict. The envelope includes parseErrors[], deferred[], suppressedFindingCount, hadAnyFindings, nextActions[] for the cooperative retry protocol (each retry carries a fresh per-attempt expiresAt), plus the coverage disclosure: lensCoverage[], coverage (full/partial), errorCodes (PARTIAL_RESULTS when a lens expired), and reviewComplete (false marks an interim envelope; the review is still open). A late result diverts its lens to expired coverage instead of rejecting the call, and an uncovered core lens caps the verdict below approve.

Architecture

See CLAUDE.md in the source repository for the two-hop flow, lens activation logic, merger semantics, and session caching.

Stable library API

The package root (@storybloq/lenses) is a stable library surface, not just an MCP server entry point. It is consumed directly by the storybloq autonomous review backend, so existing exports are not renamed or removed across minor versions. The exported core is:

  • LENSES, getLens, SURFACE_RULES - the lens registry projection and surface-activation routing rules
  • activate - decide which lenses fire for a review (with LensConfigSchema, LensIdSchema)
  • buildLensPrompt, buildAgentPrompts - construct the complete self-contained prompt(s) for activated lenses
  • renderLensBody, renderSharedPreamble - lower-level prompt-body and shared-preamble renderers
  • runMergerPipeline - the single merger entry that runs dedup, blocking policy, tension detection, and verdict computation over per-lens outputs
  • coreLensApplicability, with the CORE_LENS_NOT_APPLICABLE_EXTENSIONS, CORE_LENS_NOT_APPLICABLE_DIRS, SECURITY_SURFACE_PATHS, CONCURRENCY_SURFACE_TOKENS and CONCURRENCY_TOKEN_COVERED_EXTENSIONS tables it reads - the server-side check for whether a lens had anything in its domain to review
  • buildNewSideIndex, diffTouchedPaths (aliased as newSideFilePaths), changedDiffLines, addedDiffLines, normalizeRepoPath - the shared unified-diff parser, so a consumer building its own coverage computes the same file union the server does
  • the schema, verdict, and blocking-policy types and Zod validators re-exported from src/schema, including FindingOriginSchema, FindingOriginClassSchema and isCoveredEntry

Coverage, skips, and the approve cap

An uncovered core lens caps the verdict below approve. As of 0.6.0 "covered" is decided by isCoveredEntry, not by status alone: a core lens that reports skipped is covered only when the SERVER can verify the lens had nothing in its domain.

coreLensApplicability makes that call over the UNION of the caller-declared changedFiles and every path the diff touches, because a declared list alone is a claim, not evidence. "Touches" is read from five places, not just the +++ header: a deletion names its file only on --- a/path and diff --git, a rename names both rename from and rename to, and a mode change or binary patch carries no hunk at all. Reading +++ alone let a caller declare a docs-only change, delete a source file in the same diff, and have every core lens excuse itself. Where a diff SECTION carries no diff --git line, a leading a/ or b/ cannot be confirmed as git's prefix, so BOTH spellings join the union. Confirmation belongs to the one section that earned it and is spent there: a section is confirmed only when a diff --git opened it AND its own old-side header reads --- a/..., --- "a/..." or --- /dev/null, which is every form git writes and no form it writes for a following section. That second test is what a flag alone cannot do, because a header-only git section (a pure rename, a mode change, a binary patch) ends without a ---/+++ pair, so the NEXT section's header is the first one after that diff --git. Unconfirmed, a guessed b/docs/engine.ts was stripped into a root-anchored docs/ path and excused every core lens over a file that added a lock: stripping unconditionally would promote a repository's real root b/ directory into docs/. A change is not applicable to a lens when every file in that union is non-code, which means an extension in CORE_LENS_NOT_APPLICABLE_EXTENSIONS (documentation, data, config and image formats) or a path under a repository-ROOT docs/ or .story/ directory. Two lenses narrow that further:

  • security stays applicable whenever any file matches SECURITY_SURFACE_PATHS: CI definitions (anything under a root .github/, .circleci/ or .buildkite/, plus .gitlab-ci.yml, Jenkinsfile, Dockerfile*), dependency manifests and locks (package.json, package-lock.json, pnpm-lock.yaml, yarn.lock, any *.lock, Cargo.toml, pyproject.toml, requirements*, Pipfile, Pipfile.lock, poetry.lock, Gemfile, Gemfile.lock, go.mod, go.sum), and any *.svg, which is XML that can carry script. Config-as-code is not code by extension and is squarely the security lens's business.
  • concurrency may additionally be excused on a code change when no CHANGED diff line carries any row of CONCURRENCY_SURFACE_TOKENS. Changed means added OR removed: deleting a mutex.acquire() is a change to the concurrency surface, and reading only the added side excused the lens over exactly the edit most likely to introduce a race. A removal counts whatever its position in the hunk, including the two shapes a new-side line budget alone cannot see: a git diff -U0 deletion hunk (@@ -12 +11,0 @@), which adds nothing at all, and a removal that is the last line of a hunk at default context. Hunk membership comes from the header's old-side count, so a hunk stays open after its new-side lines run out. In that tail the parser accepts removals, \ No newline punctuation, and four header forms it can recognise there. +++ , diff --git and @@ are recognised by prefix, since a removal line always starts with -. A --- line is recognised as a header on either of two signals: what follows is a spelling git writes (a/..., "a/..." or /dev/null), or the very next line opens with +++ , since a unified diff always pairs the two. One signal alone is not enough in either direction. An ordinary removal of a source line beginning with -- (a SQL or Lua comment, usage text in a template literal) renders with the header prefix, and reading it as a header drops that removal and every one after it; a genuine header spelled --- b/docs/engine.ts or --- src/pool.ts is not in git's own form, and reading it as a removal drops a deleted file from the union, since its --- line is the only place it is named. Two residual cases remain, in opposite directions, both accepted because every alternative is worse. Under-review: a removed source line whose own text begins -- a/, -- "a/ or -- /dev/null reads as a git-spelled header, closes the hunk, and hides the removals after it. Reading no --- as a header instead would drop deleted files from the union, since their --- line is the only place they are named, and reading one as a header on the +++ pairing alone would swallow a truncated header and a split ---/index/+++ pair. Over-review: the second case needs a malformed hunk header to reach. two removals can never trigger the pairing signal, because a removed line carrying ++ y renders as -++ y rather than +++ y, so the second line has to be an ADDED line whose text begins ++ , and an added line only reaches the tail when the header's new-side count understates what the hunk carries. Under a count that short, a removed line beginning -- followed by that added line is read as a header pair. The counts would separate the two, and the parser declines to trust them there because reaching the tail on an added line is already proof they are wrong. The failure direction is the safe one: the union gains a spurious path, and a larger union can only make a lens more applicable. Context lines are excluded, since untouched surrounding code is not this change's doing. The carve-out needs two further conditions: every code file in the union must have an extension in CONCURRENCY_TOKEN_COVERED_EXTENSIONS, and every code file must actually have contributed a new-side line to the diff. An ecosystem the table does not model, or a file the diff shows no new lines for, defaults to review: "the table found nothing", "the table cannot see" and "there was nothing to look at" are three different statements, and only the first excuses a lens.

Line endings are normalized by the walk, so a CRLF diff and its LF twin produce the same paths, index keys and line content.

Only the concurrency token rule reads the diff's added lines; every other rule reads the file set. Every uncertain case resolves to applicable, because over-reviewing is the safe failure.

A skip on an applicable change keeps the self-reported basis and still caps, so a lens cannot claim a skip by prompt alone; a lens that never submitted is no-submission and never covered. A basis supplied by a caller is trusted DOWNWARD only: no-submission and self-reported are kept as given, while a supplied not-applicable is recomputed and demoted to self-reported when the server disagrees or has no diff to check it against.

A core lens that reports skipped on a change the server judges APPLICABLE is re-dispatched: lens_review_complete returns a nextActions[] entry for it, within the same maxAttempts budget that governs error and parse-failure retries, carrying a retry prompt that names the reason. That prompt says only what the server actually established: with a non-empty file union it found files in the lens's domain, and with an empty one (a plan review, or an artifact carrying no parseable diff) it could only fail to rule the domain out. Either way it permits ok with zero findings when there is genuinely nothing to report. That is the cap's actual remedy, and it is what the skipped status costs. A NOT-APPLICABLE skip is already coverage and is never re-dispatched; a non-core skip caps nothing, so it is not re-dispatched either.

An entry marked relabeled is never covered whatever its status, and the rule is about the DISPATCH rather than the pair of statuses. Within one review session the server sets the flag itself: a lens that submitted skipped and then submits ok with ZERO findings against the SAME dispatch is renaming its old answer, not producing a new one. Issuing the re-dispatch clears the flag, so the answer that comes back from it counts as the honest re-review it is. Without that escape the remedy was a trap: the cap fired, nothing re-dispatched the lens, and no sequence of honest behaviour could clear the mark. A flip the server had no budget left to ask for is still a relabel. An ok that actually carries findings is real work and is never flagged. Detection ACROSS separate review calls belongs to the caller, which is the only side that can see them; such a caller sets relabeled on the entry it supplies, and the server honours it.

When a cap fires, capReasons names it, one string per cap, so a caller can route a coverage-only revise to a lens re-run rather than another full round.

LensCoverageEntrySchema is .strict() and these entries now carry basis and relabeled, so a consumer that validates verdicts against its own bundled 0.5.x schema must upgrade together with the lens server: a 0.5.x strict parse of a 0.6.0 verdict rejects the whole payload rather than ignoring the two unknown keys.

Optional finding fields, and one direction of compatibility

As of 0.5.0 a finding may also carry principle (the review-contract principle it violates, supplied by the lens) and the reporter-supplied provenance fields dispositionReason, origin, originClass and sinceRound. All are optional and none is defaulted: absence means no claim was made, which is a different statement from a known-empty one, and principle additionally rejects blank values so that absence stays the only way to name no principle.

The addition is one-directional. A payload produced before 0.5.0 parses on 0.5.0 unchanged. The reverse does NOT hold: LensFindingSchema is .strict(), so a consumer still on 0.4.x that receives a finding carrying principle loses the WHOLE payload to the error-placeholder path, not just the field. Upgrade the consumer before anything starts producing these fields.

A merge carries the REPRESENTATIVE's claim for all five and never borrows, fills or escalates from another member of the same dedup group. Members may well describe the same defect, which is what dedup is for, but group membership does not establish it, so one member's claim is never treated as interchangeable with another's. One consequence is deliberate and worth knowing: a non-representative's originClass: "reintroduced" does not survive a merge.

Mutability boundary

All registry objects exported from the package root (LENSES, each lens projection, SURFACE_RULES, its nested rule objects and arrays) are frozen projection copies. The Zod schema instances (optsSchema) and renderBody implementations that live on the internal lens definitions are internal and unreachable from the public surface. Mutating any exported registry object throws in strict mode and can never alter prompt construction: the internal registry that buildLensPrompt and activate read is a separate, unfrozen object, disjoint from the frozen public projection.

License

PolyForm Shield 1.0.0 - source-available, non-compete (not OSI open source). Any use is permitted (personal, internal, commercial) except building a product that competes with storybloq or lenses. Competing, hosted, or white-label use requires a separate license: contact shayegh@me.com. See LICENSE and NOTICE.

FAQs

Package last updated on 15 Sep 2026

Related posts