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

@psnext/lscg

v0.1.6

Published

Local source context graphs for repositories, exposed as a CLI and MCP server.

Readme

lscg

Local source context graphs for repositories. lscg parses code with Tree-sitter, stores discovered nodes and edges in SQLite, and exposes the graph through both a CLI and an MCP server.

Requirements

  • Node.js 24+
  • npm install scripts allowed for Tree-sitter native packages

Install

npm install

CLI

Use global help to list commands, or ask any operational command for focused usage, supported options, and examples with --help or -h:

lscg --help
lscg context --help
lscg callgraph -h

The positional form lscg <command> help is not a command-help alias; help remains available as a normal command argument.

Common option aliases are -r/--root, -s/--scope, -k/--kind, -d/--depth, -l/--limit, -f/--file, and -o/--output where the command supports the corresponding option. callgraph and context use text output by default; pass --output json when machine-readable JSON is needed.

# Create repo-local and home-level graph databases
npm start -- init --scope both

# Scan the current repository into both graph scopes
npm start -- scan --scope both

# Watch the current repository and rescan on scanner-relevant changes
npm run watch
npm run scan:watch
npm start -- watch
npm start -- scan --watch

# Retrieve bounded context around a symbol (JSON envelope)
npm start -- context greet --kind symbol
npm start -- context greet --depth 2 --limit 25
npm start -- context greet --file src/app.ts --excerpts --excerpt-lines 40 --excerpt-bytes 8000
npm start -- context greet --output text
npm start -- context greet --output svg > /tmp/context.svg

# Inspect graph contents
npm start -- status --scope repo
npm start -- nodes --limit 20
npm start -- nodes greet --kind symbol --limit 20
npm start -- nodes greet --output text
npm start -- edges --kind calls --limit 20
npm start -- edges --kind attributed_to --limit 20
npm start -- edges --output text
npm start -- neighbors <node-id> --depth 2

# Visualize the graph
npm start -- view --scope repo
npm start -- view --scope home --anchor greet --search util
npm start -- view --display icon --output /tmp/lscg-view.svg

# Run a read-only SQL query
npm start -- query "select kind, count(*) as n from nodes group by kind"

# Start the MCP server over stdio
npm start -- mcp

When installed as a package, the binary names are lscg and context-graph.

MCP tools

The MCP server exposes:

  • context_graph_scan
  • context_graph_status
  • context_graph_nodes
  • context_graph_edges
  • context_graph_neighbors
  • context_graph_query

Storage

  • Repo database: .sling/graph.sqlite
  • Home database: ~/.sling/graph.sqlite

Use --scope repo, --scope home, or --scope both on commands that read or write graph data.

lscg view opens a temporary interactive HTML page by default and shows the anchor-centered subgraph for the selected scope when no --anchor is provided. The browser view includes a toggle to switch between the subgraph and the full graph. Pass --search to filter within the active graph mode, or --output <path> to write a static SVG snapshot of the default subgraph instead of opening a browser. Use --display simple, --display shape (the default), or --display icon to choose how nodes are rendered. Simple mode uses colored circles, shape mode uses kind-specific shapes, and icon mode loads SVG icons from src/view/templates/icons.

Examples:

lscg view --anchor greet --search util
lscg view --display simple --output graph.svg

lscg context <symbol> is the agent-oriented retrieval workflow. It returns a versioned JSON envelope with one result per selected scope by default. Use --output text for a concise human-readable summary or --output svg for a static graph of the returned context slice. Results distinguish ok, ambiguous, not_found, refresh_failed, and stale states from an empty context. Anchor matching prefers qualified/path and exact names before prefix and substring matches; an ambiguous match returns ranked candidates instead of merging unrelated symbols. Use --kind and repository-relative --file to disambiguate. Results are references by default; --excerpts opts into bounded source text with --excerpt-lines and --excerpt-bytes limits. Graph-reading commands never start a scan. Their Freshness Report reflects the stored graph state: fresh after a successful explicit scan, stale when no compatible graph exists or a known invalidation remains, and degraded when a retained failed-file or plugin contribution is being served. Run scan or keep watch active after source changes; a stored fresh result is not a live filesystem content-verification guarantee.

