SLOPSHOPPER

jevlight

A visual marker each time Jev acts: one sky-blue line in the transcript per action

newbandrowsstatustimer
A shopper browsing a rack in a slop shop
README

Claude Configuration Repository: Smart Agent Orchestration Framework

Continuous Integration Status: Passing Pull Request Checks Status: Passing License: MIT License - Open source software license Version 2.1 - Current software release version 12 Specialized Agents - Consolidated agent ecosystem 20 Essential Commands - Comprehensive command toolkit 5 Lightweight Skills - Focused domain expertise 42 Documentation Files - Comprehensive documentation coverage

Production-Ready Smart Agent Orchestration Framework for Claude Code CLI

Quick Start Guide • Installation Instructions • Core Features • Agent Ecosystem Overview • Command Reference • Documentation Index • Contributing Guidelines

🎯 Overview

This repository provides a comprehensive Smart Agent Orchestration Framework for Claude Code CLI, featuring 12 specialized agents (consolidated from 31) organized across 6 functional domains, 20 essential commands, and 5 lightweight skills forming a three-tier execution model. With multi-instance parallelization delivering 4-6x performance improvements and intelligent task delegation, the framework transforms development workflows through smart orchestration, coordinated parallel execution, and continuous quality validation.

🌟 What Makes This Special

  • Three-Tier Execution: Direct execution, Skills (lightweight expertise), and Agents (complex specialists)
  • 12 Specialized Agents: Consolidated from 31 for focused coverage across Development, Quality, Architecture, Infrastructure, Research, and Documentation domains
  • 20 Essential Commands: Comprehensive toolset for development, testing, deployment, and quality assurance
  • 5 Lightweight Skills: Focused domain expertise (YAML, Markdown, Python, Bash, Git workflows)
  • Multi-Instance Parallelization: Deploy 3-8 instances of the same agent type for massive performance gains
  • One-Command Deployment: Complete framework setup with /sync command
  • Production-Ready Quality: Comprehensive testing, validation, and security boundaries
  • 42 Documentation Files: Extensive guides, tutorials, and reference materials

🎪 Live Demo: See It in Action

Experience the power of parallel agent orchestration:

# Traditional approach: 3-5 minutes
# Framework approach: 30-45 seconds (5-6x faster)
/audit --scope agents

# Traditional approach: 2-3 minutes
# Framework approach: 30-40 seconds (4-5x faster)
/test

# Traditional approach: 5-7 minutes
# Framework approach: 1-2 minutes (3-4x faster)
/docs

🚀 Quick Start

Prerequisites

Ensure you have Claude Code CLI installed:

# Install via npm (recommended)
npm install -g @anthropic/claude-code

# Or via Homebrew (macOS)
brew install claude-code

# Verify installation
claude-code --version

5-Minute Setup

# 1. Clone the repository
git clone https://github.com/damilola-elegbede/claude-config.git
cd claude-config

# 2. Launch Claude Code CLI
claude-code

# 3. Deploy complete framework with one command
/sync

# 4. Verify installation with ecosystem health check
/audit --scope all

# 5. Experience the power - try these commands
/prime      # Repository analysis with 5 parallel specialists
/test       # Intelligent test execution with auto-discovery
/review     # Multi-dimensional code review

🎉 You're ready! You now have access to 12 specialized agents and 20 essential commands.

First Commands to Try

# Multi-agent repository analysis with parallel execution
/prime

# Comprehensive test execution with framework auto-discovery
/test

# Multi-dimensional code review (code-reviewer + security-auditor + test-engineer)
/review

# Documentation generation with 6 parallel processors
/docs

# Smart dependency audit with security scanning
/deps

🛠️ Core Features

🤖 Claude Sonnet 4.6 Powered

  • Latest Model: Built for Claude Sonnet 4.6 (released February 2026)
  • Enhanced Reasoning: Enhanced capabilities for complex problem-solving
  • Extended Thinking: Native support for megathink/ultrathink reasoning modes
  • Faster Performance: Improved response times enable more aggressive parallelization
  • Agent Distribution: 11 Sonnet agents, 1 Opus agent (12 total specialized agents)

🧠 Smart Agent Orchestration

  • 12 Specialized Agents: Consolidated coverage across all development domains
  • Multi-Instance Parallelization: Deploy multiple instances of the same agent type
  • Intelligent Task Delegation: Automatic specialist selection based on task complexity
  • Coordinated Wave Execution: Dependency-aware parallel execution patterns
  • Continuous Improvement: Performance feedback loops with adaptive optimization

