Add comprehensive tests for keyword detection, session state management, and user abort detection

- Implement tests for KeywordDetector including keyword detection, sanitization, and priority handling.
- Add tests for SessionStateService covering session validation, loading, saving, and state updates.
- Create tests for UserAbortDetector to validate user abort detection logic and pattern matching.
This commit is contained in:
catlog22
2026-02-18 21:48:56 +08:00
parent 65762af254
commit 46d4b4edfd
23 changed files with 6992 additions and 329 deletions

View File

@@ -1,17 +1,20 @@
# Hooks Integration for Progressive Disclosure
This document describes how to integrate session hooks with CCW's progressive disclosure system.
This document describes how to integrate session hooks with CCW's progressive disclosure system, including the new Soft Enforcement Stop Hook, Mode System, and Checkpoint/Recovery features.
## Overview
CCW now supports automatic context injection via hooks. When a session starts, the system can automatically provide a progressive disclosure index showing related sessions from the same cluster.
CCW supports automatic context injection via hooks. When a session starts, the system can automatically provide a progressive disclosure index showing related sessions from the same cluster.
## Features
### Key Features
- **Automatic Context Injection**: Session start hooks inject cluster context
- **Progressive Disclosure**: Shows related sessions, their summaries, and recovery commands
- **Silent Failure**: Hook failures don't block session start (< 5 seconds timeout)
- **Multiple Hook Types**: Supports `session-start`, `context`, and custom hooks
- **Multiple Hook Types**: Supports `session-start`, `context`, `PreCompact`, `Stop`, and custom hooks
- **Soft Enforcement**: Stop hooks never block - they inject continuation messages instead
- **Mode System**: Keyword-based mode activation with exclusive mode conflict detection
- **Checkpoint/Recovery**: Automatic state preservation before context compaction
## Hook Configuration
@@ -25,11 +28,15 @@ Place hook configurations in `.claude/settings.json`:
"session-start": [
{
"name": "Progressive Disclosure",
"description": "Injects progressive disclosure index at session start",
"description": "Injects progressive disclosure index at session start with recovery detection",
"enabled": true,
"handler": "internal:context",
"timeout": 5000,
"failMode": "silent"
"failMode": "silent",
"notes": [
"Checks for recovery checkpoints and injects recovery message if found",
"Uses RecoveryHandler.checkRecovery() for session recovery"
]
}
]
}
@@ -39,13 +46,25 @@ Place hook configurations in `.claude/settings.json`:
### Hook Types
#### `session-start`
Triggered when a new session begins. Ideal for injecting context.
Triggered when a new session begins. Ideal for injecting context and checking for recovery checkpoints.
#### `context`
Triggered on explicit context requests. Same handler as `session-start`.
#### `PreCompact`
Triggered before context compaction. Creates checkpoints to preserve session state including:
- Active mode states
- Workflow progress
- TODO summaries
#### `Stop`
Triggered when a stop is requested. Uses **Soft Enforcement** - never blocks, but may inject continuation messages.
#### `UserPromptSubmit`
Triggered when a prompt is submitted. Detects mode keywords and activates corresponding execution modes.
#### `session-end`
Triggered when a session ends. Useful for updating cluster metadata.
Triggered when a session ends. Useful for:
- Updating cluster metadata
- Cleaning up mode states
- Final checkpoint creation
#### `file-modified`
Triggered when files are modified. Can be used for auto-commits or notifications.
@@ -73,6 +92,240 @@ In `command` fields, use these variables:
- `$PROJECT_PATH`: Current project path
- `$CLUSTER_ID`: Active cluster ID (if available)
---
## Soft Enforcement Stop Hook
The Stop Hook implements **Soft Enforcement**: it never blocks stops, but injects continuation messages to encourage task completion.
### Priority Order
1. **context-limit**: Always allow (deadlock prevention)
2. **user-abort**: Respect user intent
3. **active-workflow**: Inject continuation message
4. **active-mode**: Inject continuation message via ModeRegistryService
### Configuration Example
```json
{
"hooks": {
"Stop": [
{
"name": "Soft Enforcement Stop",
"description": "Injects continuation messages for active workflows/modes",
"enabled": true,
"command": "ccw hook stop --stdin",
"timeout": 5000,
"failMode": "silent"
}
]
}
}
```
### Behavior Matrix
| Condition | continue | mode | message |
|-----------|----------|------|---------|
| Context limit reached | `true` | `context-limit` | None |
| User requested stop | `true` | `user-abort` | None |
| Active workflow | `true` | `active-workflow` | Continuation message |
| Active mode | `true` | `active-mode` | Mode-specific message |
| Normal stop | `true` | `none` | None |
### Context Limit Detection
Detected via `ContextLimitDetector`:
- `stop_reason: "context_limit_reached"`
- `stop_reason: "end_turn_limit"`
- `end_turn_reason: "max_tokens"`
- `stop_reason: "max_context"`
### User Abort Detection
Detected via `UserAbortDetector`:
- `user_requested: true`
- `stop_reason: "user_cancel"`
- `stop_reason: "cancel"`
---
## Mode System
The Mode System provides centralized mode state management with file-based persistence.
### Supported Modes
| Mode | Type | Description |
|------|------|-------------|
| `autopilot` | Exclusive | Autonomous execution mode for multi-step tasks |
| `ralph` | Non-exclusive | Research and Analysis Learning Pattern Handler |
| `ultrawork` | Non-exclusive | Ultra-focused work mode for deep tasks |
| `swarm` | Exclusive | Multi-agent swarm execution mode |
| `pipeline` | Exclusive | Pipeline execution mode for sequential tasks |
| `team` | Non-exclusive | Team collaboration mode |
| `ultraqa` | Non-exclusive | Ultra-focused QA mode |
### Exclusive Mode Conflict
Exclusive modes (`autopilot`, `swarm`, `pipeline`) cannot run concurrently. Attempting to start one while another is active will be blocked.
### Mode State Storage
Mode states are stored in `.workflow/modes/`:
```
.workflow/modes/
├── sessions/
│ ├── {session-id}/
│ │ ├── autopilot-state.json
│ │ └── ralph-state.json
│ └── ...
└── (legacy shared states)
```
### Stale Marker Cleanup
Mode markers older than 1 hour are automatically cleaned up to prevent crashed sessions from blocking indefinitely.
### Mode Activation via Keyword
Configure the `UserPromptSubmit` hook to detect keywords:
```json
{
"hooks": {
"UserPromptSubmit": [
{
"name": "Keyword Detection",
"description": "Detects mode keywords in prompts and activates corresponding modes",
"enabled": true,
"command": "ccw hook keyword --stdin",
"timeout": 5000,
"failMode": "silent"
}
]
}
}
```
### Supported Keywords
| Keyword | Mode | Aliases |
|---------|------|---------|
| `autopilot` | autopilot | - |
| `ultrawork` | ultrawork | `ulw` |
| `ralph` | ralph | - |
| `swarm` | swarm | - |
| `pipeline` | pipeline | - |
| `team` | team | - |
| `ultraqa` | ultraqa | - |
| `cancelomc` | Cancel | `stopomc` |
| `codex` | Delegate | `gpt` |
| `gemini` | Delegate | - |
---
## Checkpoint and Recovery
The Checkpoint System preserves session state before context compaction.
### Checkpoint Triggers
| Trigger | Description |
|---------|-------------|
| `manual` | User-initiated checkpoint |
| `auto` | Automatic checkpoint |
| `compact` | Before context compaction |
| `mode-switch` | When switching modes |
| `session-end` | At session termination |
### Checkpoint Storage
Checkpoints are stored in `.workflow/checkpoints/`:
```
.workflow/checkpoints/
├── 2025-02-18T10-30-45-sess123.json
├── 2025-02-18T11-15-22-sess123.json
└── ...
```
### Checkpoint Contents
```json
{
"id": "2025-02-18T10-30-45-sess123",
"created_at": "2025-02-18T10:30:45.000Z",
"trigger": "compact",
"session_id": "sess123",
"project_path": "/path/to/project",
"workflow_state": null,
"mode_states": {
"autopilot": {
"active": true,
"activatedAt": "2025-02-18T10:00:00.000Z"
}
},
"memory_context": null,
"todo_summary": {
"pending": 3,
"in_progress": 1,
"completed": 5
}
}
```
### Recovery Flow
```
┌─────────────────┐
│ Session Start │
└────────┬────────┘
v
┌─────────────────┐ No checkpoint
│ Check Recovery │ ──────────────────► Continue normally
└────────┬────────┘
│ Checkpoint found
v
┌─────────────────┐
│ Load Checkpoint │
└────────┬────────┘
v
┌─────────────────┐
│ Restore Modes │
└────────┬────────┘
v
┌─────────────────┐
│ Inject Message │
└─────────────────┘
```
### Automatic Cleanup
Only the last 10 checkpoints per session are retained. Older checkpoints are automatically removed.
---
## PreCompact Hook with Mutex
The PreCompact hook uses a mutex to prevent concurrent compaction operations for the same directory.
### Mutex Behavior
```
Request 1 ──► PreCompact ──► Create checkpoint ──► Return
Request 2 ──► Wait for Request 1 ────────────────┘
```
This prevents race conditions when multiple subagent results arrive simultaneously (e.g., in swarm/ultrawork modes).
---
## API Endpoint
### Trigger Hook
@@ -105,15 +358,17 @@ Content-Type: application/json
- `?path=/project/path`: Override project path
- `?format=markdown|json`: Response format (default: markdown)
---
## Progressive Disclosure Output Format
The hook returns a structured Markdown document:
```markdown
<ccw-session-context>
## 📋 Related Sessions Index
## Related Sessions Index
### 🔗 Active Cluster: {cluster_name} ({member_count} sessions)
### Active Cluster: {cluster_name} ({member_count} sessions)
**Intent**: {cluster_intent}
| # | Session | Type | Summary | Tokens |
@@ -130,11 +385,11 @@ ccw core-memory load {session_id}
ccw core-memory load-cluster {cluster_id}
```
### 📊 Timeline
### Timeline
```
2024-12-15 ─●─ WFS-001 (Implement auth)
2024-12-15 -- WFS-001 (Implement auth)
2024-12-16 ─●─ CLI-002 (Fix login bug) Current
2024-12-16 -- CLI-002 (Fix login bug) <- Current
```
---
@@ -142,9 +397,9 @@ ccw core-memory load-cluster {cluster_id}
</ccw-session-context>
```
## Examples
---
### Example 1: Basic Session Start Hook
## Complete Configuration Example
```json
{
@@ -152,78 +407,88 @@ ccw core-memory load-cluster {cluster_id}
"session-start": [
{
"name": "Progressive Disclosure",
"description": "Injects progressive disclosure index at session start with recovery detection",
"enabled": true,
"handler": "internal:context",
"timeout": 5000,
"failMode": "silent"
}
]
}
}
```
### Example 2: Custom Command Hook
```json
{
"hooks": {
],
"session-end": [
{
"name": "Update Cluster",
"name": "Update Cluster Metadata",
"description": "Updates cluster metadata after session ends",
"enabled": true,
"command": "ccw core-memory update-cluster --session $SESSION_ID",
"timeout": 30000,
"async": true,
"failMode": "log"
},
{
"name": "Mode State Cleanup",
"description": "Deactivates all active modes for the session",
"enabled": true,
"command": "ccw hook session-end --stdin",
"timeout": 5000,
"failMode": "silent"
}
]
}
}
```
### Example 3: File Modification Hook
```json
{
"hooks": {
],
"PreCompact": [
{
"name": "Create Checkpoint",
"description": "Creates checkpoint before context compaction",
"enabled": true,
"command": "ccw hook precompact --stdin",
"timeout": 10000,
"failMode": "log"
}
],
"Stop": [
{
"name": "Soft Enforcement Stop",
"description": "Injects continuation messages for active workflows/modes",
"enabled": true,
"command": "ccw hook stop --stdin",
"timeout": 5000,
"failMode": "silent"
}
],
"UserPromptSubmit": [
{
"name": "Keyword Detection",
"description": "Detects mode keywords in prompts and activates corresponding modes",
"enabled": true,
"command": "ccw hook keyword --stdin",
"timeout": 5000,
"failMode": "silent"
}
],
"file-modified": [
{
"name": "Auto Commit",
"name": "Auto Commit Checkpoint",
"description": "Creates git checkpoint on file modifications",
"enabled": false,
"command": "git add $FILE_PATH && git commit -m '[Auto] Save: $FILE_PATH'",
"command": "git add . && git commit -m \"[Auto] Checkpoint: $FILE_PATH\"",
"timeout": 10000,
"async": true,
"failMode": "log"
}
]
},
"notes": {
"handler": "Use 'internal:context' for built-in context generation, or 'command' for external commands",
"failMode": "Options: 'silent' (ignore errors), 'log' (log errors), 'fail' (abort on error)",
"variables": "Available: $SESSION_ID, $FILE_PATH, $PROJECT_PATH, $CLUSTER_ID",
"async": "Async hooks run in background and don't block the main flow",
"Stop hook": "The Stop hook uses Soft Enforcement - it never blocks but may inject continuation messages",
"PreCompact hook": "Creates checkpoint before compaction; uses mutex to prevent concurrent operations",
"UserPromptSubmit hook": "Detects mode keywords and activates corresponding execution modes",
"session-end hook": "Cleans up mode states using ModeRegistryService.deactivateMode()"
}
}
```
## Implementation Details
### Handler: `internal:context`
The built-in context handler:
1. Determines the current session ID
2. Queries `SessionClusteringService` for related clusters
3. Retrieves cluster members and their metadata
4. Generates a progressive disclosure index
5. Returns formatted Markdown within `<ccw-session-context>` tags
### Timeout Behavior
- Hooks have a maximum execution time (default: 5 seconds)
- If timeout is exceeded, the hook is terminated
- Behavior depends on `failMode`:
- `silent`: Continues without notification
- `log`: Logs timeout error
- `fail`: Aborts session start (not recommended)
### Error Handling
All errors are caught and handled according to `failMode`. The system ensures that hook failures never block the main workflow.
---
## Testing
@@ -239,6 +504,16 @@ curl -X POST http://localhost:3456/api/hook \
ccw core-memory context --format markdown
```
### Run Integration Tests
```bash
# Run all hook integration tests
node --test tests/integration/hooks-integration.test.ts
# Run with verbose output
node --test tests/integration/hooks-integration.test.ts --test-name-pattern="INT-.*"
```
### Expected Output
If a cluster exists:
@@ -250,6 +525,8 @@ If no cluster exists:
- Message indicating no cluster found
- Commands to search or trigger clustering
---
## Troubleshooting
### Hook Not Triggering
@@ -270,25 +547,93 @@ If no cluster exists:
2. Verify session metadata exists
3. Check that the session has been added to a cluster
### Mode Not Activating
1. Check for conflicting exclusive modes
2. Verify keyword spelling (case-insensitive)
3. Check that keyword is not inside code blocks
### Checkpoint Not Created
1. Verify `.workflow/checkpoints/` directory exists
2. Check disk space
3. Review logs for error messages
### Recovery Not Working
1. Verify checkpoint exists in `.workflow/checkpoints/`
2. Check that session ID matches
3. Ensure checkpoint file is valid JSON
---
## Performance Considerations
- Progressive disclosure index generation is fast (< 1 second typical)
- Uses cached metadata to avoid full session parsing
- Timeout enforced to prevent blocking
- Failures return empty content instead of errors
- Mutex prevents concurrent compaction operations
- Stale marker cleanup runs automatically
## Future Enhancements
---
- **Dynamic Clustering**: Real-time cluster updates during session
- **Multi-Cluster Support**: Show sessions from multiple related clusters
- **Relevance Scoring**: Sort sessions by relevance to current task
- **Token Budget**: Calculate total token usage for context loading
- **Hook Chains**: Execute multiple hooks in sequence
- **Conditional Hooks**: Execute hooks based on project state
## Architecture Diagram
```
┌─────────────────────────────────────────────────────────────┐
│ Claude Code Session │
└─────────────────────────────────────────────────────────────┘
│ Hook Events
v
┌─────────────────────────────────────────────────────────────┐
│ Hook System │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │Session Start │ │ PreCompact │ │ Stop │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
└─────────┼─────────────────┼──────────────────┼──────────────┘
│ │ │
v v v
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│RecoveryHandler │ │CheckpointService│ │ StopHandler │
│ │ │ │ │ │
│ - checkRecovery │ │ - create │ │ - SoftEnforce │
│ - formatMessage │ │ - save │ │ - detectMode │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
v v v
┌─────────────────────────────────────────────────────────────┐
│ ModeRegistryService │
│ │
│ - activateMode / deactivateMode │
│ - getActiveModes / canStartMode │
│ - cleanupStaleMarkers │
│ │
│ Storage: .workflow/modes/sessions/{sessionId}/ │
└─────────────────────────────────────────────────────────────┘
v
┌─────────────────────────────────────────────────────────────┐
│ Checkpoint Storage │
│ │
│ .workflow/checkpoints/{checkpoint-id}.json │
│ - session_id, trigger, mode_states, workflow_state │
│ - Automatic cleanup (keep last 10 per session) │
└─────────────────────────────────────────────────────────────┘
```
---
## References
- **Session Clustering**: See `session-clustering-service.ts`
- **Core Memory Store**: See `core-memory-store.ts`
- **Hook Routes**: See `routes/hooks-routes.ts`
- **Example Configuration**: See `hooks-config-example.json`
- **Stop Handler**: See `core/hooks/stop-handler.ts`
- **Mode Registry**: See `core/services/mode-registry-service.ts`
- **Checkpoint Service**: See `core/services/checkpoint-service.ts`
- **Recovery Handler**: See `core/hooks/recovery-handler.ts`
- **Keyword Detector**: See `core/hooks/keyword-detector.ts`
- **Example Configuration**: See `templates/hooks-config-example.json`