Impact contains only syntax-grounded calls edges (with caller/callee direction), and dependencies contain imports/exports; parser confidence and provenance are included so callers can verify relationships in source. Substring fallback is intentionally reported as a bounded capability warning. Use rg or grep for literal discovery when a term is not indexed—context does not replace arbitrary text search or claim complete semantic resolution.

lscg scan performs an Explicit Scan. After the first scan, its default mode is incremental: it retains unchanged file graphs and reprocesses files whose path, size, or mtime metadata changed. Use lscg scan --full when you need a complete rebuild and content verification (including same-size edits with restored mtimes).

lscg watch keeps the selected repository current in the foreground. It runs one scan immediately, then watches scanner-relevant files and prints compact status updates between rescans. lscg scan --watch provides the same Watch Session from the scan command. Use lscg scan when you want the one-shot scan output.

Pass --progress to scan, watch, or scan --watch for line-oriented, human-readable lifecycle messages on stderr while the existing JSON summary or Watch Session events remain on stdout. For example:

npm start -- scan --progress >summary.json
npm start -- watch --progress >watch-events.jsonl
npm start -- scan --watch --progress >watch-events.jsonl

Progress labels discovery/planning, plugin work, file processing, reconciliation, and optional enrichment. A normal scan may report structural completion with enrichment deferred or degraded; a Full Scan reports retries and only reports stabilized completion after its enrichment loop succeeds. Progress is informational rather than a stable machine-readable protocol, and it does not provide cancellation or animated progress bars.

Scan/watch benchmark

Run the non-blocking benchmark with:

npm run benchmark:scan-watch

The versioned JSON report separates two cold timings for this lscg checkout:

  • coldNormalStructuralReady: a normal scan's return after its structural graph is persisted, before optional enrichment completes.
  • coldFullEnrichmentComplete: a cold scan --full completion, including its required enrichment drain.

Every cold sample uses an isolated copy with no pre-existing graph database; source and Git metadata are copied so the normal and full scans exercise the same source graph and optional attribution path without changing this checkout. The report records SHA-256 fingerprints plus structural node/edge counts before normal-scan enrichment and after that scan's pending enrichment work drains. structuralEquivalence must be true: user nodes, attributed_to edges, and per-node attribution are intentionally excluded because they are optional enrichment rather than source structure.

coldNormalStructuralReadyTarget reports the 3.43-second baseline, the 2.06-second median target (the required 40% reduction), the measured p50, and whether that run met the target. Timing values and target status are benchmark results for the documented machine/run, not unit-test thresholds. Use multiple cold samples for review (the default is 10); pass -- --repetitions 5 to change the sample count.

The report also retains deterministic work counters and informational p50/p95 timings for the isolated representative fixture's initial full scan, no-change incremental scan, and one-file Watch Session update.

Git user attribution is optional. Enable it with --attribution on scan, watch, or scan --watch; normal scans return structural data with attribution pending, while --full and active watch sessions drain the selected enrichment. Without the flag, scans report attribution as disabled and do not invoke Git or create user nodes. Existing attribution remains readable if a later scan omits the flag. When enabled, contributor user nodes are keyed by normalized email and the most recent contributor is tracked for each discovered node.

Scanner plugins

The library accepts trusted, versioned scanner plugins through the plugins option on scanRepository. A plugin receives a read-only repository context with the resolved repoPath, discovered files, file contents, and a readFile helper, then returns semantic node and edge facts. Edges refer to node factKey values within their emitting plugin namespace and may connect facts from different files. Use the additive sourcePlugin and targetPlugin fields when an endpoint belongs to another plugin; raw fact keys are never parsed as namespaces. Nodes and edges may also carry plugin-defined type values; edge type defaults to its structural kind when omitted.

import { AttributionPlugin, scanRepository, type ScannerPlugin } from '@psnext/lscg';