⚡ Performance Improvements

Table: Performance Improvements with Smart Agent Orchestration Framework

OperationBefore FrameworkAfter FrameworkPerformance ImprovementTechnology Implementation
Agent Ecosystem Audit3-5 min30-45 sec5-6x faster8 parallel agent-auditors
Test Suite Execution2-3 min30-40 sec4-5x faster5 test framework instances
Documentation Generation5-7 min1-2 min3-4x faster6 document processors
Repository Analysis1-2 min15-20 sec4-6x faster5 analyzer instances
Dependency Security Audit2 min20-30 sec4-6x fasterPer-ecosystem instances

🔄 Configuration Management

  • One-Command Deployment: Complete framework setup with /sync
  • Automatic Validation: YAML compliance and security boundary checking
  • Intelligent Backup: Automatic backup creation with rollback capabilities
  • MCP Server Integration: Seamless integration with Model Context Protocol servers
  • Cross-Platform Support: Works on macOS, Linux, and Windows

🛡️ Security & Quality

  • SYSTEM BOUNDARY Protection: Multi-layered prevention of unauthorized agent invocations
  • Zero-Tolerance Quality Gates: Comprehensive pre-commit and pre-push validation
  • Security-First Design: Principle of least privilege with role-based access control
  • Comprehensive Testing: Full test coverage across all components
  • Audit Logging: Complete tracking of all agent actions and decisions

💡 Skills: Lightweight Expertise

The framework includes 5 lightweight skills that provide focused domain expertise without orchestration overhead, filling the gap between direct execution and full agent delegation.

Three-Tier Execution Model

Level 1: Direct Execution        → Simple, deterministic tasks (< 5 min)
Level 2: Skills                 → Lightweight expertise (YAML, Markdown, Python)
Level 3: Agents                 → Complex specialists (backend-engineer, ml-engineer)

Core Skills (Tier 1)

SkillCategoryFocusUse When
yamlformatYAML syntax, frontmatter validationCreating/editing agents and commands
markdownformatMarkdown linting, documentationWriting/fixing documentation
pythonlanguagePython patterns, validation scriptsWriting automation tools
bashworkflowShell scripting, git hooksBuilding workflows and hooks
git-workflowsworkflowGit operations, branching, commitsManaging version control

Skills vs Agents

  • Skills: Quick reference, format-specific, no orchestration (seconds)
  • Agents: Multi-step tasks, strategic decisions, tool orchestration (minutes)

Example:

User: "Validate this YAML frontmatter"
→ Uses yaml skill (seconds)

User: "Implement a new backend API"
→ Uses backend-engineer agent (minutes, multi-step)

See Skills Guide for detailed documentation and usage patterns.

🎭 Agent Ecosystem: 12 Specialists

The framework features 12 specialized agents (consolidated from 31) organized across 6 functional domains for complete development lifecycle coverage with reduced complexity.

📊 Agent Categories Overview

CategoryCountKey SpecialistsPurpose
Development3backend-engineer, frontend-engineer, data-engineerCore programming and implementation
Architecture1architectSystem, API, cloud, and frontend architecture
Quality3code-reviewer, security-auditor, test-engineerTesting, validation, and quality assurance
Infrastructure2devops, debuggerOperations, CI/CD, and debugging
Research1researcherTech research and codebase analysis
Resume/Career1career-assistantJob applications and career support
Documentation1tech-writerTechnical documentation and READMEs

Consolidation Summary

19 agents were consolidated into the remaining 12:

  • architect absorbs: principal-architect, api-architect, cloud-architect, frontend-architect
  • career-assistant absorbs: jd-analyzer, resume-optimizer, content-writer, career-strategist
  • code-reviewer absorbs: accessibility-auditor
  • data-engineer absorbs: database-admin
  • debugger absorbs: performance-engineer
  • devops absorbs: platform-engineer
  • frontend-engineer absorbs: ui-designer
  • researcher absorbs: codebase-analyst, ux-researcher, business-analyst, product-strategist

🚀 Smart Orchestration Examples

Multi-Platform Development Scenario
Project: Full-Stack Application
Strategy: Parallel specialist deployment across platforms
Execution:
  Wave 1:
    - architect: System architecture and API design
    - researcher: Requirements analysis and user research
  Wave 2:
    - backend-engineer: API implementation and microservices
    - frontend-engineer: Web application development
  Wave 3:
    - test-engineer: Comprehensive testing strategy and automation
    - security-auditor: Security assessment and vulnerability testing
    - debugger: Performance optimization and monitoring

