Skip to content
Content
Hook

Hooks

What mohamedabdallah-14-unslop runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.

From plugin
mohamedabdallah-14-unslop
1376 skills9 hooks
Install
> /plugin marketplace add MohamedAbdallah-14/unslop
> /plugin install unslop@unslop-marketplace

Ships with mohamedabdallah-14-unslop. Installing the plugin gets these hooks.

Where it lives

  • hooks/install.ps1GitHub
    Read the script
    # Install unslop hooks into Claude Code's user settings on Windows.
    #
    # Copies hook scripts into $CLAUDE_CONFIG_DIR/hooks/ (or ~/.claude/hooks/)
    # and registers SessionStart + UserPromptSubmit hooks in settings.json.
    # Also wires the statusline so the [unslop] badge shows when active.
    #
    # Supports: -Force to overwrite existing hooks.
    # Requires: PowerShell 5.1+, Node.js
    
    param([switch]$Force)
    
    $ErrorActionPreference = 'Stop'
    
    $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
    $ClaudeDir = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { Join-Path $HOME '.claude' }
    $HooksDir  = Join-Path $ClaudeDir 'hooks'
    $Settings  = Join-Path $ClaudeDir 'settings.json'
    
    if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
      Write-Error "Error: 'node' not found on PATH. Install Node.js from https://nodejs.org"
      exit 1
    }
    
    $HookFiles = @('package.json', 'unslop-config.js', 'unslop-activate.js', 'unslop-mode-tracker.js', 'unslop-statusline.ps1')
    
    # Check if already installed (unless -Force)
    if (-not $Force) {
      $allPresent = $true
      foreach ($hook in $HookFiles) {
        if (-not (Test-Path (Join-Path $HooksDir $hook))) {
          $allPresent = $false
          break
        }
      }
      if ($allPresent -and (Test-Path $Settings)) {
        Write-Host "Unslop hooks already installed in $HooksDir"
        Write-Host "  Re-run with -Force to overwrite."
        exit 0
      }
    }
    
    if ($Force -and (Test-Path (Join-Path $HooksDir 'unslop-activate.js'))) {
      Write-Host "Reinstalling unslop hooks (-Force)..."
    } else {
      Write-Host "Installing unslop hooks..."
    }
    
    New-Item -ItemType Directory -Force -Path $HooksDir | Out-Null
    
    foreach ($hook in $HookFiles) {
      $src = Join-Path $ScriptDir $hook
      $dst = Join-Path $HooksDir $hook
      if (Test-Path $src) {
        Copy-Item -Force $src $dst
      } else {
        $url = "https://raw.githubusercontent.com/MohamedAbdallah-14/unslop/main/hooks/$hook"
        Invoke-WebRequest -Uri $url -OutFile $dst
      }
      Write-Host "  Installed: $dst"
    }
    
    if (-not (Test-Path $Settings)) { '{}' | Out-File -Encoding utf8 $Settings }
    
    # Back up settings before modifying
    Copy-Item $Settings "$Settings.bak"
    
    $env:UNSLOP_SETTINGS = $Settings
    $env:UNSLOP_HOOKS_DIR = $HooksDir
    
    node -e @"
      const fs = require('fs');
      const settingsPath = process.env.UNSLOP_SETTINGS;
      const hooksDir = process.env.UNSLOP_HOOKS_DIR;
      const managedStatusLinePath = hooksDir + '/unslop-statusline.ps1';
      const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
      if (!settings.hooks) settings.hooks = {};
    
      if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
      const hasStart = settings.hooks.SessionStart.some(e =>
        e.hooks && e.hooks.some(h => h.command && h.command.includes('unslop'))
      );
      if (!hasStart) {
        settings.hooks.SessionStart.push({
          hooks: [{
            type: 'command',
            command: 'node \"' + hooksDir + '/unslop-activate.js\"',
            timeout: 5,
            statusMessage: 'Loading unslop mode...'
          }]
        });
      }
    
      if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
      const hasPrompt = settings.hooks.UserPromptSubmit.some(e =>
        e.hooks && e.hooks.some(h => h.command && h.command.includes('unslop'))
      );
      if (!hasPrompt) {
        settings.hooks.UserPromptSubmit.push({
          hooks: [{
            type: 'command',
            command: 'node \"' + hooksDir + '/unslop-mode-tracker.js\"',
            timeout: 5,
            statusMessage: 'Tracking unslop mode...'
          }]
        });
      }
    
      if (!settings.statusLine) {
        settings.statusLine = {
          type: 'command',
          command: 'powershell -ExecutionPolicy Bypass -File \"' + managedStatusLinePath + '\"'
        };
        console.log('  Statusline badge configured.');
      } else {
        const cmd = typeof settings.statusLine === 'string'
          ? settings.statusLine
          : (settings.statusLine.command || '');
        if (cmd.includes(managedStatusLinePath)) {
          console.log('  Statusline badge already configured.');
        } else {
          console.log('  NOTE: Existing statusline detected - unslop badge NOT added.');
        }
      }
    
      fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
      console.log('  Hooks wired in settings.json');
    "@
    
    Write-Host ""
    Write-Host "Done! Restart Claude Code to activate."
    Write-Host ""
    Write-Host "What's installed:"
    Write-Host "  - SessionStart hook: auto-loads unslop rules every session"
    Write-Host "  - Mode tracker hook: updates statusline badge when you switch modes"
    Write-Host "  - Statusline badge: shows [unslop] or [unslop:FULL] etc."
    
  • hooks/install.shGitHub
    Read the script
    #!/bin/bash
    # unslop — one-command hook installer for Claude Code
    # Installs: SessionStart hook (auto-load rules) + UserPromptSubmit hook (mode tracking)
    # Usage: bash hooks/install.sh
    #   or:  bash hooks/install.sh --force   (re-install over existing hooks)
    set -e
    
    FORCE=0
    for arg in "$@"; do
      case "$arg" in
        --force|-f) FORCE=1 ;;
      esac
    done
    
    case "$OSTYPE" in
      msys*|cygwin*|mingw*)
        echo "WARNING: Running on Windows ($OSTYPE)."
        echo "         This script works in Git Bash/MSYS but symlinks may require"
        echo "         Developer Mode or admin privileges."
        echo "         If you installed via 'claude plugin install', you don't need this script."
        echo ""
        ;;
    esac
    
    if ! command -v node >/dev/null 2>&1; then
      echo "ERROR: 'node' is required to install the unslop hooks (used to merge"
      echo "       the hook config into ~/.claude/settings.json safely)."
      echo "       Install Node.js from https://nodejs.org and re-run this script."
      exit 1
    fi
    
    CLAUDE_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
    HOOKS_DIR="$CLAUDE_DIR/hooks"
    SETTINGS="$CLAUDE_DIR/settings.json"
    REPO_URL="https://raw.githubusercontent.com/MohamedAbdallah-14/unslop/main/hooks"
    
    HOOK_FILES=("package.json" "unslop-config.js" "unslop-activate.js" "unslop-mode-tracker.js" "unslop-statusline.sh")
    
    SCRIPT_DIR=""
    if [ -n "${BASH_SOURCE[0]:-}" ] && [ -f "${BASH_SOURCE[0]}" ]; then
      SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
    fi
    
    # Check if already installed (unless --force)
    ALREADY_INSTALLED=0
    if [ "$FORCE" -eq 0 ]; then
      ALL_FILES_PRESENT=1
      for hook in "${HOOK_FILES[@]}"; do
        if [ ! -f "$HOOKS_DIR/$hook" ]; then
          ALL_FILES_PRESENT=0
          break
        fi
      done
    
      HOOKS_WIRED=0
      HAS_STATUSLINE=0
      if [ "$ALL_FILES_PRESENT" -eq 1 ] && [ -f "$SETTINGS" ]; then
        if UNSLOP_SETTINGS="$SETTINGS" node -e "
          const fs = require('fs');
          const settings = JSON.parse(fs.readFileSync(process.env.UNSLOP_SETTINGS, 'utf8'));
          const hasUnslopHook = (event) =>
            Array.isArray(settings.hooks?.[event]) &&
            settings.hooks[event].some(e =>
              e.hooks && e.hooks.some(h => h.command && h.command.includes('unslop'))
            );
          process.exit(
            hasUnslopHook('SessionStart') &&
            hasUnslopHook('UserPromptSubmit') &&
            !!settings.statusLine
              ? 0
              : 1
          );
        " >/dev/null 2>&1; then
          HOOKS_WIRED=1
          HAS_STATUSLINE=1
        fi
      fi
    
      if [ "$ALL_FILES_PRESENT" -eq 1 ] && [ "$HOOKS_WIRED" -eq 1 ] && [ "$HAS_STATUSLINE" -eq 1 ]; then
        ALREADY_INSTALLED=1
        echo "Unslop hooks already installed in $HOOKS_DIR"
        echo "  Re-run with --force to overwrite: bash hooks/install.sh --force"
        echo ""
      fi
    fi
    
    if [ "$ALREADY_INSTALLED" -eq 1 ] && [ "$FORCE" -eq 0 ]; then
      echo "Nothing to do. Hooks are already in place."
      exit 0
    fi
    
    if [ "$FORCE" -eq 1 ] && [ -f "$HOOKS_DIR/unslop-activate.js" ]; then
      echo "Reinstalling unslop hooks (--force)..."
    else
      echo "Installing unslop hooks..."
    fi
    
    mkdir -p "$HOOKS_DIR"
    
    for hook in "${HOOK_FILES[@]}"; do
      if [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/$hook" ]; then
        cp "$SCRIPT_DIR/$hook" "$HOOKS_DIR/$hook"
      else
        curl -fsSL "$REPO_URL/$hook" -o "$HOOKS_DIR/$hook"
      fi
      echo "  Installed: $HOOKS_DIR/$hook"
    done
    
    chmod +x "$HOOKS_DIR/unslop-statusline.sh"
    
    if [ ! -f "$SETTINGS" ]; then
      echo '{}' > "$SETTINGS"
    fi
    
    # Back up existing settings.json before touching it
    cp "$SETTINGS" "$SETTINGS.bak"
    
    UNSLOP_SETTINGS="$SETTINGS" UNSLOP_HOOKS_DIR="$HOOKS_DIR" node -e "
      const fs = require('fs');
      const settingsPath = process.env.UNSLOP_SETTINGS;
      const hooksDir = process.env.UNSLOP_HOOKS_DIR;
      const managedStatusLinePath = hooksDir + '/unslop-statusline.sh';
      const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
      if (!settings.hooks) settings.hooks = {};
    
      if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
      const hasStart = settings.hooks.SessionStart.some(e =>
        e.hooks && e.hooks.some(h => h.command && h.command.includes('unslop'))
      );
      if (!hasStart) {
        settings.hooks.SessionStart.push({
          hooks: [{
            type: 'command',
            command: 'node \"' + hooksDir + '/unslop-activate.js\"',
            timeout: 5,
            statusMessage: 'Loading unslop mode...'
          }]
        });
      }
    
      if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
      const hasPrompt = settings.hooks.UserPromptSubmit.some(e =>
        e.hooks && e.hooks.some(h => h.command && h.command.includes('unslop'))
      );
      if (!hasPrompt) {
        settings.hooks.UserPromptSubmit.push({
          hooks: [{
            type: 'command',
            command: 'node \"' + hooksDir + '/unslop-mode-tracker.js\"',
            timeout: 5,
            statusMessage: 'Tracking unslop mode...'
          }]
        });
      }
    
      if (!settings.statusLine) {
        settings.statusLine = {
          type: 'command',
          command: 'bash \"' + managedStatusLinePath + '\"'
        };
        console.log('  Statusline badge configured.');
      } else {
        const cmd = typeof settings.statusLine === 'string'
          ? settings.statusLine
          : (settings.statusLine.command || '');
        if (cmd.includes(managedStatusLinePath)) {
          console.log('  Statusline badge already configured.');
        } else {
          console.log('  NOTE: Existing statusline detected — unslop badge NOT added.');
          console.log('        See hooks/README.md to add the badge to your existing statusline.');
        }
      }
    
      fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
      console.log('  Hooks wired in settings.json');
    "
    
    echo ""
    echo "Done! Restart Claude Code to activate."
    echo ""
    echo "What's installed:"
    echo "  - SessionStart hook: auto-loads unslop rules every session"
    echo "  - Mode tracker hook: updates statusline badge when you switch modes"
    echo "    (/unslop subtle, /unslop full, /unslop-commit, etc.)"
    echo "  - Statusline badge: shows [unslop] or [unslop:FULL] etc."
    
  • hooks/uninstall.ps1GitHub
    Read the script
    # unslop — uninstall hook files and remove settings.json entries
    # Usage: pwsh hooks/uninstall.ps1
    
    $ErrorActionPreference = 'Stop'
    
    $ClaudeDir = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { Join-Path $HOME '.claude' }
    $HooksDir  = Join-Path $ClaudeDir 'hooks'
    $Settings  = Join-Path $ClaudeDir 'settings.json'
    $Flag      = Join-Path $ClaudeDir '.unslop-active'
    
    $HookFiles = @('package.json', 'unslop-config.js', 'unslop-activate.js', 'unslop-mode-tracker.js', 'unslop-statusline.ps1')
    
    Write-Host "Uninstalling unslop hooks..."
    
    foreach ($hook in $HookFiles) {
      $target = Join-Path $HooksDir $hook
      if (Test-Path $target) {
        Remove-Item $target -Force
        Write-Host "  Removed: $target"
      }
    }
    
    if (Test-Path $Flag) {
      Remove-Item $Flag -Force
      Write-Host "  Removed flag file: $Flag"
    }
    
    if ((Test-Path $Settings) -and (Get-Command node -ErrorAction SilentlyContinue)) {
      Copy-Item $Settings "$Settings.bak"
    
      $env:UNSLOP_SETTINGS = $Settings
      node -e @"
        const fs = require('fs');
        const settingsPath = process.env.UNSLOP_SETTINGS;
        const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
    
        if (settings.hooks) {
          for (const event of ['SessionStart', 'UserPromptSubmit']) {
            if (Array.isArray(settings.hooks[event])) {
              settings.hooks[event] = settings.hooks[event].filter(e =>
                !(e.hooks && e.hooks.some(h => h.command && h.command.includes('unslop')))
              );
              if (settings.hooks[event].length === 0) delete settings.hooks[event];
            }
          }
          if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
        }
    
        if (settings.statusLine) {
          const cmd = typeof settings.statusLine === 'string'
            ? settings.statusLine
            : (settings.statusLine.command || '');
          if (cmd.includes('unslop-statusline')) {
            delete settings.statusLine;
            console.log('  Removed statusline config.');
          }
        }
    
        fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
        console.log('  Cleaned settings.json');
    "@
    } else {
      Write-Host "  Skipped settings.json cleanup (node not found or settings.json missing)."
    }
    
    Write-Host ""
    Write-Host "Done. Restart Claude Code to complete removal."
    
  • hooks/uninstall.shGitHub
    Read the script
    #!/bin/bash
    # unslop — uninstall hook files and remove settings.json entries
    # Usage: bash hooks/uninstall.sh
    set -e
    
    CLAUDE_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
    HOOKS_DIR="$CLAUDE_DIR/hooks"
    SETTINGS="$CLAUDE_DIR/settings.json"
    FLAG="$CLAUDE_DIR/.unslop-active"
    
    HOOK_FILES=("package.json" "unslop-config.js" "unslop-activate.js" "unslop-mode-tracker.js" "unslop-statusline.sh")
    
    echo "Uninstalling unslop hooks..."
    
    for hook in "${HOOK_FILES[@]}"; do
      target="$HOOKS_DIR/$hook"
      if [ -f "$target" ]; then
        rm "$target"
        echo "  Removed: $target"
      fi
    done
    
    if [ -f "$FLAG" ]; then
      rm "$FLAG"
      echo "  Removed flag file: $FLAG"
    fi
    
    if [ -f "$SETTINGS" ] && command -v node >/dev/null 2>&1; then
      cp "$SETTINGS" "$SETTINGS.bak"
    
      UNSLOP_SETTINGS="$SETTINGS" node -e "
        const fs = require('fs');
        const settingsPath = process.env.UNSLOP_SETTINGS;
        const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
    
        if (settings.hooks) {
          for (const event of ['SessionStart', 'UserPromptSubmit']) {
            if (Array.isArray(settings.hooks[event])) {
              settings.hooks[event] = settings.hooks[event].filter(e =>
                !(e.hooks && e.hooks.some(h => h.command && h.command.includes('unslop')))
              );
              if (settings.hooks[event].length === 0) delete settings.hooks[event];
            }
          }
          if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
        }
    
        if (settings.statusLine) {
          const cmd = typeof settings.statusLine === 'string'
            ? settings.statusLine
            : (settings.statusLine.command || '');
          if (cmd.includes('unslop-statusline')) {
            delete settings.statusLine;
            console.log('  Removed statusline config.');
          }
        }
    
        fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
        console.log('  Cleaned settings.json');
      "
    else
      echo "  Skipped settings.json cleanup (node not found or settings.json missing)."
    fi
    
    echo ""
    echo "Done. Restart Claude Code to complete removal."
    
  • hooks/unslop-activate.jsGitHub
    Read the script
    #!/usr/bin/env node
    // unslop — Claude Code SessionStart activation hook
    //
    // Runs on every session start:
    //   1. Writes flag file at $CLAUDE_CONFIG_DIR/.unslop-active (statusline reads this)
    //   2. Emits unslop ruleset as hidden SessionStart context
    //   3. Detects missing statusline config and emits setup nudge
    
    const fs = require('fs');
    const path = require('path');
    const os = require('os');
    const {
      getDefaultMode, safeWriteFlag, getFlagPath,
      getTurnCounterPath, resetTurnCount,
    } = require('./unslop-config');
    
    const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
    const flagPath = getFlagPath();
    const counterPath = getTurnCounterPath();
    const settingsPath = path.join(claudeDir, 'settings.json');
    
    const mode = getDefaultMode();
    
    // Persona-drift reset: a new session always starts at turn 0. RMTBench /
    // HorizonBench report that long contexts accumulate drift; the counter is
    // only meaningful within a single session, so we zero it here.
    resetTurnCount(counterPath);
    
    if (mode === 'off') {
      try { fs.unlinkSync(flagPath); } catch (e) {}
      process.stdout.write('OK');
      process.exit(0);
    }
    
    safeWriteFlag(flagPath, mode);
    
    // Independent modes have their own skill files — don't emit the full ruleset.
    const INDEPENDENT_MODES = new Set(['commit', 'review']);
    
    if (INDEPENDENT_MODES.has(mode)) {
      process.stdout.write('UNSLOP MODE ACTIVE — level: ' + mode + '. Behavior defined by /unslop-' + mode + ' skill.');
      process.exit(0);
    }
    
    // Read SKILL.md — the single source of truth for unslop behavior.
    // Plugin installs: __dirname = <plugin_root>/hooks/, SKILL.md at <plugin_root>/skills/unslop/SKILL.md
    // Standalone installs: __dirname = $CLAUDE_CONFIG_DIR/hooks/, SKILL.md won't exist — falls back to activation rule then hardcoded rules.
    let skillContent = '';
    try {
      skillContent = fs.readFileSync(
        path.join(__dirname, '..', 'skills', 'unslop', 'SKILL.md'), 'utf8'
      );
    } catch (e) { /* try activation rule next */ }
    
    // Fallback: try the activation rule file (lighter weight than full SKILL.md)
    let activationRule = '';
    if (!skillContent) {
      try {
        activationRule = fs.readFileSync(
          path.join(__dirname, '..', 'rules', 'unslop-activate.md'), 'utf8'
        ).trim();
      } catch (e) { /* will use hardcoded fallback */ }
    }
    
    let output;
    
    if (skillContent) {
      const body = skillContent.replace(/^---[\s\S]*?---\s*/, '');
    
      // Filter intensity table and examples to the active level
      const filtered = body.split('\n').reduce((acc, line) => {
        const tableRowMatch = line.match(/^\|\s*\*\*(\S+?)\*\*\s*\|/);
        if (tableRowMatch) {
          if (tableRowMatch[1] === mode) {
            acc.push(line);
          }
          return acc;
        }
    
        const exampleMatch = line.match(/^- (\S+?):\s/);
        if (exampleMatch) {
          if (exampleMatch[1] === mode) {
            acc.push(line);
          }
          return acc;
        }
    
        acc.push(line);
        return acc;
      }, []);
    
      output = 'UNSLOP MODE ACTIVE — level: ' + mode + '\n\n' + filtered.join('\n');
    } else if (activationRule) {
      output = 'UNSLOP MODE ACTIVE — level: ' + mode + '\n\n' + activationRule;
    } else {
      output =
        'UNSLOP MODE ACTIVE — level: ' + mode + '\n\n' +
        'Write like a careful human. All technical substance stays exact. Only AI-slop dies.\n\n' +
        '## Persistence\n\n' +
        'ACTIVE EVERY RESPONSE. No revert after many turns. No drift back into AI-template English.\n' +
        'Off only: "stop unslop" / "normal mode".\n\n' +
        'Current level: **' + mode + '**. Switch: `/unslop subtle|balanced|full|voice-match|anti-detector`.\n\n' +
        '## Rules\n\n' +
        'Drop: sycophancy ("great question", "I\'d be happy to"), stock vocab (delve/tapestry/testament/seamless/holistic/leverage-as-filler), ' +
        'hedging stacks ("it\'s important to note that"), tricolon padding, em-dash pileups, performative balance, tidy five-paragraph shapes.\n\n' +
        'Keep: technical terms exact, code unchanged, real uncertainty when honest.\n' +
        'Engineer burstiness: mix short and long sentences deliberately.\n\n' +
        'Pattern: [concrete observation]. [why]. [what to do next].\n\n' +
        '## Auto-Clarity\n\n' +
        'Drop unslop style for: security warnings, irreversible actions, legal/medical/financial precision, user confused. Resume after.\n\n' +
        '## Boundaries\n\n' +
        'Code/commits/PRs: write normal. "stop unslop" or "normal mode": revert. Level persists until changed or session ends.';
    }
    
    // Detect missing statusline config — nudge Claude to help set it up
    try {
      let hasStatusline = false;
      if (fs.existsSync(settingsPath)) {
        const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
        if (settings.statusLine) {
          hasStatusline = true;
        }
      }
    
      if (!hasStatusline) {
        const isWindows = process.platform === 'win32';
        const scriptName = isWindows ? 'unslop-statusline.ps1' : 'unslop-statusline.sh';
        const scriptPath = path.join(__dirname, scriptName);
        const command = isWindows
          ? `powershell -ExecutionPolicy Bypass -File "${scriptPath}"`
          : `bash "${scriptPath}"`;
        const statusLineSnippet =
          '"statusLine": { "type": "command", "command": ' + JSON.stringify(command) + ' }';
        output += "\n\n" +
          "STATUSLINE SETUP NEEDED: The unslop plugin includes a statusline badge showing active mode " +
          "(e.g. [unslop], [unslop:full]). It is not configured yet. " +
          "To enable, add this to " + path.join(claudeDir, 'settings.json') + ": " +
          statusLineSnippet + " " +
          "Proactively offer to set this up for the user on first interaction.";
      }
    } catch (e) {
      // Silent fail — don't block session start over statusline detection
    }
    
    process.stdout.write(output);
    
  • hooks/unslop-config.jsGitHub
    Read the script
    #!/usr/bin/env node
    // unslop — shared configuration resolver
    //
    // Resolution order for default mode:
    //   1. UNSLOP_DEFAULT_MODE environment variable
    //   2. Config file defaultMode field:
    //      - $XDG_CONFIG_HOME/unslop/config.json (any platform, if set)
    //      - ~/.config/unslop/config.json (macOS / Linux fallback)
    //      - %APPDATA%\unslop\config.json (Windows fallback)
    //   3. 'balanced'
    
    const fs = require('fs');
    const path = require('path');
    const os = require('os');
    
    const VALID_MODES = [
      'off', 'subtle', 'balanced', 'full',
      'voice-match', 'anti-detector',
      'commit', 'review'
    ];
    
    function getConfigDir() {
      if (process.env.XDG_CONFIG_HOME) {
        return path.join(process.env.XDG_CONFIG_HOME, 'unslop');
      }
      if (process.platform === 'win32') {
        return path.join(
          process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
          'unslop'
        );
      }
      return path.join(os.homedir(), '.config', 'unslop');
    }
    
    function getConfigPath() {
      return path.join(getConfigDir(), 'config.json');
    }
    
    function getDefaultMode() {
      const envMode = process.env.UNSLOP_DEFAULT_MODE;
      if (envMode && VALID_MODES.includes(envMode.toLowerCase())) {
        return envMode.toLowerCase();
      }
    
      try {
        const configPath = getConfigPath();
        const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
        if (config.defaultMode && VALID_MODES.includes(config.defaultMode.toLowerCase())) {
          return config.defaultMode.toLowerCase();
        }
      } catch (e) {
        // Config file doesn't exist or is invalid
      }
    
      return 'balanced';
    }
    
    // Symlink-safe flag file write.
    // Refuses symlinks at the target file and at the immediate parent directory,
    // uses O_NOFOLLOW where available, writes atomically via temp + rename with
    // 0600 permissions. Protects against local attackers replacing the predictable
    // flag path with a symlink to clobber other files.
    function safeWriteFlag(flagPath, content) {
      try {
        const flagDir = path.dirname(flagPath);
        fs.mkdirSync(flagDir, { recursive: true });
    
        try {
          if (fs.lstatSync(flagDir).isSymbolicLink()) return;
        } catch (e) {
          return;
        }
    
        try {
          if (fs.lstatSync(flagPath).isSymbolicLink()) return;
        } catch (e) {
          if (e.code !== 'ENOENT') return;
        }
    
        const tempPath = path.join(flagDir, `.unslop-active.${process.pid}.${Date.now()}`);
        const O_NOFOLLOW = typeof fs.constants.O_NOFOLLOW === 'number' ? fs.constants.O_NOFOLLOW : 0;
        const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | O_NOFOLLOW;
        let fd;
        try {
          fd = fs.openSync(tempPath, flags, 0o600);
          fs.writeSync(fd, String(content));
          try { fs.fchmodSync(fd, 0o600); } catch (e) { /* best-effort on Windows */ }
        } finally {
          if (fd !== undefined) fs.closeSync(fd);
        }
        fs.renameSync(tempPath, flagPath);
      } catch (e) {
        // Silent fail — flag is best-effort
      }
    }
    
    // Symlink-safe, size-capped, whitelist-validated flag file read.
    // Returns null on any anomaly — never inject untrusted bytes into model context.
    const MAX_FLAG_BYTES = 64;
    
    function readFlag(flagPath) {
      try {
        let st;
        try {
          st = fs.lstatSync(flagPath);
        } catch (e) {
          return null;
        }
        if (st.isSymbolicLink() || !st.isFile()) return null;
        if (st.size > MAX_FLAG_BYTES) return null;
    
        const O_NOFOLLOW = typeof fs.constants.O_NOFOLLOW === 'number' ? fs.constants.O_NOFOLLOW : 0;
        const flags = fs.constants.O_RDONLY | O_NOFOLLOW;
        let fd;
        let out;
        try {
          fd = fs.openSync(flagPath, flags);
          const buf = Buffer.alloc(MAX_FLAG_BYTES);
          const n = fs.readSync(fd, buf, 0, MAX_FLAG_BYTES, 0);
          out = buf.slice(0, n).toString('utf8');
        } finally {
          if (fd !== undefined) fs.closeSync(fd);
        }
    
        const raw = out.trim().toLowerCase();
        if (!VALID_MODES.includes(raw)) return null;
        return raw;
      } catch (e) {
        return null;
      }
    }
    
    function getFlagPath() {
      const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
      return path.join(claudeDir, '.unslop-active');
    }
    
    // Persona-drift reinforcement counter. Tracks how many user turns have
    // passed in this session while unslop has been active. RMTBench measures
    // >30% persona degradation after 8–12 turns; HorizonBench (arXiv
    // 2604.17283, Apr 2026) benchmarks preference evolution over time. We use
    // the counter to re-emit a shorter reinforcement banner at predetermined
    // drift-risk checkpoints rather than every turn (which would get tuned out).
    function getTurnCounterPath() {
      const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
      return path.join(claudeDir, '.unslop-turn-count');
    }
    
    // Read the counter. Same symlink-safe / size-capped discipline as readFlag.
    function readTurnCount(counterPath) {
      try {
        let st;
        try {
          st = fs.lstatSync(counterPath);
        } catch (e) {
          return 0;
        }
        if (st.isSymbolicLink() || !st.isFile()) return 0;
        if (st.size > 32) return 0;
        const O_NOFOLLOW = typeof fs.constants.O_NOFOLLOW === 'number' ? fs.constants.O_NOFOLLOW : 0;
        const flags = fs.constants.O_RDONLY | O_NOFOLLOW;
        let fd, raw;
        try {
          fd = fs.openSync(counterPath, flags);
          const buf = Buffer.alloc(32);
          const n = fs.readSync(fd, buf, 0, 32, 0);
          raw = buf.slice(0, n).toString('utf8').trim();
        } finally {
          if (fd !== undefined) fs.closeSync(fd);
        }
        const n = parseInt(raw, 10);
        if (!Number.isFinite(n) || n < 0 || n > 1_000_000) return 0;
        return n;
      } catch (e) {
        return 0;
      }
    }
    
    // Symlink-safe atomic-rename write of the counter. Uses the same pattern as
    // safeWriteFlag to resist local-attacker symlink games.
    function writeTurnCount(counterPath, n) {
      try {
        const dir = path.dirname(counterPath);
        fs.mkdirSync(dir, { recursive: true });
        try {
          if (fs.lstatSync(dir).isSymbolicLink()) return;
        } catch (e) { return; }
        try {
          if (fs.lstatSy
  • hooks/unslop-mode-tracker.jsGitHub
  • hooks/unslop-statusline.ps1GitHub
  • hooks/unslop-statusline.shGitHub

All 9 scripts are listed above. The source is inlined for 6 of them, starting with whatever hooks.json actually runs. See all of them in the repo.

Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.

Ships withmohamedabdallah-14-unslop

Make AI output sound human. Strips AI-isms (sycophancy, stock vocab, hedging stacks, em-dash pileups), preserves code/URLs/headings. Plugin for Claude Code, Cursor, Windsurf, Codex, Cline, Copilot, Gemini.

Get the whole plugin
Stats
138
Stars
5
Forks
Active
Maintenance
Python
Language
MIT
License
1d ago
Last commit
4mo ago
Created

Repo: MohamedAbdallah-14/unslop