const plugin: ScannerPlugin = {
  name: 'my-analyzer',
  apiVersion: 1,
  capabilities: ['repository-analyzer'],
  async scan({ repoPath, files }) {
    return {
      nodes: files.map((file) => ({
        factKey: `artifact:${file.path}`,
        filePath: file.path,
        kind: 'symbol',
        type: 'artifact',
        name: file.path,
        metadata: { repoPath }
      })),
      edges: []
    };
  }
};

await scanRepository({ root: '/path/to/repo', plugins: [plugin] });

// Or opt into the bundled Git user attribution enrichment:
await scanRepository({ root: '/path/to/repo', plugins: [AttributionPlugin] });
// The same bundled plugin can also be selected as a convenience option:
await scanRepository({ root: '/path/to/repo', attribution: true });
// The CLI equivalent is: lscg scan --attribution

A separately packaged plugin can be passed to a host application as an imported ScannerPlugin. The copyable external scanner plugin example demonstrates this with a TODO/FIXME analyzer and a host script.

The CLI can load the default export of an external module with the repeatable --plugin option:

lscg scan --root /path/to/repo --plugin @acme/my-scanner-plugin
lscg scan --root /path/to/repo --plugin ./my-scanner-plugin.mjs
lscg watch --root /path/to/repo --plugin ./my-scanner-plugin.mjs

Relative module specifiers are resolved from the current working directory. The module must default-export one ScannerPlugin (an array is also accepted). External plugins are trusted code and run in-process; only load modules you trust. The supplied plugins are used for one-shot scans, watch, and scan --watch.

Plugins may implement initialize, scan, finalize, and dispose hooks. The core assigns deterministic IDs, validates API compatibility, persists plugin facts, and reports plugin diagnostics in the scan summary. Plugins run in-process and must be treated as trusted code.

Plugin output is bounded by conservative host-selected resource limits. The limits cover node and edge fact counts, diagnostics, metadata bytes, and total serialized output bytes. Callers can override them with pluginLimits on scanRepository; an oversized plugin contribution is rejected atomically and reported in the scan summary, while later plugins can still contribute normally.

The host keeps semantic factKey values separate from graph identity. Node facts conflict by (node, factKey) and edge facts by (edge, factKey) across plugins. merge retains both under distinct plugin-qualified identities, replace uses priority (then plugin-name order for ties), and reject keeps the incumbent and reports a diagnostic. The host assigns deterministic IDs; plugins do not provide database IDs. Use lscg edges --type <edge-type> to filter persisted edge types.

Every scan also runs the bundled PackagePlugin. It reads the repository-root package.json dependency sections (dependencies, devDependencies, peerDependencies, and optionalDependencies) and creates repository-scoped package nodes only for package roots used by source imports. A provides edge connects each package node to its matching import node. Undeclared bare external imports create implied package nodes; relative, absolute, URL, and Node built-in imports are excluded. The plugin is de-duplicated if explicitly listed in plugins.

Current parser support

The scanner supports JavaScript, JSX, TypeScript, TSX, Python, and Java via Tree-sitter. Python is handled by the bundled PythonScannerPlugin, and Java by JavaScannerPlugin using the pinned native [email protected] grammar. Java records syntax-grounded classes, interfaces, enums, records, annotation types, methods, constructors, fields, imports, method invocations, containment, and explicit-public export approximations. Java calls/imports are occurrences (no type, overload, constructor, or cross-file resolution), and inherited/module APIs are not modeled. A Java parse error retains the prior file contribution and marks freshness degraded until the file recovers.

Repositories with root or immediate-module pom.xml, build.gradle, or build.gradle.kts files also receive java-dependency package nodes from JavaDependencyPlugin. Manifest reading is bounded and non-executing; only literal Maven coordinates/properties and literal Gradle dependency calls are modeled. Dependencies remain independent from Java imports: no heuristic coordinate matching or dependency-to-import edges are emitted. Native Tree-sitter install scripts must remain allowlisted for clean installs. Cross-file symbol resolution remains a language-specific semantic-enricher concern.