devsquad 3.6.0__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 (95) hide show
  1. devsquad-3.6.0.dist-info/METADATA +944 -0
  2. devsquad-3.6.0.dist-info/RECORD +95 -0
  3. devsquad-3.6.0.dist-info/WHEEL +5 -0
  4. devsquad-3.6.0.dist-info/entry_points.txt +2 -0
  5. devsquad-3.6.0.dist-info/licenses/LICENSE +21 -0
  6. devsquad-3.6.0.dist-info/top_level.txt +2 -0
  7. scripts/__init__.py +0 -0
  8. scripts/ai_semantic_matcher.py +512 -0
  9. scripts/alert_manager.py +505 -0
  10. scripts/api/__init__.py +43 -0
  11. scripts/api/models.py +386 -0
  12. scripts/api/routes/__init__.py +20 -0
  13. scripts/api/routes/dispatch.py +348 -0
  14. scripts/api/routes/lifecycle.py +330 -0
  15. scripts/api/routes/metrics_gates.py +347 -0
  16. scripts/api_server.py +318 -0
  17. scripts/auth.py +451 -0
  18. scripts/cli/__init__.py +1 -0
  19. scripts/cli/cli_visual.py +642 -0
  20. scripts/cli.py +1094 -0
  21. scripts/collaboration/__init__.py +212 -0
  22. scripts/collaboration/_version.py +1 -0
  23. scripts/collaboration/agent_briefing.py +656 -0
  24. scripts/collaboration/ai_semantic_matcher.py +260 -0
  25. scripts/collaboration/anchor_checker.py +281 -0
  26. scripts/collaboration/anti_rationalization.py +470 -0
  27. scripts/collaboration/async_integration_example.py +255 -0
  28. scripts/collaboration/batch_scheduler.py +149 -0
  29. scripts/collaboration/checkpoint_manager.py +561 -0
  30. scripts/collaboration/ci_feedback_adapter.py +351 -0
  31. scripts/collaboration/code_map_generator.py +247 -0
  32. scripts/collaboration/concern_pack_loader.py +352 -0
  33. scripts/collaboration/confidence_score.py +496 -0
  34. scripts/collaboration/config_loader.py +188 -0
  35. scripts/collaboration/consensus.py +244 -0
  36. scripts/collaboration/context_compressor.py +533 -0
  37. scripts/collaboration/coordinator.py +668 -0
  38. scripts/collaboration/dispatcher.py +1636 -0
  39. scripts/collaboration/dual_layer_context.py +128 -0
  40. scripts/collaboration/enhanced_worker.py +539 -0
  41. scripts/collaboration/feature_usage_tracker.py +206 -0
  42. scripts/collaboration/five_axis_consensus.py +334 -0
  43. scripts/collaboration/input_validator.py +401 -0
  44. scripts/collaboration/integration_example.py +287 -0
  45. scripts/collaboration/intent_workflow_mapper.py +350 -0
  46. scripts/collaboration/language_parsers.py +269 -0
  47. scripts/collaboration/lifecycle_protocol.py +1446 -0
  48. scripts/collaboration/llm_backend.py +453 -0
  49. scripts/collaboration/llm_cache.py +448 -0
  50. scripts/collaboration/llm_cache_async.py +347 -0
  51. scripts/collaboration/llm_retry.py +387 -0
  52. scripts/collaboration/llm_retry_async.py +389 -0
  53. scripts/collaboration/mce_adapter.py +597 -0
  54. scripts/collaboration/memory_bridge.py +1607 -0
  55. scripts/collaboration/models.py +537 -0
  56. scripts/collaboration/null_providers.py +297 -0
  57. scripts/collaboration/operation_classifier.py +289 -0
  58. scripts/collaboration/output_slicer.py +225 -0
  59. scripts/collaboration/performance_monitor.py +462 -0
  60. scripts/collaboration/permission_guard.py +865 -0
  61. scripts/collaboration/prompt_assembler.py +756 -0
  62. scripts/collaboration/prompt_variant_generator.py +483 -0
  63. scripts/collaboration/protocols.py +267 -0
  64. scripts/collaboration/report_formatter.py +352 -0
  65. scripts/collaboration/retrospective.py +279 -0
  66. scripts/collaboration/role_matcher.py +92 -0
  67. scripts/collaboration/role_template_market.py +352 -0
  68. scripts/collaboration/rule_collector.py +678 -0
  69. scripts/collaboration/scratchpad.py +346 -0
  70. scripts/collaboration/skill_registry.py +151 -0
  71. scripts/collaboration/skillifier.py +878 -0
  72. scripts/collaboration/standardized_role_template.py +317 -0
  73. scripts/collaboration/task_completion_checker.py +237 -0
  74. scripts/collaboration/test_quality_guard.py +695 -0
  75. scripts/collaboration/unified_gate_engine.py +598 -0
  76. scripts/collaboration/usage_tracker.py +309 -0
  77. scripts/collaboration/user_friendly_error.py +176 -0
  78. scripts/collaboration/verification_gate.py +312 -0
  79. scripts/collaboration/warmup_manager.py +635 -0
  80. scripts/collaboration/worker.py +513 -0
  81. scripts/collaboration/workflow_engine.py +684 -0
  82. scripts/dashboard.py +1088 -0
  83. scripts/generate_benchmark_report.py +786 -0
  84. scripts/history_manager.py +604 -0
  85. scripts/mcp_server.py +289 -0
  86. skills/__init__.py +32 -0
  87. skills/dispatch/handler.py +52 -0
  88. skills/intent/handler.py +59 -0
  89. skills/registry.py +67 -0
  90. skills/retrospective/__init__.py +0 -0
  91. skills/retrospective/handler.py +125 -0
  92. skills/review/handler.py +356 -0
  93. skills/security/handler.py +454 -0
  94. skills/test/__init__.py +0 -0
  95. skills/test/handler.py +78 -0
