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

laymos

v0.0.17

Published

Enforces architectural dependency rules and explores source dependencies.

Downloads

1,404

Readme

laymos

Enforces architectural dependency rules and explores source dependencies.

Big picture

A project's architecture usually lives in people's heads. laymos reads most of it from the folder tree and asks for the rest in a plain laymos.config.json: a folder with an index.ts is a Module, any other folder is a Wrapper for what is inside, Rules say which Module or Wrapper may import which, and Exceptions name the few imports no Rule could hold, each with its Reason. laymos lint classifies every import against that and reports every gap, saying which key would close it. laymos inspect answers the reverse question: given a file or Module, what does it depend on and who is allowed to reach it.

The same config can point at a folder of Stories that teach the Project to its Reader. Each folder is a Story told by its story.md, from the top Story's pitch down to one part's edge cases; the *.proof.ts files beside it are its Proofs. A Proof is one claim in one file: given a Preparation, when an Action is performed, a Verification holds. It runs in the process or, for UI, in a real Chromium at a person's pace, and every run keeps Evidence a reader can judge: each phase's value, a trace, and for browser Proofs a Recording of every Tab. laymos stories runs them and prints the Story tree with a verdict per Proof. How to write a Telling is in docs/writing-stories.md.

The library entry does everything the CLI does, so other tools can host it. @kestack/devtools serves Architecture Analyses, change sets, and Proof reports over RPC to its browser UI using the browser-safe schema subpaths, and plays Recordings back on the Stories canvas. Stories capture traces with @kestack/effect-tracer.

Terms are defined in CONTEXT.md. Decisions are in docs/adr/; the tree of Modules, Rules and Exceptions is ADR-0019, the Story model is ADR-0018, and a Proof is ADR-0017. The config reference is in docs/config.md and the CLI reference in docs/cli.md.

Install

pnpm add -D laymos

Peer dependencies:

  • effect (^4.0.0): every library function returns an Effect, and Proofs are written as Effects.
  • playwright-core (1.63.0, optional): drives Chromium for browser Proofs. Install the browser once with npx playwright install chromium.
  • vite (^8.0.0, optional): serves each browser Proof's page with the Project's own Vite config.

Exports

laymos

Node-only. Reads the config, walks the source tree, and runs git.

| Export | What it does | | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | analyzeProject | Reads a config path and returns the full Architecture Analysis: config, Module tree, every import's verdict, and findings. | | inspectProject | The same Architecture Analysis, for the inspect project view. | | inspectModule | Reports one Module or Wrapper: its node, Reach, dependents, dependencies, and the imports it is a side of. | | inspectFile | Reports one file's owning Module, role, and dependencies, optionally recursive. | | InspectionTargetNotFound | Error when the inspected file or path does not exist. | | loadFileList | Lists every git-tracked file beneath a Module or Wrapper, each marked analyzed or not. | | loadFileContent | Reads one file of the Project; binary files come back empty and marked. | | loadFolderFiles | Reads every git-tracked file beneath the given paths of any folder. | | FileNotFound | Error when the path names no Module, Wrapper, or file inside the Project. | | FileReadError | Error when a file could not be read. | | getStoryTree | Loads the Story tree from storiesPath: each folder's Telling, its Proofs, and Telling issues, without running any Proof. | | findTellingIssues | Lists every Telling issue in the tree without importing any Proof file. | | planStories | Loads the tree and scopes it to a Story or Proof id, then returns the total, the scoped tree, and a Stream of run events. | | runStories | Same as planStories but returns only the Stream of Started and Finished events. | | loadStoryReports | Reads every saved report.json whose Proof is still in the tree. | | StoriesError | Error for a missing storiesPath, an unloadable or invalid Proof file, an unknown scope, or a bad timeout. | | loadBranches | Lists the git branches of the project's repository. | | loadChangeSet | Lists paths added, modified or deleted against a base ref, default HEAD. | | loadFileDiff | Returns the hunks of one file against a base ref. | | GitError | Error when the folder is not a repository, the ref is unknown, or git failed. | | ConfigError | Error when the config could not be read, parsed, decoded, or validated. | | CruiseError | Error when the source tree could not be walked or parsed. | | ArchitectureAnalysisSchema | Re-export from laymos/architecture-analysis-schema. | | FileListSchema, FileContentSchema, FolderFileSchema | Re-exports from laymos/architecture-analysis-schema. | | ProofReportSchema | Re-export from laymos/story/schema. | | ProofRunEventSchema | Re-export from laymos/story/schema. | | StoryTreeSchema | Re-export from laymos/story/schema. | | BranchSchema, ChangedPathSchema, ChangeSetSchema, ChangeStatusSchema, DiffHunkSchema, DiffLineSchema, FileDiffSchema | Re-exports from laymos/change-set-schema. |

laymos/architecture-analysis-schema

Browser-safe. Schemas only; no file system access.

