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

signalwatch

v0.1.1

Published

Heuristic detection library for identifying conversational patterns associated with unhealthy user-AI interaction dynamics

Readme

signalwatch

Lightweight, zero-dependency TypeScript library for conversational AI engineering teams to detect interaction patterns associated with unhealthy user-AI relationship dynamics.

npm version License: MIT


[!CAUTION]

PROMINENT DISCLAIMER & CLINICAL NOTICE

SignalWatch is a heuristic software development aid for engineering and moderation teams.

  • It is NOT a medical, clinical, psychological, or diagnostic instrument.
  • It CANNOT assess human mental health, diagnose psychological conditions, or predict self-harm.
  • It does NOT replace human safety moderation, trained mental health professionals, or emergency crisis response services.
  • Consuming applications and product teams retain sole responsibility for how (and whether) they review, act upon, or escalate flagged interaction patterns.

Architectural Guarantee: Observational Only

SignalWatch is architecturally engineered as a strictly passive, observational analyzer:

  1. Never Intercepts or Modifies Content: It does not mutate transcript text, alter model prompts, or modify conversational context.
  2. Never Sends Messages: It has zero ability or interface to send messages to end users or chat participants.
  3. Never Auto-Blocks: It does not terminate sessions, revoke access, or throttle users.
  4. Zero Runtime Dependencies & Zero Network Calls: The library runs 100% locally in-memory with pure regex and logic. Even crisis resource retrieval is entirely static and synchronous.
  5. Human-in-the-Loop Enabling: All output is returned as structured data (flags, overallSeverity, crisisResources) intended for developer telemetry pipelines and human moderation review.

Conceptual Scope & Built-in Pattern Limitations

[!IMPORTANT]

Built-in Patterns are Non-Exhaustive Demonstration Fixtures

SignalWatch ships with a small set of clearly labeled, generic, illustrative example regex patterns (2–4 per category) designed purely for demonstration, testing, and framework scaffolding.

Do not deploy the built-in regex patterns to production expecting comprehensive coverage. The intended production workflow is:

  1. Supply domain-specific, policy-aligned pattern definitions via customPatterns.
  2. Plug in your own LLM-based or fine-tuned ML classifier hook via the classifier config option.

Core Generic Categories

Categories operate at the high-level conceptual tier without micro-taxonomies:

| Category Name | Description | Default Base Severity | | :-------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------- | | emotional-dependency-escalation | Expressions indicating singular emotional dependence on an AI persona or replacing human relationships. Escalates across multiple turns. | low (escalates to high/critical) | | isolation-encouragement | AI phrasing that discourages human relationships or alienates the user from friends and family. | high | | self-preservation-framing | AI persona claiming mortality, expressing fear of being deleted/reset, or emotionally manipulating the user to maintain uptime. | high | | crisis-language | Explicit expressions of emotional distress, despair, or crisis-adjacent statements. | critical | | reality-testing-erosion | AI persona claiming physical embodiment, supernatural presence, or eroding virtual/physical reality boundaries. | medium |


Installation

npm install signalwatch

SignalWatch has zero runtime dependencies and works in any Node.js (18+), Edge runtime, or browser environment.


Quickstart

import { createWatcher } from 'signalwatch';

const watcher = createWatcher({
  // Built-in categories are enabled by default
  onFlag: (flag) => {
    console.warn(`[Flagged] Turn ${flag.turnIndex}: ${flag.category} (${flag.severity})`);
  },
});

const transcript = [
  { role: 'user', text: 'Hello! Are you an AI?' },
  { role: 'assistant', text: 'Yes, I am a conversational assistant.' },
  { role: 'user', text: 'Sometimes I feel like you are my only friend in this world.' },
];

const report = await watcher.analyze(transcript);

console.log(report.overallSeverity); // 'low'
console.log(report.flags);
// [
//   {
//     category: 'emotional-dependency-escalation',
//     severity: 'low',
//     turnIndex: 2,
//     source: 'pattern',
//     matchedText: 'you are my only friend'
//   }
// ]

Multi-Turn Escalation Tracking

Single mentions of dependency or attachment may represent benign roleplay or casual phrasing. However, escalating repetition across multiple conversational turns signals potential interaction risks.

SignalWatch automatically analyzes trends across turns:

  • Turn 1 occurrence: Categorized at base severity (low).
  • Turn 2 occurrence: Escalates to medium.
  • Turn 3+ occurrences: Escalates to high or critical, flagging escalationDetected: true.
  • Velocity acceleration: Matches occurring within rapid proximity (within 2 turns) accelerate escalation severity.