@@ -0,0 +1,656 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Agent Briefing System
5
+
6
+ Provides intelligent briefing generation for agents to understand:
7
+ - Project context and goals
8
+ - Current task requirements
9
+ - Historical decisions and patterns
10
+ - Team collaboration context
11
+
12
+ Usage:
13
+ from scripts.collaboration.agent_briefing import AgentBriefing
14
+
15
+ briefing = AgentBriefing(
16
+ agent_role="Architect",
17
+ project_context={"name": "DevSquad", "version": "3.6.0-Prod"}
18
+ )
19
+
20
+ # Generate briefing
21
+ content = briefing.generate_briefing(
22
+ task="Design Protocol interface system",
23
+ context={"priority": "high"}
24
+ )
25
+
26
+ # Generate project overview
27
+ overview = briefing.generate_project_overview(".")
28
+
29
+ # Generate role-specific understanding
30
+ understanding = briefing.generate_role_understanding("security", ".")
31
+
32
+ # Update with new information
33
+ briefing.update_briefing(
34
+ key="decisions",
35
+ value="Use Python Protocol for interface definition"
36
+ )
37
+ """
38
+
39
+ import json
40
+ import logging
41
+ import re
42
+ from typing import Dict, Any, List, Optional
43
+ from dataclasses import dataclass, field, asdict
44
+ from datetime import datetime
45
+ from pathlib import Path
46
+
47
+
48
+ logger = logging.getLogger(__name__)
49
+
50
+ _SAFE_FILENAME_RE = re.compile(r'[^\w\-.]')
51
+
52
+
53
+ @dataclass
54
+ class BriefingSection:
55
+ """Briefing section data structure"""
56
+ title: str
57
+ content: str
58
+ priority: int = 1 # 1=high, 2=medium, 3=low
59
+ timestamp: float = field(default_factory=lambda: datetime.now().timestamp())
60
+ metadata: Dict[str, Any] = field(default_factory=dict)
61
+
62
+ def to_dict(self) -> Dict[str, Any]:
63
+ """Convert to dictionary"""
64
+ return asdict(self)
65
+
66
+
67
+ @dataclass
68
+ class AgentContext:
69
+ """Agent context information"""
70
+ role: str
71
+ capabilities: List[str] = field(default_factory=list)
72
+ constraints: List[str] = field(default_factory=list)
73
+ preferences: Dict[str, Any] = field(default_factory=dict)
74
+ history: List[Dict[str, Any]] = field(default_factory=list)
75
+
76
+ def to_dict(self) -> Dict[str, Any]:
77
+ """Convert to dictionary"""
78
+ return asdict(self)
79
+
80
+
81
+ class AgentBriefing:
82
+ """
83
+ Agent Briefing Generator
84
+
85
+ Features:
86
+ - Context-aware briefing generation
87
+ - Historical pattern recognition
88
+ - Priority-based information filtering
89
+ - Incremental updates
90
+ - Persistence support
91
+ - Project overview generation (V3.6.0)
92
+ - Role-specific understanding generation (V3.6.0)
93
+ """
94
+
95
+ def __init__(
96
+ self,
97
+ agent_role: str,
98
+ project_context: Optional[Dict[str, Any]] = None,
99
+ storage_dir: Optional[str] = None
100
+ ):
101
+ self.agent_role = agent_role
102
+ self.project_context = project_context or {}
103
+ self.storage_dir = Path(storage_dir or "data/briefings")
104
+ self.storage_dir.mkdir(parents=True, exist_ok=True)
105
+
106
+ self.sections: Dict[str, BriefingSection] = {}
107
+ self.agent_context = AgentContext(role=agent_role)
108
+
109
+ self._load_briefing()
110
+
111
+ def generate_briefing(
112
+ self,
113
+ task: str,
114
+ context: Optional[Dict[str, Any]] = None,
115
+ max_length: Optional[int] = None
116
+ ) -> str:
117
+ context = context or {}
118
+
119
+ briefing_parts = []
120
+
121
+ briefing_parts.append(self._generate_role_section())
122
+ briefing_parts.append(self._generate_project_section())
123
+ briefing_parts.append(self._generate_task_section(task, context))
124
+
125
+ if self.agent_context.history:
126
+ briefing_parts.append(self._generate_history_section())
127
+
128
+ sorted_sections = sorted(
129
+ self.sections.values(),
130
+ key=lambda s: (s.priority, -s.timestamp)
131
+ )
132
+ for section in sorted_sections:
133
+ briefing_parts.append(f"## {section.title}\n\n{section.content}")
134
+
135
+ full_briefing = "\n\n".join(briefing_parts)
136
+
137
+ if max_length and len(full_briefing) > max_length:
138
+ full_briefing = full_briefing[:max_length] + "\n\n[Briefing truncated...]"
139
+
140
+ self._add_to_history(task, context, full_briefing)
141
+
142
+ return full_briefing
143
+
144
+ def generate_project_overview(self, project_root: str = ".") -> str:
145
+ """
146
+ Generate project overview document.
147
+
148
+ Analyzes project structure to produce a module-level understanding
149
+ document covering tech stack, module structure, core components,
150
+ and dependency relationships.
151
+
152
+ Args:
153
+ project_root: Root directory of the project to analyze
154
+
155
+ Returns:
156
+ Markdown-formatted project overview document
157
+ """
158
+ root = Path(project_root)
159
+
160
+ tech_stack = self._analyze_tech_stack(root)
161
+ modules = self._identify_modules(root)
162
+ core_components = self._identify_core_components(root)
163
+
164
+ lines = ["# Project Overview", ""]
165
+
166
+ lines.append("## Tech Stack")
167
+ for category, items in tech_stack.items():
168
+ lines.append(f"- **{category}**: {', '.join(items)}")
169
+ lines.append("")
170
+
171
+ lines.append("## Module Structure")
172
+ for mod in modules:
173
+ indent = " " * mod.get("depth", 0)
174
+ lines.append(f"{indent}- {mod['icon']} **{mod['name']}** — {mod['description']}")
175
+ lines.append("")
176
+
177
+ lines.append("## Core Components")
178
+ for comp in core_components:
179
+ lines.append(f"- **{comp['name']}** ({comp['file']}): {comp['description']}")
180
+ lines.append("")
181
+
182
+ return "\n".join(lines)
183
+
184
+ def generate_role_understanding(
185
+ self, role: str, project_root: str = "."
186
+ ) -> str:
187
+ """
188
+ Generate role-specific project understanding document.
189
+
190
+ Produces a tailored understanding document for a specific role,
191
+ highlighting aspects most relevant to that role's responsibilities.
192
+
193
+ Args:
194
+ role: Role identifier (e.g., "architect", "security", "tester")
195
+ project_root: Root directory of the project to analyze
196
+
197
+ Returns:
198
+ Markdown-formatted role-specific understanding document
199
+ """
200
+ root = Path(project_root)
201
+ tech_stack = self._analyze_tech_stack(root)
202
+ modules = self._identify_modules(root)
203
+
204
+ role_focus = {
205
+ "architect": {
206
+ "title": "Architecture Understanding",
207
+ "focus_areas": ["Module Structure", "Dependency Graph", "Design Patterns"],
208
+ "key_questions": [
209
+ "Is the module decomposition following high cohesion / low coupling?",
210
+ "Are there circular dependencies?",
211
+ "Is the abstraction level appropriate?",
212
+ ],
213
+ },
214
+ "security": {
215
+ "title": "Security Understanding",
216
+ "focus_areas": ["Authentication", "Authorization", "Data Protection", "Input Validation"],
217
+ "key_questions": [
218
+ "Where are the authentication and authorization checks?",
219
+ "How is sensitive data protected at rest and in transit?",
220
+ "Are all external inputs validated?",
221
+ ],
222
+ },
223
+ "tester": {
224
+ "title": "Testing Understanding",
225
+ "focus_areas": ["Test Coverage", "Test Strategy", "Critical Paths"],
226
+ "key_questions": [
227
+ "What is the current test coverage?",
228
+ "Are boundary conditions and error paths tested?",
229
+ "What are the most critical code paths to test?",
230
+ ],
231
+ },
232
+ "solo-coder": {
233
+ "title": "Development Understanding",
234
+ "focus_areas": ["Code Conventions", "Hot Spots", "Technical Debt"],
235
+ "key_questions": [
236
+ "What are the coding conventions used?",
237
+ "Where are the most complex functions?",
238
+ "What technical debt exists?",
239
+ ],
240
+ },
241
+ "devops": {
242
+ "title": "Operations Understanding",
243
+ "focus_areas": ["Deployment", "Monitoring", "Configuration", "Logging"],
244
+ "key_questions": [
245
+ "How is the application deployed?",
246
+ "What metrics are monitored?",
247
+ "How is configuration managed across environments?",
248
+ ],
249
+ },
250
+ "product-manager": {
251
+ "title": "Product Understanding",
252
+ "focus_areas": ["Features", "User Flows", "Acceptance Criteria"],
253
+ "key_questions": [
254
+ "What features are currently implemented?",
255
+ "What are the core user flows?",
256
+ "Are all acceptance criteria met?",
257
+ ],
258
+ },
259
+ "ui-designer": {
260
+ "title": "UI/UX Understanding",
261
+ "focus_areas": ["Component Library", "Interaction Patterns", "Accessibility"],
262
+ "key_questions": [
263
+ "What UI components are available?",
264
+ "Are interaction patterns consistent?",
265
+ "Is accessibility supported?",
266
+ ],
267
+ },
268
+ }
269
+
270
+ focus = role_focus.get(role, role_focus.get("solo-coder", {}))
271
+
272
+ lines = [f"# {focus.get('title', 'Project Understanding')}", ""]
273
+ lines.append(f"**Role**: {role}")
274
+ lines.append("")
275
+
276
+ lines.append("## Focus Areas")
277
+ for area in focus.get("focus_areas", []):
278
+ lines.append(f"- {area}")
279
+ lines.append("")
280
+
281
+ lines.append("## Key Questions to Answer")
282
+ for q in focus.get("key_questions", []):
283
+ lines.append(f"- [ ] {q}")
284
+ lines.append("")
285
+
286
+ lines.append("## Tech Stack Summary")
287
+ for category, items in tech_stack.items():
288
+ lines.append(f"- **{category}**: {', '.join(items)}")
289
+ lines.append("")
290
+
291
+ lines.append("## Relevant Modules")
292
+ for mod in modules:
293
+ indent = " " * mod.get("depth", 0)
294
+ lines.append(f"{indent}- {mod['icon']} **{mod['name']}** — {mod['description']}")
295
+ lines.append("")
296
+
297
+ return "\n".join(lines)
298
+
299
+ def _analyze_tech_stack(self, root: Path) -> Dict[str, List[str]]:
300
+ """Analyze project tech stack from config files."""
301
+ stack: Dict[str, List[str]] = {}
302
+
303
+ pyproject = root / "pyproject.toml"
304
+ if pyproject.exists():
305
+ stack["Language"] = ["Python"]
306
+ try:
307
+ content = pyproject.read_text(encoding="utf-8")
308
+ if "fastapi" in content.lower():
309
+ stack.setdefault("Web Framework", []).append("FastAPI")
310
+ if "streamlit" in content.lower():
311
+ stack.setdefault("Web Framework", []).append("Streamlit")
312
+ if "openai" in content.lower():
313
+ stack.setdefault("AI Backend", []).append("OpenAI")
314
+ if "anthropic" in content.lower():
315
+ stack.setdefault("AI Backend", []).append("Anthropic")
316
+ if "sqlite" in content.lower():
317
+ stack.setdefault("Database", []).append("SQLite")
318
+ if "pyyaml" in content.lower() or "yaml" in content.lower():
319
+ stack.setdefault("Config", []).append("YAML")
320
+ except Exception:
321
+ pass
322
+
323
+ dockerfile = root / "Dockerfile"
324
+ if dockerfile.exists():
325
+ stack.setdefault("Deployment", []).append("Docker")
326
+
327
+ github_dir = root / ".github"
328
+ if github_dir.exists():
329
+ stack.setdefault("CI/CD", []).append("GitHub Actions")
330
+
331
+ if not stack:
332
+ stack["Language"] = ["Unknown"]
333
+
334
+ return stack
335
+
336
+ def _identify_modules(self, root: Path) -> List[Dict[str, Any]]:
337
+ """Identify project modules and their descriptions."""
338
+ modules: List[Dict[str, Any]] = []
339
+
340
+ known_dirs = {
341
+ "scripts/collaboration": {"icon": "🔧", "description": "Core collaboration engine"},
342
+ "scripts/cli": {"icon": "💻", "description": "CLI interface"},
343
+ "scripts/dashboard": {"icon": "📊", "description": "Web dashboard"},
344
+ "scripts/api": {"icon": "🌐", "description": "REST API server"},
345
+ "templates/concerns": {"icon": "📦", "description": "Concern packs"},
346
+ "docs": {"icon": "📄", "description": "Documentation"},
347
+ "tests": {"icon": "🧪", "description": "Test suite"},
348
+ "benchmarks": {"icon": "⚡", "description": "Performance benchmarks"},
349
+ }
350
+
351
+ for dir_path, info in known_dirs.items():
352
+ full_path = root / dir_path
353
+ if full_path.exists() and full_path.is_dir():
354
+ py_files = list(full_path.rglob("*.py"))
355
+ modules.append({
356
+ "name": dir_path,
357
+ "icon": info["icon"],
358
+ "description": f"{info['description']} ({len(py_files)} files)",
359
+ "depth": dir_path.count("/"),
360
+ })
361
+
362
+ return modules
363
+
364
+ def _identify_core_components(self, root: Path) -> List[Dict[str, str]]:
365
+ """Identify core components from key source files."""
366
+ components: List[Dict[str, str]] = []
367
+
368
+ known_components = [
369
+ ("dispatcher.py", "MultiAgentDispatcher", "Unified dispatch entry point"),
370
+ ("coordinator.py", "Coordinator", "Global orchestrator for multi-agent collaboration"),
371
+ ("worker.py", "Worker", "Role execution unit"),
372
+ ("scratchpad.py", "Scratchpad", "Shared blackboard for inter-agent communication"),
373
+ ("consensus.py", "ConsensusEngine", "Weighted voting consensus mechanism"),
374
+ ("five_axis_consensus.py", "FiveAxisConsensusEngine", "Five-axis code review consensus"),
375
+ ("llm_backend.py", "LLMBackend", "Multi-backend LLM abstraction layer"),
376
+ ("permission_guard.py", "PermissionGuard", "4-level permission control system"),
377
+ ("input_validator.py", "InputValidator", "Input validation and injection detection"),
378
+ ("memory_bridge.py", "MemoryBridge", "Cross-session memory management"),
379
+ ("workflow_engine.py", "WorkflowEngine", "Task-to-workflow orchestration"),
380
+ ("context_compressor.py", "ContextCompressor", "4-level context compression"),
381
+ ("anti_rationalization.py", "AntiRationalizationEngine", "Anti-rationalization pattern injection"),
382
+ ("verification_gate.py", "VerificationGate", "Evidence-based completion verification"),
383
+ ]
384
+
385
+ collab_dir = root / "scripts" / "collaboration"
386
+ for filename, class_name, description in known_components:
387
+ if (collab_dir / filename).exists():
388
+ components.append({
389
+ "name": class_name,
390
+ "file": f"scripts/collaboration/{filename}",
391
+ "description": description,
392
+ })
393
+
394
+ return components
395
+
396
+ def _generate_role_section(self) -> str:
397
+ """Generate agent role section"""
398
+ content = f"# Agent Briefing: {self.agent_role}\n\n"
399
+ content += f"**Role**: {self.agent_role}\n\n"
400
+
401
+ if self.agent_context.capabilities:
402
+ content += "**Capabilities**:\n"
403
+ for cap in self.agent_context.capabilities:
404
+ content += f"- {cap}\n"
405
+ content += "\n"
406
+
407
+ if self.agent_context.constraints:
408
+ content += "**Constraints**:\n"
409
+ for constraint in self.agent_context.constraints:
410
+ content += f"- {constraint}\n"
411
+ content += "\n"
412
+
413
+ return content.strip()
414
+
415
+ def _generate_project_section(self) -> str:
416
+ """Generate project context section"""
417
+ if not self.project_context:
418
+ return ""
419
+
420
+ content = "## Project Context\n\n"
421
+
422
+ for key, value in self.project_context.items():
423
+ if isinstance(value, (list, dict)):
424
+ content += f"**{key.replace('_', ' ').title()}**:\n"
425
+ content += f"```json\n{json.dumps(value, indent=2)}\n```\n\n"
426
+ else:
427
+ content += f"**{key.replace('_', ' ').title()}**: {value}\n\n"
428
+
429
+ return content.strip()
430
+
431
+ def _generate_task_section(self, task: str, context: Dict[str, Any]) -> str:
432
+ """Generate current task section"""
433
+ content = "## Current Task\n\n"
434
+ content += f"{task}\n\n"
435
+
436
+ if context:
437
+ content += "**Task Context**:\n"
438
+ for key, value in context.items():
439
+ content += f"- **{key.replace('_', ' ').title()}**: {value}\n"
440
+ content += "\n"
441
+
442
+ return content.strip()
443
+
444
+ def _generate_history_section(self, limit: int = 5) -> str:
445
+ """Generate historical context section"""
446
+ content = "## Recent History\n\n"
447
+
448
+ recent_history = self.agent_context.history[-limit:]
449
+ for i, entry in enumerate(reversed(recent_history), 1):
450
+ task = entry.get("task", "Unknown task")
451
+ timestamp = entry.get("timestamp", 0)
452
+ dt = datetime.fromtimestamp(timestamp)
453
+
454
+ content += f"{i}. **{task}** ({dt.strftime('%Y-%m-%d %H:%M')})\n"
455
+
456
+ if "outcome" in entry:
457
+ content += f" - Outcome: {entry['outcome']}\n"
458
+
459
+ if "key_decisions" in entry:
460
+ content += f" - Key decisions: {', '.join(entry['key_decisions'])}\n"
461
+
462
+ content += "\n"
463
+
464
+ return content.strip()
465
+
466
+ def update_briefing(
467
+ self,
468
+ key: str,
469
+ value: Any,
470
+ section: Optional[str] = None,
471
+ priority: int = 2
472
+ ) -> None:
473
+ if section:
474
+ if section not in self.sections:
475
+ self.sections[section] = BriefingSection(
476
+ title=section,
477
+ content="",
478
+ priority=priority
479
+ )
480
+
481
+ if isinstance(value, str):
482
+ self.sections[section].content += f"\n- **{key}**: {value}"
483
+ else:
484
+ self.sections[section].content += f"\n- **{key}**: {json.dumps(value)}"
485
+
486
+ self.sections[section].timestamp = datetime.now().timestamp()
487
+ else:
488
+ if key == "capabilities":
489
+ if isinstance(value, list):
490
+ self.agent_context.capabilities.extend(value)
491
+ else:
492
+ self.agent_context.capabilities.append(value)
493
+ elif key == "constraints":
494
+ if isinstance(value, list):
495
+ self.agent_context.constraints.extend(value)
496
+ else:
497
+ self.agent_context.constraints.append(value)
498
+ elif key == "preferences":
499
+ if isinstance(value, dict):
500
+ self.agent_context.preferences.update(value)
501
+ else:
502
+ self.agent_context.preferences[key] = value
503
+
504
+ self._save_briefing()
505
+
506
+ def add_section(
507
+ self,
508
+ title: str,
509
+ content: str,
510
+ priority: int = 2,
511
+ metadata: Optional[Dict[str, Any]] = None
512
+ ) -> None:
513
+ self.sections[title] = BriefingSection(
514
+ title=title,
515
+ content=content,
516
+ priority=priority,
517
+ metadata=metadata or {}
518
+ )
519
+
520
+ self._save_briefing()
521
+
522
+ def remove_section(self, title: str) -> bool:
523
+ if title in self.sections:
524
+ del self.sections[title]
525
+ self._save_briefing()
526
+ return True
527
+ return False
528
+
529
+ def get_section(self, title: str) -> Optional[BriefingSection]:
530
+ return self.sections.get(title)
531
+
532
+ def list_sections(self) -> List[str]:
533
+ return list(self.sections.keys())
534
+
535
+ def clear_history(self) -> None:
536
+ self.agent_context.history.clear()
537
+ self._save_briefing()
538
+
539
+ def export_briefing(self, output_path: str) -> None:
540
+ briefing_data = {
541
+ "agent_role": self.agent_role,
542
+ "project_context": self.project_context,
543
+ "agent_context": self.agent_context.to_dict(),
544
+ "sections": {
545
+ title: section.to_dict()
546
+ for title, section in self.sections.items()
547
+ },
548
+ "exported_at": datetime.now().isoformat()
549
+ }
550
+
551
+ Path(output_path).write_text(
552
+ json.dumps(briefing_data, indent=2),
553
+ encoding='utf-8'
554
+ )
555
+
556
+ logger.info(f"Briefing exported to {output_path}")
557
+
558
+ def _add_to_history(
559
+ self,
560
+ task: str,
561
+ context: Dict[str, Any],
562
+ briefing: str
563
+ ) -> None:
564
+ history_entry = {
565
+ "task": task,
566
+ "context": context,
567
+ "briefing_length": len(briefing),
568
+ "timestamp": datetime.now().timestamp()
569
+ }
570
+
571
+ self.agent_context.history.append(history_entry)
572
+
573
+ if len(self.agent_context.history) > 100:
574
+ self.agent_context.history = self.agent_context.history[-100:]
575
+
576
+ self._save_briefing()
577
+
578
+ def _save_briefing(self) -> None:
579
+ try:
580
+ safe_role = _SAFE_FILENAME_RE.sub('_', self.agent_role.lower())
581
+ briefing_file = self.storage_dir / f"{safe_role}_briefing.json"
582
+
583
+ briefing_data = {
584
+ "agent_role": self.agent_role,
585
+ "project_context": self.project_context,
586
+ "agent_context": self.agent_context.to_dict(),
587
+ "sections": {
588
+ title: section.to_dict()
589
+ for title, section in self.sections.items()
590
+ },
591
+ "updated_at": datetime.now().isoformat()
592
+ }
593
+
594
+ briefing_file.write_text(
595
+ json.dumps(briefing_data, indent=2),
596
+ encoding='utf-8'
597
+ )
598
+ except Exception as e:
599
+ logger.warning(f"Failed to save briefing: {e}")
600
+
601
+ def _load_briefing(self) -> None:
602
+ try:
603
+ safe_role = _SAFE_FILENAME_RE.sub('_', self.agent_role.lower())
604
+ briefing_file = self.storage_dir / f"{safe_role}_briefing.json"
605
+
606
+ if not briefing_file.exists():
607
+ return
608
+
609
+ briefing_data = json.loads(briefing_file.read_text(encoding='utf-8'))
610
+
611
+ self.project_context = briefing_data.get("project_context", {})
612
+
613
+ agent_context_data = briefing_data.get("agent_context", {})
614
+ self.agent_context = AgentContext(**agent_context_data)
615
+
616
+ sections_data = briefing_data.get("sections", {})
617
+ for title, section_data in sections_data.items():
618
+ self.sections[title] = BriefingSection(**section_data)
619
+
620
+ logger.info(f"Loaded briefing for {self.agent_role}")
621
+ except Exception as e:
622
+ logger.warning(f"Failed to load briefing: {e}")
623
+
624
+
625
+ _briefing_instances: Dict[str, AgentBriefing] = {}
626
+
627
+
628
+ def get_agent_briefing(
629
+ agent_role: str,
630
+ project_context: Optional[Dict[str, Any]] = None,
631
+ storage_dir: Optional[str] = None
632
+ ) -> AgentBriefing:
633
+ if agent_role not in _briefing_instances:
634
+ _briefing_instances[agent_role] = AgentBriefing(
635
+ agent_role=agent_role,
636
+ project_context=project_context,
637
+ storage_dir=storage_dir
638
+ )
639
+
640
+ return _briefing_instances[agent_role]
641
+
642
+
643
+ def reset_briefings() -> None:
644
+ """Reset all briefing instances (for testing)"""
645
+ global _briefing_instances
646
+ _briefing_instances.clear()
647
+
648
+
649
+ __version__ = "3.6.0"
650
+ __all__ = [
651
+ "AgentBriefing",
652
+ "BriefingSection",
653
+ "AgentContext",
654
+ "get_agent_briefing",
655
+ "reset_briefings",
656
+ ]