| Export | What it does | | ----------------------------- | ------------------------------------------------------------------------------------------ | | ArchitectureAnalysisSchema | The wire contract for an Architecture Analysis: config, Module tree, imports, findings. | | ModuleTreeSchema | The tree read from disk: every node's kind, shape, Index, own files, parent, and children. | | TreeNodeSchema | One Module or Wrapper. | | ModuleImportSchema | One import between two nodes with its verdict: nested, rule, exception, or violation. | | FindingSchema | A file no Module owns, or a Rule or Exception nothing uses. | | RuleSchema | One concrete Rule, from and to. | | ExceptionSchema | One Exception with its Reason. | | ProjectConfigSchema | The decoded config with every key present. | | ProjectConfigInputSchema | The authoring config, where optional keys fall back to defaults. | | ConfigValidationIssueSchema | One validation problem with its kind and message. | | FileListSchema | The files beneath one Module or Wrapper, each marked analyzed or not. | | FileContentSchema | One file's path and contents. | | FolderFileSchema | One file of a folder read by loadFolderFiles. |

laymos/change-set-schema

Browser-safe.

| Export | What it does | | -------------------- | ----------------------------------------------------------------------- | | ChangeStatusSchema | added or modified. | | ChangedPathSchema | One changed path with its status. | | ChangeSetSchema | Every changed path against a base ref. | | DiffLineSchema | One line of a diff hunk. | | DiffHunkSchema | One hunk of a file diff. | | FileDiffSchema | The hunks of one file. | | BranchSchema | One local or remote-tracking branch, with remote and current flags. |

laymos/story

For Proof files. Browser-safe: Proof files are imported by the runner in Node and by the page in the browser.

| Export | What it does | | ----------------- | ------------------------------------------------------------------------------------------------------------------ | | Proof.make | Builds a process Proof from a title, prepare, act, and verify; optional description, Critical mark, timeout. | | Proof.browser | Builds a browser Proof: page mounts the UI in the page, prepare opens Devices and Tabs. | | Proof.assert | Records a described pass or fail in the current phase. | | Proof.budget | Asserts every span with a name recorded so far ended within a Duration; a missing span fails. | | Gesture.tap | One finger touches a target in a short, visible hold. | | Gesture.press | One finger holds on a target, one second unless told otherwise. | | Gesture.swipe | One or more fingers sweep across a target in a direction. | | Gesture.drag | One finger carries a target to another target or a point. | | Gesture.pinch | Two fingers spread or close on a target by a scale. | | Gesture.rotate | Two fingers turn on a target by degrees. | | Gesture.fingers | Any number of fingers, each along its own timed path. | | ProofContext | The service the runner provides to every phase; Proof.assert and Proof.budget use it. | | isProof | Type guard for a Proof value. |

laymos/story/schema

Browser-safe.

| Export | What it does | | ---------------------- | ------------------------------------------------------------------------------------------------------- | | StoryTreeSchema | The top Story of the tree. | | StoryNodeSchema | One Story: id, title, pitch, body, its Proofs, sub-Stories, and Telling issues. | | TellingIssueSchema | missing-telling, incomplete-telling, broken-link, or unnamed-part, with its target and message. | | ProofLeafSchema | One Proof: id, name, title, description, Venue, Critical mark, and the source of its file. | | ProofSourceSchema | The path and contents of a Proof file. | | VenueSchema | process or browser. | | ProofReportSchema | One Proof run: verdict, phases, trace, Steps, and Recordings. | | ProofRunEventSchema | Started or Finished with the report, streamed by a Stories run. | | ProofVerdictSchema | passed, failed, errored, or unprepared. | | PhaseReportSchema | One phase: status, timing, returned value or error, and assertions. | | PhaseNameSchema | prepare, act, or verify. | | PhaseStatusSchema | passed, failed, errored, or skipped. | | ProofAssertionSchema | One assertion with its description and result. | | CapturedTraceSchema | Every span and log recorded during the run. | | StepSchema | One named Step on one Tab, with its phase, timing, outcome, and screenshot. | | StepKindSchema | open, click, type, press, scroll, gesture, wait, screenshot, raw, or close. | | RecordingSchema | Every frame one Tab produced, on the Proof's clock. | | FrameSchema | One frame file and the moment it appeared. | | DeviceKindSchema | desktop or mobile. | | JsonValueSchema | Any JSON value, as phase values are kept. |

laymos/skills-command

| Export | What it does | | ------------------- | ------------------------------------------------------------------------------- | | makeSkillsCommand | Builds a skills CLI subcommand that lists, prints, or installs skill folders. |

CLI

| Command | What it does | | --------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | laymos lint | Classifies every import, reports Violations, unowned files, unused Rules, Tellings, and Proofs. | | laymos inspect project [--json] | Prints the Module tree, Rules, and Exceptions. | | laymos inspect file <path> [--recursive] [--json] | Shows a file's Module, role, and dependency tree. | | laymos inspect module <path> [--json] | Shows a Module or Wrapper, its Reach, and both dependency directions. | | laymos stories [scope] [--concurrency <n>] | Runs the Proofs in a scope and prints the Story tree with each verdict. | | laymos skills [<name>] [--install <dir>] | Lists, prints, or installs the shipped agent skills. |

