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.
Files changed (56) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +8 -0
  3. claude_mpm/cli/__init__.py +11 -0
  4. claude_mpm/cli/commands/analyze.py +2 -1
  5. claude_mpm/cli/commands/configure.py +9 -8
  6. claude_mpm/cli/commands/configure_tui.py +3 -1
  7. claude_mpm/cli/commands/dashboard.py +288 -0
  8. claude_mpm/cli/commands/debug.py +0 -1
  9. claude_mpm/cli/commands/mpm_init.py +442 -0
  10. claude_mpm/cli/commands/mpm_init_handler.py +84 -0
  11. claude_mpm/cli/parsers/base_parser.py +15 -0
  12. claude_mpm/cli/parsers/dashboard_parser.py +113 -0
  13. claude_mpm/cli/parsers/mpm_init_parser.py +128 -0
  14. claude_mpm/constants.py +10 -0
  15. claude_mpm/core/config.py +18 -0
  16. claude_mpm/core/instruction_reinforcement_hook.py +266 -0
  17. claude_mpm/core/pm_hook_interceptor.py +105 -8
  18. claude_mpm/dashboard/analysis_runner.py +52 -25
  19. claude_mpm/dashboard/static/built/components/activity-tree.js +1 -1
  20. claude_mpm/dashboard/static/built/components/code-tree.js +2 -0
  21. claude_mpm/dashboard/static/built/components/code-viewer.js +2 -0
  22. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  23. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  24. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  25. claude_mpm/dashboard/static/css/code-tree.css +330 -1
  26. claude_mpm/dashboard/static/dist/components/activity-tree.js +1 -1
  27. claude_mpm/dashboard/static/dist/components/code-tree.js +2593 -2
  28. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  29. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  30. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  31. claude_mpm/dashboard/static/js/components/activity-tree.js +212 -13
  32. claude_mpm/dashboard/static/js/components/build-tracker.js +15 -13
  33. claude_mpm/dashboard/static/js/components/code-tree.js +2503 -917
  34. claude_mpm/dashboard/static/js/components/event-viewer.js +58 -19
  35. claude_mpm/dashboard/static/js/dashboard.js +46 -44
  36. claude_mpm/dashboard/static/js/socket-client.js +74 -32
  37. claude_mpm/dashboard/templates/index.html +25 -20
  38. claude_mpm/services/agents/deployment/agent_template_builder.py +11 -7
  39. claude_mpm/services/agents/memory/memory_format_service.py +3 -1
  40. claude_mpm/services/cli/agent_cleanup_service.py +1 -4
  41. claude_mpm/services/cli/socketio_manager.py +39 -8
  42. claude_mpm/services/cli/startup_checker.py +0 -1
  43. claude_mpm/services/core/cache_manager.py +0 -1
  44. claude_mpm/services/infrastructure/monitoring.py +1 -1
  45. claude_mpm/services/socketio/event_normalizer.py +64 -0
  46. claude_mpm/services/socketio/handlers/code_analysis.py +449 -0
  47. claude_mpm/services/socketio/server/connection_manager.py +3 -1
  48. claude_mpm/tools/code_tree_analyzer.py +930 -24
  49. claude_mpm/tools/code_tree_builder.py +0 -1
  50. claude_mpm/tools/code_tree_events.py +113 -15
  51. {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.12.dist-info}/METADATA +2 -1
  52. {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.12.dist-info}/RECORD +56 -48
  53. {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.12.dist-info}/WHEEL +0 -0
  54. {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.12.dist-info}/entry_points.txt +0 -0
  55. {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.12.dist-info}/licenses/LICENSE +0 -0
  56. {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", {"todos": todos, "source": "PM", "intercepted": True}
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(todos) if todos else 0,
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(todos) if todos else 0} todos"
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()