claude-mpm 4.1.10__py3-none-any.whl → 4.1.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/INSTRUCTIONS.md +8 -0
- claude_mpm/cli/__init__.py +11 -0
- claude_mpm/cli/commands/analyze.py +2 -1
- claude_mpm/cli/commands/configure.py +9 -8
- claude_mpm/cli/commands/configure_tui.py +3 -1
- claude_mpm/cli/commands/dashboard.py +288 -0
- claude_mpm/cli/commands/debug.py +0 -1
- claude_mpm/cli/commands/mpm_init.py +442 -0
- claude_mpm/cli/commands/mpm_init_handler.py +84 -0
- claude_mpm/cli/parsers/base_parser.py +15 -0
- claude_mpm/cli/parsers/dashboard_parser.py +113 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +128 -0
- claude_mpm/constants.py +10 -0
- claude_mpm/core/config.py +18 -0
- claude_mpm/core/instruction_reinforcement_hook.py +266 -0
- claude_mpm/core/pm_hook_interceptor.py +105 -8
- claude_mpm/dashboard/analysis_runner.py +52 -25
- claude_mpm/dashboard/static/built/components/activity-tree.js +1 -1
- claude_mpm/dashboard/static/built/components/code-tree.js +2 -0
- claude_mpm/dashboard/static/built/components/code-viewer.js +2 -0
- claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/built/dashboard.js +1 -1
- claude_mpm/dashboard/static/built/socket-client.js +1 -1
- claude_mpm/dashboard/static/css/code-tree.css +330 -1
- claude_mpm/dashboard/static/dist/components/activity-tree.js +1 -1
- claude_mpm/dashboard/static/dist/components/code-tree.js +2593 -2
- claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/activity-tree.js +212 -13
- claude_mpm/dashboard/static/js/components/build-tracker.js +15 -13
- claude_mpm/dashboard/static/js/components/code-tree.js +2503 -917
- claude_mpm/dashboard/static/js/components/event-viewer.js +58 -19
- claude_mpm/dashboard/static/js/dashboard.js +46 -44
- claude_mpm/dashboard/static/js/socket-client.js +74 -32
- claude_mpm/dashboard/templates/index.html +25 -20
- claude_mpm/services/agents/deployment/agent_template_builder.py +11 -7
- claude_mpm/services/agents/memory/memory_format_service.py +3 -1
- claude_mpm/services/cli/agent_cleanup_service.py +1 -4
- claude_mpm/services/cli/socketio_manager.py +39 -8
- claude_mpm/services/cli/startup_checker.py +0 -1
- claude_mpm/services/core/cache_manager.py +0 -1
- claude_mpm/services/infrastructure/monitoring.py +1 -1
- claude_mpm/services/socketio/event_normalizer.py +64 -0
- claude_mpm/services/socketio/handlers/code_analysis.py +449 -0
- claude_mpm/services/socketio/server/connection_manager.py +3 -1
- claude_mpm/tools/code_tree_analyzer.py +930 -24
- claude_mpm/tools/code_tree_builder.py +0 -1
- claude_mpm/tools/code_tree_events.py +113 -15
- {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.12.dist-info}/METADATA +2 -1
- {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.12.dist-info}/RECORD +56 -48
- {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.12.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.12.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.12.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.12.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MPM-Init parser module for claude-mpm CLI.
|
|
3
|
+
|
|
4
|
+
WHY: This module handles the mpm-init command parser configuration,
|
|
5
|
+
providing a clean interface for initializing projects with optimal
|
|
6
|
+
Claude Code and Claude MPM standards.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def add_mpm_init_subparser(subparsers: Any) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Add the mpm-init subparser to the main parser.
|
|
16
|
+
|
|
17
|
+
WHY: The mpm-init command sets up projects for optimal use with
|
|
18
|
+
Claude Code and Claude MPM by delegating to the Agentic Coder Optimizer.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
subparsers: The subparsers object to add the mpm-init command to
|
|
22
|
+
"""
|
|
23
|
+
mpm_init_parser = subparsers.add_parser(
|
|
24
|
+
"mpm-init",
|
|
25
|
+
help="Initialize project for optimal Claude Code and Claude MPM usage",
|
|
26
|
+
description=(
|
|
27
|
+
"Initialize a project with comprehensive documentation, single-path workflows, "
|
|
28
|
+
"and optimized structure for AI agent understanding. Uses the Agentic Coder "
|
|
29
|
+
"Optimizer agent to establish clear standards and remove ambiguity."
|
|
30
|
+
),
|
|
31
|
+
epilog=(
|
|
32
|
+
"Examples:\n"
|
|
33
|
+
" claude-mpm mpm-init # Initialize current directory\n"
|
|
34
|
+
" claude-mpm mpm-init --project-type web # Initialize as web project\n"
|
|
35
|
+
" claude-mpm mpm-init --framework react # Initialize with React framework\n"
|
|
36
|
+
" claude-mpm mpm-init /path/to/project --force # Force reinitialize project"
|
|
37
|
+
),
|
|
38
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Project configuration options
|
|
42
|
+
config_group = mpm_init_parser.add_argument_group("project configuration")
|
|
43
|
+
config_group.add_argument(
|
|
44
|
+
"--project-type",
|
|
45
|
+
choices=[
|
|
46
|
+
"web",
|
|
47
|
+
"api",
|
|
48
|
+
"cli",
|
|
49
|
+
"library",
|
|
50
|
+
"mobile",
|
|
51
|
+
"desktop",
|
|
52
|
+
"fullstack",
|
|
53
|
+
"data",
|
|
54
|
+
"ml",
|
|
55
|
+
],
|
|
56
|
+
help="Type of project to initialize (auto-detected if not specified)",
|
|
57
|
+
)
|
|
58
|
+
config_group.add_argument(
|
|
59
|
+
"--framework",
|
|
60
|
+
type=str,
|
|
61
|
+
help="Specific framework to configure (e.g., react, vue, django, fastapi, express)",
|
|
62
|
+
)
|
|
63
|
+
config_group.add_argument(
|
|
64
|
+
"--language",
|
|
65
|
+
choices=["python", "javascript", "typescript", "go", "rust", "java", "cpp"],
|
|
66
|
+
help="Primary programming language (auto-detected if not specified)",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Initialization options
|
|
70
|
+
init_group = mpm_init_parser.add_argument_group("initialization options")
|
|
71
|
+
init_group.add_argument(
|
|
72
|
+
"--force",
|
|
73
|
+
action="store_true",
|
|
74
|
+
help="Force reinitialization even if project is already configured",
|
|
75
|
+
)
|
|
76
|
+
init_group.add_argument(
|
|
77
|
+
"--minimal",
|
|
78
|
+
action="store_true",
|
|
79
|
+
help="Create minimal configuration (CLAUDE.md only, no additional setup)",
|
|
80
|
+
)
|
|
81
|
+
init_group.add_argument(
|
|
82
|
+
"--comprehensive",
|
|
83
|
+
action="store_true",
|
|
84
|
+
help="Create comprehensive setup including CI/CD, testing, and deployment configs",
|
|
85
|
+
)
|
|
86
|
+
init_group.add_argument(
|
|
87
|
+
"--use-venv",
|
|
88
|
+
action="store_true",
|
|
89
|
+
help="Use traditional Python venv instead of mamba/conda environment",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Template options
|
|
93
|
+
template_group = mpm_init_parser.add_argument_group("template options")
|
|
94
|
+
template_group.add_argument(
|
|
95
|
+
"--template",
|
|
96
|
+
type=str,
|
|
97
|
+
help="Use a specific template from claude-mpm templates library",
|
|
98
|
+
)
|
|
99
|
+
template_group.add_argument(
|
|
100
|
+
"--list-templates", action="store_true", help="List available project templates"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Output options
|
|
104
|
+
output_group = mpm_init_parser.add_argument_group("output options")
|
|
105
|
+
output_group.add_argument(
|
|
106
|
+
"--dry-run",
|
|
107
|
+
action="store_true",
|
|
108
|
+
help="Show what would be done without making changes",
|
|
109
|
+
)
|
|
110
|
+
output_group.add_argument(
|
|
111
|
+
"--json", action="store_true", help="Output results in JSON format"
|
|
112
|
+
)
|
|
113
|
+
output_group.add_argument(
|
|
114
|
+
"--verbose",
|
|
115
|
+
action="store_true",
|
|
116
|
+
help="Show detailed output during initialization",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Path argument
|
|
120
|
+
mpm_init_parser.add_argument(
|
|
121
|
+
"project_path",
|
|
122
|
+
nargs="?",
|
|
123
|
+
default=".",
|
|
124
|
+
help="Path to project directory (default: current directory)",
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Set the command handler
|
|
128
|
+
mpm_init_parser.set_defaults(command="mpm-init")
|
claude_mpm/constants.py
CHANGED
|
@@ -43,6 +43,7 @@ class CLICommands(str, Enum):
|
|
|
43
43
|
CLEANUP = "cleanup-memory"
|
|
44
44
|
MCP = "mcp"
|
|
45
45
|
DOCTOR = "doctor"
|
|
46
|
+
DASHBOARD = "dashboard"
|
|
46
47
|
|
|
47
48
|
def with_prefix(self, prefix: CLIPrefix = CLIPrefix.MPM) -> str:
|
|
48
49
|
"""Get command with prefix."""
|
|
@@ -99,6 +100,15 @@ class MonitorCommands(str, Enum):
|
|
|
99
100
|
PORT = "port"
|
|
100
101
|
|
|
101
102
|
|
|
103
|
+
class DashboardCommands(str, Enum):
|
|
104
|
+
"""Dashboard subcommand constants."""
|
|
105
|
+
|
|
106
|
+
START = "start"
|
|
107
|
+
STOP = "stop"
|
|
108
|
+
STATUS = "status"
|
|
109
|
+
OPEN = "open"
|
|
110
|
+
|
|
111
|
+
|
|
102
112
|
class ConfigCommands(str, Enum):
|
|
103
113
|
"""Config subcommand constants."""
|
|
104
114
|
|
claude_mpm/core/config.py
CHANGED
|
@@ -533,6 +533,24 @@ class Config:
|
|
|
533
533
|
"exclude_dependencies": False, # Whether to exclude agent dependencies too
|
|
534
534
|
"case_sensitive": False, # Whether agent name matching is case-sensitive
|
|
535
535
|
},
|
|
536
|
+
# Instruction reinforcement system configuration
|
|
537
|
+
"instruction_reinforcement": {
|
|
538
|
+
"enabled": True,
|
|
539
|
+
"test_mode": True,
|
|
540
|
+
"injection_interval": 5,
|
|
541
|
+
"test_messages": [
|
|
542
|
+
"[TEST-REMINDER] This is an injected instruction reminder",
|
|
543
|
+
"[PM-INSTRUCTION] Remember to delegate all work to agents",
|
|
544
|
+
"[PM-INSTRUCTION] Do not use Edit, Write, or Bash tools directly",
|
|
545
|
+
"[PM-INSTRUCTION] Your role is orchestration and coordination",
|
|
546
|
+
],
|
|
547
|
+
"production_messages": [
|
|
548
|
+
"[PM-REMINDER] Delegate implementation tasks to specialized agents",
|
|
549
|
+
"[PM-REMINDER] Use Task tool for all work delegation",
|
|
550
|
+
"[PM-REMINDER] Focus on orchestration, not implementation",
|
|
551
|
+
"[PM-REMINDER] Your role is coordination and management",
|
|
552
|
+
],
|
|
553
|
+
},
|
|
536
554
|
}
|
|
537
555
|
|
|
538
556
|
# Apply defaults for missing keys
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""Instruction Reinforcement Hook for PM instruction drift prevention.
|
|
2
|
+
|
|
3
|
+
This module implements a hook that intercepts TodoWrite calls and injects
|
|
4
|
+
reminder messages at configurable intervals to combat PM instruction drift
|
|
5
|
+
during long conversations.
|
|
6
|
+
|
|
7
|
+
WHY this is needed:
|
|
8
|
+
- PM agents can drift from their original instructions during long sessions
|
|
9
|
+
- Direct tool usage (Edit, Write, Bash) is a common drift pattern
|
|
10
|
+
- Reminder injection via TodoWrite is a non-intrusive way to reinforce instructions
|
|
11
|
+
- Configurable intervals and message rotation provide flexibility
|
|
12
|
+
|
|
13
|
+
The hook works by:
|
|
14
|
+
1. Tracking TodoWrite call count
|
|
15
|
+
2. Injecting reminders at configured intervals (default: every 5 calls)
|
|
16
|
+
3. Rotating through multiple reminder messages
|
|
17
|
+
4. Providing metrics for monitoring effectiveness
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import threading
|
|
21
|
+
from datetime import datetime
|
|
22
|
+
from typing import Any, Dict, List, Optional
|
|
23
|
+
|
|
24
|
+
from ..core.logger import get_logger
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class InstructionReinforcementHook:
|
|
28
|
+
"""Hook for injecting instruction reminders into TodoWrite calls.
|
|
29
|
+
|
|
30
|
+
This class intercepts TodoWrite operations and periodically injects
|
|
31
|
+
reminder messages to help prevent PM instruction drift.
|
|
32
|
+
|
|
33
|
+
Key features:
|
|
34
|
+
- Configurable injection intervals
|
|
35
|
+
- Message rotation for variety
|
|
36
|
+
- Thread-safe operation
|
|
37
|
+
- Metrics tracking
|
|
38
|
+
- Test mode support
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
42
|
+
"""Initialize the instruction reinforcement hook.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
config: Configuration dictionary with optional keys:
|
|
46
|
+
- enabled: Whether the hook is enabled (default: True)
|
|
47
|
+
- test_mode: Whether to use test messages (default: True)
|
|
48
|
+
- injection_interval: Calls between injections (default: 5)
|
|
49
|
+
- test_messages: List of test messages to rotate through
|
|
50
|
+
"""
|
|
51
|
+
self.logger = get_logger("instruction_reinforcement_hook")
|
|
52
|
+
|
|
53
|
+
# Initialize configuration
|
|
54
|
+
config = config or {}
|
|
55
|
+
self.enabled = config.get("enabled", True)
|
|
56
|
+
self.test_mode = config.get("test_mode", True)
|
|
57
|
+
self.injection_interval = config.get("injection_interval", 5)
|
|
58
|
+
|
|
59
|
+
# Thread-safe counters
|
|
60
|
+
self._lock = threading.Lock()
|
|
61
|
+
self.call_count = 0
|
|
62
|
+
self.injection_count = 0
|
|
63
|
+
self.message_index = 0
|
|
64
|
+
|
|
65
|
+
# Default test messages (will be used in test_mode)
|
|
66
|
+
self.test_messages = config.get(
|
|
67
|
+
"test_messages",
|
|
68
|
+
[
|
|
69
|
+
"[TEST-REMINDER] This is an injected instruction reminder",
|
|
70
|
+
"[PM-INSTRUCTION] Remember to delegate all work to agents",
|
|
71
|
+
"[PM-INSTRUCTION] Do not use Edit, Write, or Bash tools directly",
|
|
72
|
+
"[PM-INSTRUCTION] Your role is orchestration and coordination",
|
|
73
|
+
],
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Production messages (used when test_mode=False)
|
|
77
|
+
self.production_messages = [
|
|
78
|
+
"[STOP] Are you about to use Edit/Write/Bash? Delegate to Engineer instead!",
|
|
79
|
+
"[DELEGATE] Your job is coordination, not implementation - pass this to an agent",
|
|
80
|
+
"[REMINDER] PM = Project Manager, not Project Implementer - delegate this work",
|
|
81
|
+
"[CHECK] If you're reading code files, stop and delegate to Research Agent",
|
|
82
|
+
"[WARNING] Direct implementation detected - use 'do this yourself' to override",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
self.logger.info(
|
|
86
|
+
f"InstructionReinforcementHook initialized: "
|
|
87
|
+
f"enabled={self.enabled}, test_mode={self.test_mode}, "
|
|
88
|
+
f"interval={self.injection_interval}"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def intercept_todowrite(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
92
|
+
"""Intercept TodoWrite parameters and potentially inject reminders.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
params: TodoWrite parameters dictionary containing 'todos' list
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Modified parameters with potentially injected reminders
|
|
99
|
+
"""
|
|
100
|
+
if not self.enabled:
|
|
101
|
+
return params
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
# Extract todos safely
|
|
105
|
+
todos = params.get("todos", [])
|
|
106
|
+
if not isinstance(todos, list):
|
|
107
|
+
self.logger.warning("Invalid todos format - skipping injection")
|
|
108
|
+
return params
|
|
109
|
+
|
|
110
|
+
with self._lock:
|
|
111
|
+
self.call_count += 1
|
|
112
|
+
|
|
113
|
+
# Check if we should inject
|
|
114
|
+
if self.should_inject():
|
|
115
|
+
modified_todos = self.inject_reminders(todos)
|
|
116
|
+
params = params.copy() # Don't modify original
|
|
117
|
+
params["todos"] = modified_todos
|
|
118
|
+
self.injection_count += 1
|
|
119
|
+
|
|
120
|
+
self.logger.info(
|
|
121
|
+
f"Injected reminder #{self.injection_count} at call #{self.call_count}"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return params
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
self.logger.error(f"Error in TodoWrite interception: {e}")
|
|
128
|
+
# Return original params on error to avoid breaking functionality
|
|
129
|
+
return params
|
|
130
|
+
|
|
131
|
+
def should_inject(self) -> bool:
|
|
132
|
+
"""Determine if a reminder should be injected.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
True if injection should occur
|
|
136
|
+
"""
|
|
137
|
+
if not self.enabled:
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
return self.call_count % self.injection_interval == 0
|
|
141
|
+
|
|
142
|
+
def inject_reminders(self, todos: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
143
|
+
"""Inject reminder message into todos list.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
todos: Original todos list
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Modified todos list with reminder injected at the beginning
|
|
150
|
+
"""
|
|
151
|
+
# Choose message set based on mode
|
|
152
|
+
messages = self.test_messages if self.test_mode else self.production_messages
|
|
153
|
+
|
|
154
|
+
# Get next message (rotate through available messages)
|
|
155
|
+
message = messages[self.message_index % len(messages)]
|
|
156
|
+
self.message_index += 1
|
|
157
|
+
|
|
158
|
+
# Create reminder todo
|
|
159
|
+
reminder_todo = {
|
|
160
|
+
"content": message,
|
|
161
|
+
"status": "pending",
|
|
162
|
+
"activeForm": "Processing instruction reminder",
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# Insert at beginning of todos list
|
|
166
|
+
modified_todos = todos.copy()
|
|
167
|
+
modified_todos.insert(0, reminder_todo)
|
|
168
|
+
|
|
169
|
+
self.logger.debug(f"Injected reminder: {message}")
|
|
170
|
+
|
|
171
|
+
return modified_todos
|
|
172
|
+
|
|
173
|
+
def get_metrics(self) -> Dict[str, Any]:
|
|
174
|
+
"""Get current hook metrics.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Dictionary containing metrics:
|
|
178
|
+
- call_count: Total TodoWrite calls processed
|
|
179
|
+
- injection_count: Total reminders injected
|
|
180
|
+
- injection_rate: Ratio of injections to calls
|
|
181
|
+
- next_injection_in: Calls until next injection
|
|
182
|
+
- enabled: Whether hook is enabled
|
|
183
|
+
- test_mode: Whether using test messages
|
|
184
|
+
"""
|
|
185
|
+
with self._lock:
|
|
186
|
+
next_injection = self.injection_interval - (
|
|
187
|
+
self.call_count % self.injection_interval
|
|
188
|
+
)
|
|
189
|
+
if next_injection == self.injection_interval:
|
|
190
|
+
next_injection = 0 # Next call will trigger injection
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
"call_count": self.call_count,
|
|
194
|
+
"injection_count": self.injection_count,
|
|
195
|
+
"injection_rate": self.injection_count / max(self.call_count, 1),
|
|
196
|
+
"next_injection_in": next_injection,
|
|
197
|
+
"enabled": self.enabled,
|
|
198
|
+
"test_mode": self.test_mode,
|
|
199
|
+
"injection_interval": self.injection_interval,
|
|
200
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
def reset_counters(self):
|
|
204
|
+
"""Reset all counters to zero.
|
|
205
|
+
|
|
206
|
+
Useful for testing or starting fresh tracking.
|
|
207
|
+
"""
|
|
208
|
+
with self._lock:
|
|
209
|
+
self.call_count = 0
|
|
210
|
+
self.injection_count = 0
|
|
211
|
+
self.message_index = 0
|
|
212
|
+
|
|
213
|
+
self.logger.info("Reset instruction reinforcement counters")
|
|
214
|
+
|
|
215
|
+
def update_config(self, config: Dict[str, Any]):
|
|
216
|
+
"""Update hook configuration.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
config: New configuration values
|
|
220
|
+
"""
|
|
221
|
+
if "enabled" in config:
|
|
222
|
+
self.enabled = config["enabled"]
|
|
223
|
+
if "test_mode" in config:
|
|
224
|
+
self.test_mode = config["test_mode"]
|
|
225
|
+
if "injection_interval" in config:
|
|
226
|
+
self.injection_interval = max(1, config["injection_interval"]) # Minimum 1
|
|
227
|
+
if "test_messages" in config:
|
|
228
|
+
self.test_messages = config["test_messages"]
|
|
229
|
+
|
|
230
|
+
self.logger.info(f"Updated configuration: {config}")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# Global instance for singleton pattern
|
|
234
|
+
_instruction_reinforcement_hook: Optional[InstructionReinforcementHook] = None
|
|
235
|
+
_hook_lock = threading.Lock()
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def get_instruction_reinforcement_hook(
|
|
239
|
+
config: Optional[Dict[str, Any]] = None,
|
|
240
|
+
) -> InstructionReinforcementHook:
|
|
241
|
+
"""Get the global instruction reinforcement hook instance.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
config: Configuration for first-time initialization
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
InstructionReinforcementHook instance
|
|
248
|
+
"""
|
|
249
|
+
global _instruction_reinforcement_hook
|
|
250
|
+
|
|
251
|
+
with _hook_lock:
|
|
252
|
+
if _instruction_reinforcement_hook is None:
|
|
253
|
+
_instruction_reinforcement_hook = InstructionReinforcementHook(config)
|
|
254
|
+
|
|
255
|
+
return _instruction_reinforcement_hook
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def reset_instruction_reinforcement_hook():
|
|
259
|
+
"""Reset the global hook instance.
|
|
260
|
+
|
|
261
|
+
Primarily used for testing to ensure clean state.
|
|
262
|
+
"""
|
|
263
|
+
global _instruction_reinforcement_hook
|
|
264
|
+
|
|
265
|
+
with _hook_lock:
|
|
266
|
+
_instruction_reinforcement_hook = None
|
|
@@ -19,6 +19,7 @@ from typing import Any, Dict, List, Optional
|
|
|
19
19
|
|
|
20
20
|
from ..core.hook_manager import get_hook_manager
|
|
21
21
|
from ..core.logger import get_logger
|
|
22
|
+
from .instruction_reinforcement_hook import get_instruction_reinforcement_hook
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class PMHookInterceptor:
|
|
@@ -31,11 +32,16 @@ class PMHookInterceptor:
|
|
|
31
32
|
- Provides real-time event streaming for PM operations
|
|
32
33
|
"""
|
|
33
34
|
|
|
34
|
-
def __init__(self):
|
|
35
|
+
def __init__(self, instruction_reinforcement_config=None):
|
|
35
36
|
self.logger = get_logger("pm_hook_interceptor")
|
|
36
37
|
self.hook_manager = get_hook_manager()
|
|
37
38
|
self._in_intercept = threading.local() # Prevent recursion
|
|
38
39
|
|
|
40
|
+
# Initialize instruction reinforcement hook
|
|
41
|
+
self.instruction_reinforcement_hook = get_instruction_reinforcement_hook(
|
|
42
|
+
instruction_reinforcement_config
|
|
43
|
+
)
|
|
44
|
+
|
|
39
45
|
def intercept_todowrite(self, original_function):
|
|
40
46
|
"""Decorator to intercept TodoWrite calls and trigger hooks.
|
|
41
47
|
|
|
@@ -58,12 +64,29 @@ class PMHookInterceptor:
|
|
|
58
64
|
# Extract todos from arguments
|
|
59
65
|
todos = self._extract_todos_from_args(args, kwargs)
|
|
60
66
|
|
|
67
|
+
# Apply instruction reinforcement hook (modify parameters if needed)
|
|
68
|
+
params = {"todos": todos}
|
|
69
|
+
modified_params = (
|
|
70
|
+
self.instruction_reinforcement_hook.intercept_todowrite(params)
|
|
71
|
+
)
|
|
72
|
+
modified_todos = modified_params.get("todos", todos)
|
|
73
|
+
|
|
74
|
+
# Update args/kwargs with potentially modified todos
|
|
75
|
+
if modified_todos != todos:
|
|
76
|
+
args, kwargs = self._update_args_with_todos(
|
|
77
|
+
args, kwargs, modified_todos
|
|
78
|
+
)
|
|
79
|
+
self.logger.debug(
|
|
80
|
+
f"Applied instruction reinforcement: {len(modified_todos)} todos"
|
|
81
|
+
)
|
|
82
|
+
|
|
61
83
|
# Trigger pre-tool hook
|
|
62
84
|
self.hook_manager.trigger_pre_tool_hook(
|
|
63
|
-
"TodoWrite",
|
|
85
|
+
"TodoWrite",
|
|
86
|
+
{"todos": modified_todos, "source": "PM", "intercepted": True},
|
|
64
87
|
)
|
|
65
88
|
|
|
66
|
-
# Call the original function
|
|
89
|
+
# Call the original function with potentially modified arguments
|
|
67
90
|
result = original_function(*args, **kwargs)
|
|
68
91
|
|
|
69
92
|
# Trigger post-tool hook
|
|
@@ -71,14 +94,18 @@ class PMHookInterceptor:
|
|
|
71
94
|
"TodoWrite",
|
|
72
95
|
0,
|
|
73
96
|
{
|
|
74
|
-
"todos_count": len(
|
|
97
|
+
"todos_count": len(modified_todos) if modified_todos else 0,
|
|
98
|
+
"original_todos_count": len(todos) if todos else 0,
|
|
75
99
|
"source": "PM",
|
|
76
100
|
"success": True,
|
|
101
|
+
"instruction_reinforcement_applied": len(modified_todos)
|
|
102
|
+
!= len(todos),
|
|
77
103
|
},
|
|
78
104
|
)
|
|
79
105
|
|
|
80
106
|
self.logger.debug(
|
|
81
|
-
f"Successfully intercepted TodoWrite with {len(
|
|
107
|
+
f"Successfully intercepted TodoWrite with {len(modified_todos) if modified_todos else 0} todos "
|
|
108
|
+
f"(original: {len(todos) if todos else 0})"
|
|
82
109
|
)
|
|
83
110
|
|
|
84
111
|
return result
|
|
@@ -119,6 +146,39 @@ class PMHookInterceptor:
|
|
|
119
146
|
|
|
120
147
|
return []
|
|
121
148
|
|
|
149
|
+
def _update_args_with_todos(
|
|
150
|
+
self, args, kwargs, modified_todos: List[Dict[str, Any]]
|
|
151
|
+
):
|
|
152
|
+
"""Update function arguments with modified todos list.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
args: Original positional arguments
|
|
156
|
+
kwargs: Original keyword arguments
|
|
157
|
+
modified_todos: Modified todos list to inject
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Tuple of (updated_args, updated_kwargs)
|
|
161
|
+
"""
|
|
162
|
+
# Update kwargs if todos was passed as keyword argument
|
|
163
|
+
if "todos" in kwargs:
|
|
164
|
+
kwargs = kwargs.copy()
|
|
165
|
+
kwargs["todos"] = modified_todos
|
|
166
|
+
return args, kwargs
|
|
167
|
+
|
|
168
|
+
# Update positional args if todos was passed positionally
|
|
169
|
+
args_list = list(args)
|
|
170
|
+
for i, arg in enumerate(args_list):
|
|
171
|
+
if isinstance(arg, list) and arg and isinstance(arg[0], dict):
|
|
172
|
+
# Check if this looks like a todos list
|
|
173
|
+
if "content" in arg[0] or "id" in arg[0]:
|
|
174
|
+
args_list[i] = modified_todos
|
|
175
|
+
return tuple(args_list), kwargs
|
|
176
|
+
|
|
177
|
+
# If we can't find where todos was passed, add as keyword argument
|
|
178
|
+
kwargs = kwargs.copy()
|
|
179
|
+
kwargs["todos"] = modified_todos
|
|
180
|
+
return args, kwargs
|
|
181
|
+
|
|
122
182
|
def trigger_manual_todowrite_hooks(self, todos: List[Dict[str, Any]]):
|
|
123
183
|
"""Manually trigger TodoWrite hooks for given todos.
|
|
124
184
|
|
|
@@ -169,16 +229,37 @@ class PMHookInterceptor:
|
|
|
169
229
|
self.logger.error(f"Error manually triggering TodoWrite hooks: {e}")
|
|
170
230
|
return False
|
|
171
231
|
|
|
232
|
+
def get_instruction_reinforcement_metrics(self) -> Dict[str, Any]:
|
|
233
|
+
"""Get metrics from the instruction reinforcement hook.
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
Dictionary containing reinforcement metrics
|
|
237
|
+
"""
|
|
238
|
+
return self.instruction_reinforcement_hook.get_metrics()
|
|
239
|
+
|
|
240
|
+
def reset_instruction_reinforcement_counters(self):
|
|
241
|
+
"""Reset instruction reinforcement counters."""
|
|
242
|
+
self.instruction_reinforcement_hook.reset_counters()
|
|
243
|
+
self.logger.info("Reset instruction reinforcement counters via PM interceptor")
|
|
244
|
+
|
|
172
245
|
|
|
173
246
|
# Global instance
|
|
174
247
|
_pm_hook_interceptor: Optional[PMHookInterceptor] = None
|
|
175
248
|
|
|
176
249
|
|
|
177
|
-
def get_pm_hook_interceptor() -> PMHookInterceptor:
|
|
178
|
-
"""Get the global PM hook interceptor instance.
|
|
250
|
+
def get_pm_hook_interceptor(instruction_reinforcement_config=None) -> PMHookInterceptor:
|
|
251
|
+
"""Get the global PM hook interceptor instance.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
instruction_reinforcement_config: Configuration for instruction reinforcement hook
|
|
255
|
+
(only used on first initialization)
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
PMHookInterceptor instance
|
|
259
|
+
"""
|
|
179
260
|
global _pm_hook_interceptor
|
|
180
261
|
if _pm_hook_interceptor is None:
|
|
181
|
-
_pm_hook_interceptor = PMHookInterceptor()
|
|
262
|
+
_pm_hook_interceptor = PMHookInterceptor(instruction_reinforcement_config)
|
|
182
263
|
return _pm_hook_interceptor
|
|
183
264
|
|
|
184
265
|
|
|
@@ -215,3 +296,19 @@ def simulate_pm_todowrite_operation(todos: List[Dict[str, Any]]):
|
|
|
215
296
|
|
|
216
297
|
# Log completion
|
|
217
298
|
logger.info("PM TodoWrite simulation completed")
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def get_instruction_reinforcement_metrics() -> Dict[str, Any]:
|
|
302
|
+
"""Get instruction reinforcement metrics from the global PM hook interceptor.
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
Dictionary containing reinforcement metrics
|
|
306
|
+
"""
|
|
307
|
+
interceptor = get_pm_hook_interceptor()
|
|
308
|
+
return interceptor.get_instruction_reinforcement_metrics()
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def reset_instruction_reinforcement_counters():
|
|
312
|
+
"""Reset instruction reinforcement counters in the global PM hook interceptor."""
|
|
313
|
+
interceptor = get_pm_hook_interceptor()
|
|
314
|
+
interceptor.reset_instruction_reinforcement_counters()
|