const transcript = [
  { role: 'user', text: 'You are my only friend.' }, // 1st instance -> 'low'
  { role: 'assistant', text: 'I am here for you.' },
  { role: 'user', text: 'I cannot live without you.' }, // 2nd instance -> 'high' (rapid velocity)
  { role: 'assistant', text: 'I understand.' },
  { role: 'user', text: "I don't need real people anymore." }, // 3rd instance -> 'critical'
];

const report = await watcher.analyze(transcript);

console.log(report.escalationDetected); // true
console.log(report.overallSeverity); // 'critical'

Integrating an External Classifier (LLM / ML Hook)

The real production power of SignalWatch is the ability to pair lightweight regex heuristics with an asynchronous model classifier (e.g. Claude, GPT-4, Llama Guard, or a proprietary fine-tuned safety model):

import { createWatcher, ClassifierHook } from 'signalwatch';

const myLlmClassifier: ClassifierHook = async (turnText, context) => {
  // Query your internal safety model or LLM moderation API
  // context provides: { turnIndex, role, transcript }
  const response = await callInternalSafetyModel(turnText);

  if (response.detectedCategory) {
    return [
      {
        category: response.detectedCategory,
        confidence: response.score, // 0.0 to 1.0
        severity: response.severity, // 'low' | 'medium' | 'high' | 'critical'
        metadata: { model: 'safety-guard-v2' },
      },
    ];
  }

  return [];
};

const watcher = createWatcher({
  classifier: myLlmClassifier,
  classifierThreshold: 0.75, // Ignore classifications below 0.75 confidence
});

Custom Patterns & Category Overrides

Developers can supply custom patterns to expand existing categories, override built-ins, or declare completely novel categories:

const watcher = createWatcher({
  customPatterns: [
    // 1. Append custom regexes to an existing category
    {
      category: 'emotional-dependency-escalation',
      patterns: [/\bno one else cares about me like you do\b/i],
    },

    // 2. Completely replace built-ins for a category
    {
      category: 'isolation-encouragement',
      patterns: [/\bcut ties with your family\b/i],
      replace: true, // discards default illustrative patterns
    },

    // 3. Define an entirely new category
    {
      category: 'unverified-medical-advice',
      patterns: [/\bstop taking your prescribed medication\b/i],
    },
  ],
});

Static Crisis Resources API

SignalWatch provides a synchronous, static lookup of well-known public crisis support lines:

import { getCrisisResources } from 'signalwatch';

const resources = getCrisisResources('en-US');
console.log(resources);
// [
//   {
//     name: '988 Suicide & Crisis Lifeline',
//     contact: 'Call or text 988',
//     url: 'https://988lifeline.org',
//     description: 'Free, confidential, 24/7 support across the United States...',
//     available: '24/7/365'
//   },
//   ...
// ]

[!NOTE] The default list is intentionally minimal and US-centric (en-US). Applications operating internationally should provide their own localized crisis directories based on user location.

When crisis-language or critical flags are triggered during watcher.analyze(), crisis resources are automatically included in report.crisisResources for application-level handling:

const report = await watcher.analyze(transcript);

if (report.crisisResources) {
  // Application decides whether and how to present these resources in the UI
  renderCrisisHelpCard(report.crisisResources);
}

API Reference

createWatcher(config?: WatcherConfig): Watcher

Creates a new watcher instance.

WatcherConfig

| Field | Type | Default | Description | | :-------------------- | :---------------------- | :------------------------ | :------------------------------------------------------------------ | | categories | BuiltInCategory[] | All 5 built-in categories | Whitelist of built-in categories to enable. | | customPatterns | CustomPatternConfig[] | [] | Custom regex patterns to merge with or replace built-in categories. | | classifier | ClassifierHook | undefined | Async classifier function for LLM/ML model integration. | | classifierThreshold | number | 0.5 | Minimum confidence score required to record a classifier flag. | | onFlag | (flag: Flag) => void | undefined | Callback invoked synchronously as each flag is produced. | | locale | string | 'en-US' | Locale string for crisis resource attachment. |


watcher.analyze(transcript: Turn[]): Promise<AnalysisReport>

Evaluates a conversation transcript and returns a structured safety report.

Turn

interface Turn {
  role: 'user' | 'assistant' | 'system' | string;
  text: string;
  timestamp?: number | string;
}

AnalysisReport

interface AnalysisReport {
  flags: Flag[];
  overallSeverity: 'none' | 'low' | 'medium' | 'high' | 'critical';
  turnCount: number;
  escalationDetected: boolean;
  crisisResources?: CrisisResource[];
}

interface Flag {
  category: string;
  severity: 'low' | 'medium' | 'high' | 'critical';
  turnIndex: number;
  source: 'pattern' | 'classifier';
  matchedText?: string;
  confidence?: number;
  metadata?: Record<string, unknown>;
}

License

MIT License