Result: 70% faster delivery through coordinated parallel execution
Quality Assurance Orchestration
Project: Production Readiness Assessment
Strategy: Multi-dimensional validation with parallel specialists
Execution:
  - code-reviewer: Code quality analysis and best practices validation
  - security-auditor: Security vulnerability assessment and compliance
  - test-engineer: Test coverage analysis and automation strategy
  - debugger: Performance bottleneck identification and optimization

Quality Gates: 95% coverage across all validation dimensions
Performance: 5x faster than sequential validation

🛠️ Essential Commands

⭐⭐⭐⭐⭐ Five-Star Commands (Core Orchestration)

CommandDescriptionPerformance GainKey Features
/syncDeploy complete framework configurationsN/AOne-command setup, validation, backup
/testMulti-agent test execution with auto-discovery4-5x faster5 test suite instances, framework detection
/primeParallel repository analysis and insights4-6x faster5 analyzer instances, comprehensive profiling
/auditUnified ecosystem validation (agents/commands/all)5-6x fasterParallel validation, comprehensive coverage
/reviewMulti-dimensional quality analysisEnhanced coverageParallel specialists, comprehensive assessment
/docsDocumentation orchestration with parallel processors3-4x faster6 document instances, automated generation
/planStrategic project planning with principal-architectEnhanced qualityTDD methodology, architectural guidance
/debugSystematic investigation with evidence gatheringImproved accuracyHypothesis testing, systematic debugging
/resolve-commentsIntelligent PR resolution based on comment analysisContext-awareMulti-agent deployment, automated fixes
/depsSecurity-first dependency managementVulnerability scanningMulti-language support, security assessment
/fix-ciAutomated CI/CD failure resolutionPattern recognitionDevOps expertise, automated remediation
/prIntelligent PR creation with tech-writer collaborationEnhanced descriptionsContext-aware analysis, professional documentation
/implementation-planGenerate detailed implementation plans without executionPlanning accelerationTask breakdown, risk assessment, verification planning
/verifyCommand execution verification with percentage alignment analysisQuality assuranceMulti-wave analysis, requirement validation, actionable recommendations

⭐⭐⭐⭐ Four-Star Commands (Enhanced Operations)

  • /commit - Enhanced Git operations with repository hygiene features, automatic temporary file cleanup, and professional repository standards maintenance
  • /push - Safe repository operations with comprehensive validation
  • /branch - Context-aware branching with intelligent naming conventions
  • /deploy - Production deployment with comprehensive orchestration
  • /monitor - System monitoring with intelligent alerting and analysis
  • /audit --scope commands - Command quality assurance and ecosystem validation

⭐⭐⭐ Three-Star Commands (Utility & Support)

  • /ship-it - Release management with comprehensive workflow automation
  • /prompt - Prompt development and testing utility for optimization

💾 Installation

Option 1: Quick Setup (Recommended)

Perfect for most users wanting immediate access to the complete framework:

# Clone the repository
git clone https://github.com/damilola-elegbede/claude-config.git
cd claude-config

# Launch Claude Code CLI
claude-code

# Deploy complete framework
/sync

# Verify installation
/audit --scope agents
/prime --lite

Option 2: Custom Installation

For users who want selective component installation:

# Clone repository
git clone https://github.com/damilola-elegbede/claude-config.git
cd claude-config

# Create Claude configuration directory
mkdir -p ~/.claude

# Install specific components:

# Core agents only
cp -r system-configs/.claude/agents ~/.claude/agents

# Essential commands only
cp -r system-configs/.claude/commands ~/.claude/commands

# Audio notifications (macOS)
cp system-configs/.claude/settings.json ~/.claude/settings.json

# Verify installation
claude-code
/audit --scope agents

Option 3: Development Installation

For contributors and advanced users:

# Fork and clone your fork
git clone https://github.com/YOUR-USERNAME/claude-config.git
cd claude-config

# Add upstream remote
git remote add upstream https://github.com/damilola-elegbede/claude-config.git

# Run comprehensive validation
./tests/test.sh
./scripts/validate-agent-yaml.py

# Deploy development configuration
/sync

# Run full ecosystem validation
/audit --scope agents
/audit --scope commands

System Requirements

  • Claude Code CLI: Version 1.0 or higher
  • Operating System: macOS, Linux, or Windows
  • Python: 3.8+ (for validation scripts)
  • Node.js: 16+ (for npm installation method)
  • Disk Space: ~50MB for complete framework
  • Memory: 4GB+ recommended for optimal performance

