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

@exellix/narrix-detector

v2.0.0

Published

Generic narrative detection pipeline (CNI v1 + narrator mapping v1 + feature packs + rules + signals) for Node.js/TypeScript.

Readme

narrix-detector

A generic, deterministic narrative detection pipeline for Node.js/TypeScript.

It standardizes three things:

  1. CNI v11 — detector input shape: cni (subject + facts + signals + evidence) + scoping (facts and signals from upstream, e.g. scoper)
  2. Narrator Mapping v1 — used outside the detector (e.g. in narrix-scoper) to turn raw objects into CNI
  3. Detection pipeline — rules → signals/narratives (with conflict resolution)

This package is intentionally generic: you bring your domain packs (rules, signal catalog). Normalization (mapping + raw input → CNI) is done by the caller or by a separate package (e.g. narrix-scoper).


Install

npm i @exellix/narrix-detector

Package name: @exellix/narrix-detector (scoped npm package)

Optional peers (only needed if you want adapters):

npm i nx-functions nx-rules

Quick start

Detector input is CNI v11 + scoping. Build CniV11 and scoping: { facts, signals } yourself or use mappingToCni + toDetectorInput when you still have raw input + mapping.

import {
  createNarrativeEngine,
  toDetectorInput,
  mappingToCni,
  type CniV11,
  type CniFactV11,
  type CniSignalV11,
  type SignalsCatalogV1,
  type RulePackV1,
} from "@exellix/narrix-detector";

const signals: SignalsCatalogV1 = {
  schema: "signals.catalog.v1",
  signals: {
    SIG_PUBLIC_EXPOSED: {
      code: "SIG_PUBLIC_EXPOSED",
      title: "Public exposure",
      severityDefault: "medium",
      description: "Subject appears to be exposed to untrusted scope.",
      evidence: { expected: ["path:isPublic", "path:ingress.pathCount"] }
    }
  }
};

const rules: RulePackV1 = {
  schema: "rules.pack.v1",
  packId: "subnet.rules.v1",
  version: "1.0.0",
  rules: [
    {
      ruleId: "subnet-public-exposed",
      when: { eq: [{ path: "baggage.subnet.isPublic" }, { const: true }] },
      emit: {
        signals: [
          { code: "SIG_PUBLIC_EXPOSED", severity: "medium", evidence: [{ path: "isPublic" }] }
        ],
        narratives: [{ narrativeTypeId: "subnet.exposure.public-facing", confidence: 0.8 }]
      }
    }
  ]
};

const engine = createNarrativeEngine({
  signalsCatalog: signals,
  rulePacks: [rules],
});

// Example: normalize elsewhere (e.g. mappingToCni), then run detector
const mapping = { /* NarratorMappingV1 */ };
const rawInput = { id: "subnet-123", name: "Subnet A", cidr: "10.0.1.0/24", isPublic: true };
const { cni } = await mappingToCni(mapping, rawInput);
cni.baggage = cni.baggage ?? {};
(cni.baggage as any).subnet = { isPublic: rawInput.isPublic };

const result = await engine.evaluate(toDetectorInput(cni));
console.log(result.signals);
console.log(result.narratives);

Or pass a pre-built CNI v11 + scoping (e.g. from narrix-scoper):

const detectorInput = {
  cni: myCniV11,   // CniV11
  scoping: {
    facts: myFacts,   // CniFactV11[]
    signals: mySignals // CniSignalV11[]
  }
};
const result = await engine.evaluate(detectorInput);

Detector input

The detector accepts a single shape:

  • cniCniV11 (subject, meta, optional facts/signals/narratives, baggage, etc.)
  • scoping{ facts: CniFactV11[]; signals: CniSignalV11[] } (facts and signals from upstream scope; merged with cni during evaluation)

Use toDetectorInput(cniV1, scoping?) to build this from a CniV1 (e.g. from mappingToCni). Normalization (raw input + mapping → CNI) is not done inside the detector; use mappingToCni in your pipeline or get CNI from narrix-scoper.


Relationship to engine and outcome classification

  • Story contracts — This package defines the canonical shapes for narratives and CNI (e.g. CniNarrativeV1, narrativeTypeId, confidence, evidence, meta). Engine and consumers can rely on these types when integrating with narrix-detector.
  • Outcome classification — Mapping narrativeTypeId to outcome labels (e.g. outcomes[narrativeTypeId]) is engine-owned. The classifier pack format and outcome layer live in the engine (or consumer). narrix-detector does not define or require an outcome/classifier pack; it only emits narratives from rules.
  • Optional dependency — For runtimes that do not use narrix-detector for story contracts or pipeline, this package is optional (documentation-only). When the engine or consumers rely on CNI/narrative contracts or the detection pipeline, add @exellix/narrix-detector as a dependency and align with this repo’s specs and .reports checklists.

Important contracts (pack authors)

Feature → Signal boundary

Features do not emit signals. Features can:

  • compute derived values and write them to baggage.*
  • add “candidates” to baggage.candidates.* (optional pattern)

Only rules emit signals (and narratives). The detector does not run mapping-declared features; run them before calling the detector. See .reports/gap-analysis-detector-input-v11.md.

Conflict resolution scope (default)

Conflicts are resolved per (itemId + subject.id + signalKey). Default merge:

  • keep max severity
  • union evidence pointers
  • union tags

You can override merge policy in createNarrativeEngine({ conflictPolicy }).


Advanced Features

Generalization (Semantic Layer)

Map source-specific paths to generic identifiers to write rules once:

"semantics": {
  "$.risk.score": { "path": "metrics.threat_level" }
}

Rules can then use when: { gt: [{ path: "$.risk.score" }, { const: 8 }] }.

Flexible Mappings

  • _iterate: Process arrays to generate multiple facts/bullets.
  • _condition: conditional logic (equals, startsWith, gte, etc.).
  • bullets: Organize qualitative summaries into named sections.
  • recommendations: Dynamic advice based on direct paths or conditional lists.

See SPEC.md for the full format specification.


Adapters

nx-functions adapter

import { createNxFunctionsRegistryAdapter } from "@exellix/narrix-detector/adapters/nx-functions";

nx-rules adapter

import { createNxRulesRuleEngineAdapter } from "@exellix/narrix-detector/adapters/nx-rules";

Version history

See CHANGELOG.md for release notes and contract-version compatibility.

License

MIT