Details and exit codes are in docs/cli.md.

Usage

Analyze a project from Node

Read a config and get the full Architecture Analysis as one value. This is what the CLI and the DevTools server both do first.

import { Effect, Schema } from 'effect';
import { analyzeProject, ArchitectureAnalysisSchema } from 'laymos';

const analysis = await analyzeProject('./laymos.config.json').pipe(
  Effect.runPromise,
);

analysis.config.sourceRoots; // ['src']
analysis.tree.nodes; // every Module and Wrapper
analysis.imports.filter(({ verdict }) => verdict.kind === 'violation');

// Send it over the wire: Maps and Sets encode to plain JSON.
const codec = Schema.toCodecJson(ArchitectureAnalysisSchema);
const json = Schema.encodeSync(codec)(analysis);

How it works:

  • analyzeProject loads and validates the config, walks sourceRoots with oxc, reads the Module tree, and gives every import its verdict.
  • Failures are typed: ConfigError for the config, CruiseError for the source walk.
  • ArchitectureAnalysisSchema is the same contract the browser decodes.

Tell a Story and prove it

Each folder beneath storiesPath is a Story. Its story.md is the Telling: a # title, a one-sentence pitch, then a short body that links each part by its id. Proofs sit beside it as *.proof.ts files, each default-exporting one Proof that imports only what the Project ships, effect, and laymos/story.

// stories/story.md:
//   # Resources
//
//   What you open stays open until you have checked it.
//
//   A [resource lives until Verification ends](my-app/resource-lives).
//
// stories/resource-lives.proof.ts:
import { Effect } from 'effect';
import { Proof } from 'laymos/story';

export default Proof.make({
  title: 'A resource lives until Verification ends',
  prepare: Effect.gen(function* () {
    const resource = yield* Effect.acquireRelease(
      Effect.succeed({ open: true }),
      (resource) => Effect.sync(() => (resource.open = false)),
    );
    yield* Proof.assert('the resource opened', resource.open);
    return resource;
  }),
  act: (resource) =>
    Effect.succeed({ doubled: 21 * 2, open: resource.open }).pipe(
      Effect.withSpan('double'),
    ),
  verify: (output, resource) =>
    Effect.gen(function* () {
      yield* Proof.assert('21 doubled is 42', output.doubled === 42);
      yield* Proof.assert('the resource is still open', resource.open);
      yield* Proof.budget('double', '5 millis');
    }),
});

How it works:

  • The top Story's id is the Project's folder name (my-app); a sub-Story adds its folder path (my-app/sync), and a Proof adds its file name without .proof.ts (my-app/resource-lives). laymos stories my-app/sync runs that Story and everything beneath it.
  • A Telling shows its parts in the order it first links them; unlinked Proofs follow by name. A missing or incomplete Telling, a link to nothing, and a sub-Story never linked are Telling issues that laymos lint reports.
  • The three phases run in one scope, so what prepare acquires lives until verify ends. A false assertion in prepare makes the Proof unprepared; a false one later makes it failed; a phase that dies, or a timeout, makes it errored. The run is traced as Proof › prepare / act / verify.

Prove it in the browser

A browser Proof mounts what it exercises with page, then performs named Steps on Tabs. The run records every Tab at a person's pace, so the Recording reads like someone using the app.

import { Effect } from 'effect';
import { Gesture, Proof } from 'laymos/story';

export default Proof.browser({
  title: 'Two fingers zoom the photo',
  page: (root) => {
    root.innerHTML = '<div id="photo"></div><p id="scale">1.00</p>';
    // wire touch handlers that write the scale into #scale
  },
  prepare: (browser) => browser.open('mobile'),
  act: (tab) =>
    Effect.gen(function* () {
      yield* tab.gesture('Pinch the photo open', Gesture.pinch('#photo', 2));
      return Number(yield* tab.text('#scale'));
    }),
  verify: (scale) => Proof.assert('the photo doubled', scale > 1.8),
});

How it works:

  • One Vite dev server per run serves /__laymos/proof/<id>, which imports the Proof file and calls page(root); the Project's vite.config.* applies.
  • desktop is 1280×800; mobile is Playwright's Pixel 7. Each browser.open is a new Device with its own storage; tab.device.open adds a Tab on the same Device.
  • Steps move at a person's pace: the pointer travels 0.5–0.9 s, each key takes about 100 ms, a tap is held 150 ms, a Gesture lasts a second unless given a duration, and the screen rests about 0.7 s after each Step. Browser Proofs time out after 90 seconds by default.
  • Evidence lands in .laymos/stories/<proof id>/: report.json, one folder of frames per Tab, and steps/<n>.jpg for each Step.