📋 The /sync Command: Framework Deployment

The /sync command is the cornerstone of the framework deployment system, providing one-command setup with comprehensive validation and rollback capabilities.

Basic Usage

# Standard deployment
/sync

# Preview changes without deployment
/sync --dry-run

# Force deployment with backup
/sync --backup --force

What Gets Deployed

The sync process deploys the complete framework configuration:

  • 12 Agent Definitions: All specialist agents to ~/.claude/agents/
  • 20 Command Definitions: Essential commands to ~/.claude/commands/
  • Output Styles: Formatting configurations to ~/.claude/output-styles/
  • Mods: Hook-module plugins (e.g. glassbox, a live view of Claude's work; jevlight, a marker each time Jev acts) to ~/.claude/mods/, loaded via CLAUDE_CODE_PLUGIN_DIRS
  • System Settings: Audio notifications and preferences to ~/.claude/settings.json
  • MCP Server Configuration: Model Context Protocol server integration
  • Statusline Integration: Intelligent terminal statusline for development context

Deployment Process

  1. Pre-deployment Validation: YAML syntax, permissions, and configuration integrity
  2. Automatic Backup Creation: Complete backup of existing configurations
  3. File Synchronization: Efficient deployment using rsync with exclusions
  4. MCP Server Integration: Seamless integration with Claude Desktop configuration
  5. Post-deployment Validation: Comprehensive verification of all components
  6. Rollback on Failure: Automatic restoration if any deployment step fails

Expected Output

🔄 Syncing Claude configurations...
📁 Source: system-configs/.claude/ (56 files)
📁 Target: ~/.claude/

✅ Pre-sync validation:
  - Configuration syntax: Valid (12 agents, 20 commands)
  - Target directory: Ready
  - Permissions: OK

💾 Creating backup: ~/.claude.backup.20250909_143022

🔄 Synchronizing files:
  ✅ Agents: 12 files → ~/.claude/agents/
  ✅ Skills: 34 skills → ~/.claude/skills/
  ✅ Output styles: 8 files → ~/.claude/output-styles/
  ✅ Settings: settings.json, statusline.sh, exit_hook.sh, session_start_version_check.sh

📡 MCP Server Configuration:
  ✅ Updated Claude Desktop config with MCP servers:
    - filesystem, github, shadcn-ui
    - context7, notionApi

✅ Post-sync validation:
  - File integrity: All files copied successfully
  - Agent configs: 12/12 valid
  - Commands: 20/20 functional
  - MCP integration: 5/5 connected

📊 Sync completed successfully:
  Files synced: 56 total
  Backup location: ~/.claude.backup.20250909_143022
  Sync time: 2.3 seconds

🧪 Testing & Validation

Comprehensive Test Suite

# Run all tests with intelligent orchestration
/test

# Run specific test categories
./tests/test.sh commands
./tests/test.sh config
./tests/test.sh integration
./tests/test.sh performance

# Validate agent YAML compliance
./scripts/validate-agent-yaml.py

# Command behavioral validation
/audit --scope commands

Test Coverage Areas

  • ✅ Command Validation: Behavioral testing and functionality verification
  • ✅ Agent Configuration: YAML schema compliance and security boundaries
  • ✅ Integration Testing: End-to-end workflow validation
  • ✅ Security Validation: SYSTEM BOUNDARY protection and access control
  • ✅ Performance Testing: Benchmark validation and regression detection
  • ✅ Documentation Testing: Consistency and accuracy verification

🏗️ Architecture & Project Structure

Repository Structure

claude-config/
├── README.md                       # This comprehensive documentation
├── QUICKSTART.md                   # 5-minute setup guide
├── CONTRIBUTING.md                 # Development and contribution guidelines
├── SECURITY.md                     # Security policies and reporting
├── LICENSE                         # MIT license
├── system-configs/                 # Source-of-truth configurations
│   ├── .claude/                   # Claude Code configuration
│   │   ├── agents/                # 12 agent definitions
│   │   │   ├── backend-engineer.md
│   │   │   ├── frontend-engineer.md
│   │   │   ├── security-auditor.md
│   │   │   ├── test-engineer.md
│   │   │   └── ... (8 more agents)
│   │   ├── commands/              # 20 command definitions
│   │   │   ├── sync.md
│   │   │   ├── test.md
│   │   │   ├── prime.md
│   │   │   ├── agent-audit.md
│   │   │   ├── verify.md
│   │   │   └── ... (15 more commands)
│   │   ├── output-styles/         # Formatting configurations
│   │   ├── mods/                  # Hook-module plugins (glassbox, jevlight)
│   │   ├── settings.json          # Hook configuration and audio preferences
│   │   ├── statusline.sh          # Terminal statusline integration
│   │   ├── exit_hook.sh           # SessionEnd cleanup hook
│   │   └── session_start_version_check.sh  # SessionStart upgrade/CHANGELOG capture hook
├── docs/                          # 42 comprehensive documentation files
│   ├── setup/                     # Installation and configuration guides
│   ├── development/               # Development guidelines and requirements
│   ├── performance/               # Performance optimization guides
│   ├── quality/                   # Quality assurance documentation
│   ├── architecture/              # System architecture documentation
│   ├── agents/                    # Agent templates and categories
│   ├── api/                       # API documentation and specifications
│   ├── guides/                    # Tutorials and comprehensive gu
Source 2 files
hooks/register.tsx 396 lines
1import { atom, read, update } from "claude-code";
2import type { EngineInterface, Register } from "claude-code";
3
4import type { Marks } from "../types";
5
6// jevlight: a visual marker each time Jev acts. Jev is the set of settings
7// (shell) hooks in settings.json; they run beneath every hooks module, so
8// awaiting `next(e)` here returns what they decided. A pass leaves no mark.
9//
10// v2 draws EVERY action in sky blue, in three places:
11//   1. a tool call's action (deny, ask, note, rewrite, trim) is a line under
12//      that call's row (ToolResult / ToolGroup), keyed by tool_use_id;
13//   2. a prompt's action (UserPromptSubmit note or hold) is a line under the
14//      user's own message row (UserMessage), keyed by the prompt text;
15//   3. a stop's action (held stop, note) is a line under the reply it judged
16//      (AssistantMessage), keyed by the reply text;
17// and a session-level action (SessionStart context) is a plain log row (the
18// record) plus a sky-blue band above the prompt until the next prompt.
19// If a coloured row is never drawn (a -p host, a text that does not match), a
20// plain log line says it after FALLBACK_MS, so no action is ever silent.
21//
22// It only watches: every hook returns the result it was handed, unchanged. It
23// cannot tell which settings hook acted, so a non-Jev settings hook that acts
24// (gate.sh) is marked as Jev too. A hook that answers only `systemMessage` or
25// plain stdout is invisible here (the folded result has no such field); the
26// engine itself draws a systemMessage as a "<Event> says: ..." notice.
27
28// The fields of a settings hook result that mean a hook acted.
29export type Outcome = {
30  deny?: string;
31  ask?: string;
32  updatedInput?: Record<string, unknown>;
33  block?: string;
34  preventContinuation?: true;
35  stopReason?: string;
36  additionalContext?: string[];
37  updatedToolOutput?: unknown;
38  updatedMCPToolOutput?: unknown;
39};
40
41export const SKY = "#87CEEB";
42// A reason is shown whole and the transcript row wraps it; the cap only stops a runaway line.
43const LINE_MAX = 1000;
44// Calls whose marks are kept; older ones scroll out of view anyway.
45const KEEP = 200;
46// Sessions whose marks the store keeps, so a resume can draw them again.
47const SESSIONS_KEPT = 20;
48// How long an anchored line waits for its coloured row before a plain log line says it.
49export const FALLBACK_MS = 2500;
50
51const marks = atom({ plugin: "jevlight", key: "marks" } as const, {} as Marks);
52// Lines anchored to a row that has no tool_use_id: "u:<hash>" under a prompt, "a:<hash>" under a reply.
53const notes = atom({ plugin: "jevlight", key: "notes" } as const, {} as Marks);
54// Session-level lines drawn in the band above the prompt until the next prompt.
55const band = atom({ plugin: "jevlight", key: "band" } as const, [] as string[]);
56// The session the marks belong to, learned from any settings hook event.
57const sid = atom(
58  { plugin: "jevlight", key: "sid" } as const,
59  null as string | null,
60);
61
62// Anchors whose coloured row was drawn at least once (read by the fallback timer).
63const drawn = new Set<string>();
64
65export const firstLine = (text: string) => {
66  const line =
67    text
68      .trim()
69      .split("\n")
70      .find((l) => l.trim() !== "") ?? "";
71  return line.length > LINE_MAX ? `${line.slice(0, LINE_MAX - 1)}…` : line;
72};
73
74// The engine prefixes a hook's reason with "PreToolUse:Bash hook error: ", and an
75// exit-2 inline guard adds its whole script in brackets before its stderr.
76export const unprefix = (text: string) => {
77  let t = text.replace(/^[A-Za-z]+(:\S+)? hook error: /, "");
78  if (t.startsWith("[")) {
79    const k = t.lastIndexOf("]: ");
80    if (k > 0) t = t.slice(k + 3);
81  }
82  return t;
83};
84
85// Who acted. Everything is Jev by default (D wants every action flashed); only the inline git guards'
86// "BLOCKED: ..." / "WARNING: ..." wording marks a settings hook that is not Jev.
87export const who = (text: string) =>
88  /^(BLOCKED|WARNING):/.test(unprefix(text).trim()) ? "Hook" : "Jev";
89
90// The line a reader needs: a reason that is only a header ("... violations:")
91// is followed by its first bullet, since the bullet says what was wrong.
92export const gist = (text: string) => {
93  const lines = unprefix(text.trim())
94    .split("\n")
95    .map((l) => l.trim())
96    .filter((l) => l !== "");
97  const head = lines[0] ?? "";
98  const next = lines[1]?.replace(/^[-*•]\s*/, "");
99  const joined = /[:)]$/.test(head) && next ? `${head} ${next}` : head;
100  return joined.length > LINE_MAX
101    ? `${joined.slice(0, LINE_MAX - 1)}…`
102    : joined;
103};
104
105const said = (text: string) => (gist(text) ? `: ${gist(text)}` : "");
106
107// What Jev did at one event, one line per action. `where` names the tool
108// call or the moment (`Bash`, `Bash failure`, `your prompt`).
109export const actionsOf = (where: string, r: Outcome | undefined): string[] => {
110  if (!r) return [];
111  const lines: string[] = [];
112  const add = (text: string | undefined, line: string) =>
113    // U+FE0F asks for emoji presentation; without it a programming font draws its own outline glyph.
114    lines.push(`⚡️ ${text === undefined ? "Jev" : who(text)} ${line}`);
115  if (r.deny !== undefined) add(r.deny, `blocked ${where}${said(r.deny)}`);
116  else if (r.ask !== undefined)
117    add(r.ask, `asked about ${where}${said(r.ask)}`);
118  if (r.block !== undefined) add(r.block, `held ${where}${said(r.block)}`);
119  if (r.preventContinuation) {
120    add(r.stopReason, `stopped the session${said(r.stopReason ?? "")}`);
121  }
122  // Each hook that added context is its own action.
123  for (const c of r.additionalContext ?? []) {
124    if (c.trim() !== "") add(c, `noted ${where}${said(c)}`);
125  }
126  if (r.updatedInput !== undefined) add(undefined, `rewrote ${where}'s input`);
127  if (
128    r.updatedToolOutput !== undefined ||
129    r.updatedMCPToolOutput !== undefined
130  ) {
131    add(undefined, `trimmed ${where}`);
132  }
133  return lines;
134};
135
136// Adds lines to a call's marks, dropping the oldest calls past KEEP.
137export const addMarks = (all: Marks, id: string, lines: string[]): Marks => {
138  const next = { ...all, [id]: [...(all[id] ?? []), ...lines] };
139  const ids = Object.keys(next);
140  for (const old of ids.slice(0, Math.max(0, ids.length - KEEP))) {
141    delete next[old];
142  }
143  return next;
144};
145
146// A key for a row with no tool_use_id: the kind and a hash of its whitespace-normalised text.
147export const anchorKey = (kind: "u" | "a", text: string) => {
148  const s = text.replace(/\s+/g, " ").trim();
149  let h = 5381;
150  for (let i = 0; i < s.length; i++) h = ((h * 33) ^ s.charCodeAt(i)) >>> 0;
151  return `${kind}:${h.toString(36)}:${s.length}`;
152};
153
154// Saves this session's marks and notes, keeping the newest SESSIONS_KEPT sessions.
155const persist = async ($: EngineInterface) => {
156  const session = await read($, sid);
157  if (!session) return;
158  await $.store.set(`marks:${session}`, await read($, marks));
159  await $.store.set(`notes:${session}`, await read($, notes));
160  const kept = ((await $.store.get("sessions")) as string[] | undefined) ?? [];
161  const sessions = [...kept.filter((s) => s !== session), session];
162  for (const old of sessions.slice(0, -SESSIONS_KEPT)) {
163    await $.store.delete(`marks:${old}`);
164    await $.store.delete(`notes:${old}`);
165  }
166  await $.store.set("sessions", sessions.slice(-SESSIONS_KEPT));
167};
168
169// Learns the session id; on a resume, draws its saved marks again.
170const remember = async ($: EngineInterface, session: string) => {
171  try {
172    const previous = await read($, sid);
173    if (previous === session) return;
174    await update($, sid, () => session);
175    const saved = (await $.store.get(`marks:${session}`)) as Marks | undefined;
176    const savedNotes = (await $.store.get(`notes:${session}`)) as
177      | Marks
178      | undefined;
179    // Switching sessions drops the old session's marks (tool_use_ids could
180    // coincide); the first id learned keeps marks drawn before it was known.
181    if (previous) {
182      await update($, marks, () => saved ?? {});
183      await update($, notes, () => savedNotes ?? {});
184      await update($, band, () => []);
185    } else {
186      if (saved) {
187        await update($, marks, (all) => ({ ...saved, ...(all ?? {}) }));
188      }
189      if (savedNotes) {
190        await update($, notes, (all) => ({ ...savedNotes, ...(all ?? {}) }));
191      }
192    }
193  } catch {
194    // losing old marks never stops the session
195  }
196};
197
198// Asks the engine to draw the rows again now that a line exists for one.
199const redraw = ($: EngineInterface) => {
200  try {
201    $.ui.invalidate("ui.render");
202  } catch {
203    // a host that cannot redraw shows the line at the next natural draw
204  }
205};
206
207type Where = { id?: string; anchor?: string; session?: boolean };
208
209// A marker must never break the hook chain: a failure to record it is dropped.
210const mark = async (
211  $: EngineInterface,
212  where: string,
213  r: unknown,
214  at: Where = {},
215) => {
216  try {
217    const lines = actionsOf(where, r as Outcome);
218    if (lines.length === 0) return;
219    if (at.id) {
220      await update($, marks, (all) => addMarks(all ?? {}, at.id!, lines));
221      await persist($);
222    } else if (at.anchor && !(await read($, notes))[at.anchor]) {
223      // A row is found by its text alone, so only a text's first occurrence is anchored; a repeat
224      // ("continue" twice) falls through to a plain line where it happened, never under the first.
225      const key = at.anchor;
226      drawn.delete(key);
227      await update($, notes, (all) => addMarks(all ?? {}, key, lines));
228      await persist($);
229      // No coloured row drawn in time (a -p host, a text that does not match): say it plainly.
230      const say = () => {
231        if (!drawn.has(key)) for (const line of lines) $.ui.log(line);
232      };
233      try {
234        $.clock.after(FALLBACK_MS, say);
235      } catch {
236        // a host with no engine clock (the test kit): the host's own timer
237        setTimeout(say, FALLBACK_MS);
238      }
239      redraw($);
240    } else {
241      // The record: a plain row. For a session-level action, also a band above the prompt.
242      for (const line of lines) $.ui.log(line);
243      if (at.session) {
244        await update($, band, (all) => [...(all ?? []), ...lines].slice(-5));
245        redraw($);
246      }
247    }
248  } catch {
249    // recording the marker failed; Jev's result still goes on below
250  }
251};
252
253// If a hook here throws, hand on what Jev decided: next(e) replays the call
254// that already ran, so nothing runs twice and a Jev block still stands.
255const keepJev = <E, R>(_$: unknown, e: E, next: (e: E) => R) => next(e);
256
257export const register: Register = (on) => {
258  // 0.1.0 pinned a status line, which outlives a reload; clear it.
259  on("session.start", async ($, e, next) => {
260    $.ui.status(undefined);
261    return next(e);
262  });
263
264  // PreToolUse carries no session id; the events around it teach `sid`.
265  on("classic.PreToolUse", async ($, e, next) => {
266    const r = await next(e);
267    await mark($, e.tool, r, { id: e.tool_use_id });
268    return r;
269  }).catch(keepJev);
270
271  on("classic.PostToolUse", async ($, e, next) => {
272    await remember($, e.session_id);
273    const r = await next(e);
274    await mark($, `${e.tool_name} output`, r, { id: e.tool_use_id });
275    return r;
276  }).catch(keepJev);
277
278  on("classic.PostToolUseFailure", async ($, e, next) => {
279    await remember($, e.session_id);
280    const r = await next(e);
281    await mark($, `${e.tool_name} failure`, r, { id: e.tool_use_id });
282    return r;
283  }).catch(keepJev);
284
285  on("classic.UserPromptSubmit", async ($, e, next) => {
286    await remember($, e.session_id);
287    // A new prompt ends the session-level band.
288    await update($, band, () => []);
289    const r = await next(e);
290    await mark($, "your prompt", r, { anchor: anchorKey("u", e.prompt ?? "") });
291    return r;
292  }).catch(keepJev);
293
294  on("classic.Stop", async ($, e, next) => {
295    await remember($, e.session_id);
296    const r = await next(e);
297    const reply = e.last_assistant_message ?? "";
298    await mark(
299      $,
300      "the stop",
301      r,
302      reply.trim() ? { anchor: anchorKey("a", reply) } : {},
303    );
304    return r;
305  }).catch(keepJev);
306
307  on("classic.SessionStart", async ($, e, next) => {
308    await remember($, e.session_id);
309    const r = await next(e);
310    await mark($, "session start", r, { session: true });
311    return r;
312  }).catch(keepJev);
313
314  // The sky-blue lines under a call's result row.
315  on("ui.render", { component: "ToolResult" }, async ($, e, next) => {
316    const lines = (await read($, marks))[e.props.tool_use_id] ?? [];
317    if (lines.length === 0) return next(e);
318    const { Box, Text } = $.ui.resolve(e);
319    return (
320      <Box flexDirection="column">
321        {await next(e)}
322        {lines.map((l) => (
323          <Text color={SKY}>{l}</Text>
324        ))}
325      </Box>
326    );
327  });
328
329  // And under a group of calls ("Read 3 files"), folded or expanded.
330  on("ui.render", { component: "ToolGroup" }, async ($, e, next) => {
331    const all = await read($, marks);
332    const lines = e.props.calls.flatMap((c) =>
333      c.tool_use_id ? (all[c.tool_use_id] ?? []) : [],
334    );
335    if (lines.length === 0) return next(e);
336    const { Box, Text } = $.ui.resolve(e);
337    return (
338      <Box flexDirection="column">
339        {await next(e)}
340        {lines.map((l) => (
341          <Text color={SKY}>{l}</Text>
342        ))}
343      </Box>
344    );
345  });
346
347  // Under the user's own message: what Jev did with that prompt.
348  on("ui.render", { component: "UserMessage" }, async ($, e, next) => {
349    const key = anchorKey("u", e.props.text);
350    const lines = (await read($, notes))[key] ?? [];
351    if (lines.length === 0) return next(e);
352    drawn.add(key);
353    const { Box, Text } = $.ui.resolve(e);
354    return (
355      <Box flexDirection="column">
356        {await next(e)}
357        {lines.map((l) => (
358          <Text color={SKY}>{l}</Text>
359        ))}
360      </Box>
361    );
362  });
363
364  // Under the reply Jev judged at the stop (a hold, a note).
365  on("ui.render", { component: "AssistantMessage" }, async ($, e, next) => {
366    const key = anchorKey("a", e.props.text);
367    const lines = (await read($, notes))[key] ?? [];
368    if (lines.length === 0) return next(e);
369    drawn.add(key);
370    const { Box, Text } = $.ui.resolve(e);
371    return (
372      <Box flexDirection="column">
373        {await next(e)}
374        {lines.map((l) => (
375          <Text color={SKY}>{l}</Text>
376        ))}
377      </Box>
378    );
379  });
380
381  // The band above the prompt: session-level actions until the next prompt.
382  on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
383    const lines = await read($, band);
384    if (lines.length === 0 || e.props.hasSurvey) return next(e);
385    const { Box, Text } = $.ui.resolve(e);
386    return (
387      <Box flexDirection="column">
388        {await next(e)}
389        {lines.map((l) => (
390          <Text color={SKY}>{l}</Text>
391        ))}
392      </Box>
393    );
394  });
395};
396
types/index.d.ts 16 lines
1// Jev's marks by tool call: each `tool_use_id` Jev acted on, and one line per
2// action, drawn in sky blue under that call's row. Notes use the same shape,
3// keyed by an anchor ("u:<hash>" for a prompt, "a:<hash>" for a reply).
4export type Marks = Record<string, string[]>;
5
6declare module "claude-code" {
7  interface PluginState {
8    jevlight: {
9      marks: Marks;
10      notes: Marks;
11      band: string[];
12      sid: string | null;
13    };
14  }
15}
16