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,1446 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Unified Lifecycle Architecture (Plan C Implementation)
5
+
6
+ Core abstractions:
7
+ - LifecycleMode: SHORTCUT / FULL / CUSTOM enum
8
+ - PhaseDefinition: Unified phase structure
9
+ - ViewMapping: CLI command → 11-phase mapping
10
+ - LifecycleProtocol: Abstract interface for lifecycle management
11
+
12
+ Spec reference: docs/spec/SPEC_Lifecycle_Unified_Architecture_C.md
13
+ """
14
+
15
+ import logging
16
+ from abc import ABC, abstractmethod
17
+ from dataclasses import dataclass, field
18
+ from datetime import datetime
19
+ from enum import Enum
20
+ from typing import Any, Dict, List, Optional
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class LifecycleMode(Enum):
26
+ """
27
+ Three lifecycle modes for different usage scenarios.
28
+
29
+ Modes:
30
+ - SHORTCUT: CLI 6-command simplified view (spec/plan/build/test/review/ship)
31
+ - FULL: Complete 11-phase project lifecycle (P1-P11)
32
+ - CUSTOM: User-defined workflow with selected phases
33
+ """
34
+ SHORTCUT = "shortcut"
35
+ FULL = "full"
36
+ CUSTOM = "custom"
37
+
38
+
39
+ @dataclass
40
+ class PhaseDefinition:
41
+ """Unified definition for a single lifecycle phase."""
42
+ phase_id: str
43
+ name: str
44
+ description: str
45
+ role_id: str
46
+ dependencies: List[str] = field(default_factory=list)
47
+ artifacts_in: str = ""
48
+ artifacts_out: str = ""
49
+ gate_condition: str = ""
50
+ reviewers: List[str] = field(default_factory=list)
51
+ optional: bool = False
52
+ order: int = 0
53
+
54
+ def to_dict(self) -> Dict[str, Any]:
55
+ return {
56
+ "phase_id": self.phase_id,
57
+ "name": self.name,
58
+ "description": self.description,
59
+ "role_id": self.role_id,
60
+ "dependencies": self.dependencies,
61
+ "artifacts_in": self.artifacts_in,
62
+ "artifacts_out": self.artifacts_out,
63
+ "gate_condition": self.gate_condition,
64
+ "reviewers": self.reviewers,
65
+ "optional": self.optional,
66
+ "order": self.order,
67
+ }
68
+
69
+
70
+ class PhaseState(Enum):
71
+ """Phase execution state."""
72
+ PENDING = "pending"
73
+ RUNNING = "running"
74
+ COMPLETED = "completed"
75
+ FAILED = "failed"
76
+ SKIPPED = "skipped"
77
+ BLOCKED = "blocked"
78
+
79
+
80
+ @dataclass
81
+ class GateResult:
82
+ """Result of gate check for a phase."""
83
+ passed: bool
84
+ verdict: str # APPROVE / CONDITIONAL / REJECT
85
+ red_flags: List[Dict[str, Any]] = field(default_factory=list)
86
+ missing_evidence: List[Dict[str, Any]] = field(default_factory=list)
87
+ gap_report: str = ""
88
+
89
+ def to_dict(self) -> Dict[str, Any]:
90
+ return {
91
+ "passed": self.passed,
92
+ "verdict": self.verdict,
93
+ "red_flags_count": len(self.red_flags),
94
+ "missing_evidence_count": len(self.missing_evidence),
95
+ "gap_report": self.gap_report[:200] if self.gap_report else "",
96
+ }
97
+
98
+
99
+ @dataclass
100
+ class PhaseResult:
101
+ """Result of advancing to a phase."""
102
+ success: bool
103
+ phase_id: str
104
+ previous_state: PhaseState
105
+ new_state: PhaseState
106
+ gate_result: Optional[GateResult] = None
107
+ error: str = ""
108
+
109
+
110
+ @dataclass
111
+ class LifecycleStatus:
112
+ """Overall lifecycle status."""
113
+ mode: LifecycleMode
114
+ current_phase: Optional[str]
115
+ completed_phases: List[str]
116
+ failed_phases: List[str]
117
+ blocked_phases: List[str]
118
+ progress_percent: float
119
+ can_advance: bool
120
+ next_phase: Optional[str]
121
+
122
+ def to_summary(self) -> str:
123
+ lines = [
124
+ f"Lifecycle Status ({self.mode.value.upper()} Mode)",
125
+ f"Current: {self.current_phase or 'Not started'}",
126
+ f"Progress: {self.progress_percent:.0f}%",
127
+ f"Completed: {len(self.completed_phases)} phases",
128
+ ]
129
+ if self.next_phase:
130
+ lines.append(f"Next: {self.next_phase}")
131
+ if not self.can_advance:
132
+ lines.append("⚠️ Blocked: Check gate conditions")
133
+ return "\n".join(lines)
134
+
135
+
136
+ @dataclass
137
+ class ViewMapping:
138
+ """Maps a CLI command to underlying 11-phase segments."""
139
+ command: str
140
+ phases: List[str] # Phase IDs this command covers
141
+ mode: LifecycleMode = LifecycleMode.SHORTCUT
142
+ description: str = ""
143
+ required_roles: List[str] = field(default_factory=list)
144
+ gate: str = ""
145
+ pre_dispatch_message: str = ""
146
+
147
+ def covers_phase(self, phase_id: str) -> bool:
148
+ return phase_id in self.phases
149
+
150
+
151
+ @dataclass
152
+ class SpecTemplate:
153
+ """Specification document template."""
154
+ template_id: str
155
+ name: str
156
+ phase_id: str
157
+ sections: List[Dict[str, Any]] = field(default_factory=list)
158
+ required_fields: List[str] = field(default_factory=list)
159
+ validation_rules: List[Dict[str, Any]] = field(default_factory=list)
160
+
161
+
162
+ SPEC_TEMPLATES: Dict[str, SpecTemplate] = {
163
+ "requirements": SpecTemplate(
164
+ template_id="requirements",
165
+ name="Requirements Specification",
166
+ phase_id="P1",
167
+ sections=[
168
+ {"title": "Objectives", "description": "Project goals and success criteria"},
169
+ {"title": "User Stories", "description": "User stories with acceptance criteria"},
170
+ {"title": "Non-Functional Requirements", "description": "Performance, security, scalability"},
171
+ {"title": "Constraints", "description": "Technical and business constraints"},
172
+ {"title": "Boundaries", "description": "In-scope and out-of-scope items"},
173
+ ],
174
+ required_fields=["objectives", "user_stories"],
175
+ validation_rules=[
176
+ {"field": "objectives", "check": "not_empty", "severity": "critical"},
177
+ {"field": "user_stories", "check": "min_count", "value": 1, "severity": "critical"},
178
+ ],
179
+ ),
180
+ "architecture": SpecTemplate(
181
+ template_id="architecture",
182
+ name="Architecture Specification",
183
+ phase_id="P2",
184
+ sections=[
185
+ {"title": "System Overview", "description": "High-level architecture diagram"},
186
+ {"title": "Tech Stack", "description": "Technology selection and rationale"},
187
+ {"title": "Service Boundaries", "description": "Module/service decomposition"},
188
+ {"title": "Quality Attributes", "description": "Performance, security, reliability targets"},
189
+ ],
190
+ required_fields=["system_overview", "tech_stack"],
191
+ validation_rules=[
192
+ {"field": "tech_stack", "check": "not_empty", "severity": "critical"},
193
+ ],
194
+ ),
195
+ "technical": SpecTemplate(
196
+ template_id="technical",
197
+ name="Technical Specification",
198
+ phase_id="P3",
199
+ sections=[
200
+ {"title": "API Specifications", "description": "Endpoint definitions"},
201
+ {"title": "Interface Definitions", "description": "Internal interfaces"},
202
+ {"title": "Data Models", "description": "Schema definitions"},
203
+ {"title": "Error Handling", "description": "Error codes and recovery"},
204
+ ],
205
+ required_fields=["api_specifications"],
206
+ validation_rules=[
207
+ {"field": "api_specifications", "check": "not_empty", "severity": "critical"},
208
+ ],
209
+ ),
210
+ }
211
+
212
+
213
+ # Predefined view mappings (CLI → 11 phases)
214
+ VIEW_MAPPINGS: Dict[str, ViewMapping] = {
215
+ "spec": ViewMapping(
216
+ command="spec",
217
+ phases=["P1", "P2"],
218
+ description="Define requirements and architecture before implementation",
219
+ required_roles=["architect", "product-manager"],
220
+ gate="spec_first",
221
+ pre_dispatch_message=(
222
+ "📋 Generating specification (P1: Requirements + P2: Architecture). "
223
+ "Output will include objectives, structure, testing plan, and boundaries."
224
+ ),
225
+ ),
226
+ "plan": ViewMapping(
227
+ command="plan",
228
+ phases=["P7"],
229
+ description="Break down work into verifiable tasks and test plans",
230
+ required_roles=["architect", "product-manager"],
231
+ gate="task_breakdown_complete",
232
+ pre_dispatch_message=(
233
+ "📝 Decomposing into atomic tasks (P7: Test Planning). "
234
+ "Output includes test plan with acceptance criteria."
235
+ ),
236
+ ),
237
+ "build": ViewMapping(
238
+ command="build",
239
+ phases=["P8"],
240
+ description="Implement incrementally with TDD discipline",
241
+ required_roles=["architect", "solo-coder", "tester"],
242
+ gate="incremental_verification",
243
+ pre_dispatch_message=(
244
+ "🔨 Building in thin vertical slices (P8: Implementation). "
245
+ "Each slice: implement → test → verify. ~100 lines per slice max."
246
+ ),
247
+ ),
248
+ "test": ViewMapping(
249
+ command="test",
250
+ phases=["P9"],
251
+ description="Run tests with mandatory evidence requirements",
252
+ required_roles=["tester", "solo-coder"],
253
+ gate="evidence_required",
254
+ pre_dispatch_message=(
255
+ "🧪 Running tests with verification gate (P9: Test Execution). "
256
+ "Evidence required: test output, build status, diff summary."
257
+ ),
258
+ ),
259
+ "review": ViewMapping(
260
+ command="review",
261
+ phases=["P8_review_embedded", "P6_partial"],
262
+ description="Five-axis code review and security checks",
263
+ required_roles=["solo-coder", "security", "tester", "architect"],
264
+ gate="change_size_limit",
265
+ pre_dispatch_message=(
266
+ "🔍 Conducting multi-dimensional review. "
267
+ "Change size target: ~100 lines. Severity labels: Critical/Required/Nit."
268
+ ),
269
+ ),
270
+ "ship": ViewMapping(
271
+ command="ship",
272
+ phases=["P10"],
273
+ description="Pre-launch verification and deployment preparation",
274
+ required_roles=["devops", "security", "architect"],
275
+ gate="pre_launch_checklist",
276
+ pre_dispatch_message=(
277
+ "🚀 Running pre-launch checklist across 6 dimensions. "
278
+ "Rollback plan required."
279
+ ),
280
+ ),
281
+ "spec-init": ViewMapping(
282
+ command="spec-init",
283
+ phases=["P1"],
284
+ description="Initialize specification document from template",
285
+ required_roles=["architect", "product-manager"],
286
+ gate="spec_template_complete",
287
+ pre_dispatch_message=(
288
+ "📋 Initializing specification document from template. "
289
+ "Choose template: requirements / architecture / technical."
290
+ ),
291
+ ),
292
+ "spec-analyze": ViewMapping(
293
+ command="spec-analyze",
294
+ phases=["P1", "P2"],
295
+ description="Analyze existing codebase to generate specification draft",
296
+ required_roles=["architect"],
297
+ gate="code_analysis_complete",
298
+ pre_dispatch_message=(
299
+ "🔍 Analyzing codebase structure to generate specification draft. "
300
+ "Uses CodeMapGenerator for multi-language analysis."
301
+ ),
302
+ ),
303
+ "spec-validate": ViewMapping(
304
+ command="spec-validate",
305
+ phases=["P1", "P2", "P3"],
306
+ description="Validate specification completeness and consistency",
307
+ required_roles=["architect", "tester"],
308
+ gate="spec_validation_passed",
309
+ pre_dispatch_message=(
310
+ "✅ Validating specification against completeness rules. "
311
+ "Checks required fields, consistency, and coverage."
312
+ ),
313
+ ),
314
+ }
315
+
316
+
317
+ class LifecycleProtocol(ABC):
318
+ """
319
+ Abstract interface for unified lifecycle management.
320
+
321
+ This protocol decouples the view layer (CLI 6 commands) from the core engine
322
+ (WorkflowEngine with 11 phases), enabling both SHORTCUT and FULL modes.
323
+ """
324
+
325
+ @abstractmethod
326
+ def get_mode(self) -> LifecycleMode:
327
+ """Return current lifecycle mode."""
328
+ ...
329
+
330
+ @abstractmethod
331
+ def set_mode(self, mode: LifecycleMode) -> None:
332
+ """Switch lifecycle mode."""
333
+ ...
334
+
335
+ @abstractmethod
336
+ def get_all_phases(self) -> List[PhaseDefinition]:
337
+ """Return all available phases in current mode."""
338
+ ...
339
+
340
+ @abstractmethod
341
+ def get_active_phases(self) -> List[PhaseDefinition]:
342
+ """Return phases active for the current task/context."""
343
+ ...
344
+
345
+ @abstractmethod
346
+ def get_phase(self, phase_id: str) -> Optional[PhaseDefinition]:
347
+ """Return specific phase by ID, or None if not found."""
348
+ ...
349
+
350
+ @abstractmethod
351
+ def get_current_phase(self) -> Optional[PhaseDefinition]:
352
+ """Return current phase, or None if not started."""
353
+ ...
354
+
355
+ @abstractmethod
356
+ def advance_to_phase(self, phase_id: str) -> PhaseResult:
357
+ """Advance to specified phase, running gate checks."""
358
+ ...
359
+
360
+ @abstractmethod
361
+ def check_gate(self, phase_id: Optional[str] = None) -> GateResult:
362
+ """Check gate conditions for phase (default: current)."""
363
+ ...
364
+
365
+ @abstractmethod
366
+ def get_status(self) -> LifecycleStatus:
367
+ """Return overall lifecycle status."""
368
+ ...
369
+
370
+ @abstractmethod
371
+ def get_view_mapping(self, command: str) -> Optional[ViewMapping]:
372
+ """Get view mapping for a CLI command (SHORTCUT mode only)."""
373
+ ...
374
+
375
+ @abstractmethod
376
+ def resolve_command_to_phases(self, command: str) -> List[PhaseDefinition]:
377
+ """Resolve a CLI command to its underlying phase definitions."""
378
+ ...
379
+
380
+
381
+ class ShortcutLifecycleAdapter(LifecycleProtocol):
382
+ """
383
+ Adapter that implements LifecycleProtocol using CLI 6-command shortcuts.
384
+
385
+ This is the default implementation for SHORTCUT mode.
386
+ Maps CLI commands to 11-phase segments via VIEW_MAPPINGS.
387
+
388
+ Integration (Plan C):
389
+ - Uses UnifiedGateEngine for real gate checks
390
+ - Integrates with CheckpointManager for state persistence
391
+ - Supports lifecycle state save/restore
392
+ """
393
+
394
+ def __init__(self, use_unified_gate: bool = True):
395
+ self._mode = LifecycleMode.SHORTCUT
396
+ self._current_phase: Optional[str] = None
397
+ self._completed_phases: List[str] = []
398
+ self._phase_states: Dict[str, PhaseState] = {}
399
+ self._use_unified_gate = use_unified_gate
400
+ self._gate_engine = None
401
+ self._checkpoint_manager = None
402
+ self._task_id: Optional[str] = None
403
+
404
+ # Initialize unified gate engine if requested
405
+ if use_unified_gate:
406
+ try:
407
+ from scripts.collaboration.unified_gate_engine import (
408
+ UnifiedGateEngine,
409
+ GateType,
410
+ PhaseGateContext,
411
+ get_shared_gate_engine,
412
+ )
413
+ self._gate_engine = get_shared_gate_engine()
414
+ self._GateType = GateType
415
+ self._PhaseGateContext = PhaseGateContext
416
+ logger.debug("UnifiedGateEngine initialized for ShortcutLifecycleAdapter")
417
+ except ImportError as e:
418
+ logger.warning("UnifiedGateEngine not available: %s", e)
419
+ self._use_unified_gate = False
420
+
421
+ def set_task_id(self, task_id: str) -> None:
422
+ """Set task ID for checkpoint integration."""
423
+ self._task_id = task_id
424
+ if not self._checkpoint_manager:
425
+ try:
426
+ from scripts.collaboration.checkpoint_manager import CheckpointManager
427
+ self._checkpoint_manager = CheckpointManager()
428
+ logger.info("CheckpointManager initialized for task %s", task_id)
429
+ except ImportError as e:
430
+ logger.warning("CheckpointManager not available: %s", e)
431
+
432
+ def enable_checkpoint_integration(self, storage_path: str = "./checkpoints") -> bool:
433
+ """
434
+ Enable checkpoint manager integration for state persistence.
435
+
436
+ Args:
437
+ storage_path: Path for storing checkpoints and lifecycle state
438
+
439
+ Returns:
440
+ True if successfully enabled
441
+ """
442
+ try:
443
+ from scripts.collaboration.checkpoint_manager import CheckpointManager
444
+ self._checkpoint_manager = CheckpointManager(storage_path=storage_path)
445
+ logger.info(
446
+ "CheckpointManager enabled at %s",
447
+ storage_path,
448
+ )
449
+ return True
450
+ except Exception as e:
451
+ logger.warning("Failed to enable checkpoint integration: %s", e)
452
+ return False
453
+
454
+ def save_state(self) -> bool:
455
+ """
456
+ Save current lifecycle state to checkpoint manager.
457
+
458
+ Returns:
459
+ True if saved successfully
460
+ """
461
+ if self._checkpoint_manager and self._task_id:
462
+ try:
463
+ phase_states_str = {
464
+ pid: (state.value if hasattr(state, 'value') else str(state))
465
+ for pid, state in self._phase_states.items()
466
+ }
467
+ mode_str = self._mode.value if hasattr(self._mode, 'value') else str(self._mode)
468
+
469
+ return self._checkpoint_manager.save_lifecycle_state(
470
+ task_id=self._task_id,
471
+ current_phase=self._current_phase,
472
+ phase_states=phase_states_str,
473
+ completed_phases=self._completed_phases.copy(),
474
+ mode=mode_str,
475
+ )
476
+ except Exception as e:
477
+ logger.warning("Failed to save lifecycle state: %s", e)
478
+ return False
479
+ return False
480
+
481
+ def restore_state(self) -> bool:
482
+ """
483
+ Restore lifecycle state from checkpoint manager.
484
+
485
+ Returns:
486
+ True if restored successfully
487
+ """
488
+ if self._checkpoint_manager and self._task_id:
489
+ try:
490
+ state = self._checkpoint_manager.load_lifecycle_state(self._task_id)
491
+ if state:
492
+ self._current_phase = state.get("current_phase")
493
+ self._completed_phases = state.get("completed_phases", [])
494
+
495
+ phase_states_raw = state.get("phase_states", {})
496
+ self._phase_states = {}
497
+ for pid, pstate in phase_states_raw.items():
498
+ try:
499
+ from scripts.collaboration.lifecycle_protocol import PhaseState
500
+ self._phase_states[pid] = PhaseState(pstate)
501
+ except ValueError:
502
+ self._phase_states[pid] = PhaseState.PENDING
503
+
504
+ mode_raw = state.get("mode", "shortcut")
505
+ try:
506
+ from scripts.collaboration.lifecycle_protocol import LifecycleMode
507
+ self._mode = LifecycleMode(mode_raw)
508
+ except ValueError:
509
+ pass
510
+
511
+ logger.info(
512
+ "Restored lifecycle state: task=%s, phase=%s",
513
+ self._task_id,
514
+ self._current_phase,
515
+ )
516
+ return True
517
+ except Exception as e:
518
+ logger.warning("Failed to restore lifecycle state: %s", e)
519
+ return False
520
+ return False
521
+
522
+ def get_mode(self) -> LifecycleMode:
523
+ return self._mode
524
+
525
+ def set_mode(self, mode: LifecycleMode) -> None:
526
+ self._mode = mode
527
+
528
+ def get_all_phases(self) -> List[PhaseDefinition]:
529
+ from scripts.collaboration.workflow_engine import PHASE_TEMPLATES
530
+
531
+ phases = []
532
+ for pid, ptmpl in PHASE_TEMPLATES.items():
533
+ phases.append(PhaseDefinition(
534
+ phase_id=pid,
535
+ name=ptmpl.get("name", pid),
536
+ description=ptmpl.get("description", ""),
537
+ role_id=ptmpl.get("role_id", ""),
538
+ dependencies=ptmpl.get("dependencies", []),
539
+ artifacts_in=ptmpl.get("artifacts_in", ""),
540
+ artifacts_out=ptmpl.get("artifacts_out", ""),
541
+ gate_condition=ptmpl.get("gate_condition", ""),
542
+ reviewers=ptmpl.get("reviewers", []),
543
+ optional=ptmpl.get("optional", False),
544
+ ))
545
+ return sorted(phases, key=lambda p: p.phase_id)
546
+
547
+ def get_active_phases(self) -> List[PhaseDefinition]:
548
+ if self._mode == LifecycleMode.SHORTCUT and self._current_phase:
549
+ mapping = self._get_current_mapping()
550
+ if mapping:
551
+ return [
552
+ p for p in self.get_all_phases()
553
+ if mapping.covers_phase(p.phase_id)
554
+ ]
555
+ return self.get_all_phases()
556
+
557
+ def get_phase(self, phase_id: str) -> Optional[PhaseDefinition]:
558
+ for p in self.get_all_phases():
559
+ if p.phase_id == phase_id:
560
+ return p
561
+ return None
562
+
563
+ def get_current_phase(self) -> Optional[PhaseDefinition]:
564
+ if self._current_phase:
565
+ return self.get_phase(self._current_phase)
566
+ return None
567
+
568
+ def advance_to_phase(self, phase_id: str) -> PhaseResult:
569
+ prev_state = self._phase_states.get(phase_id, PhaseState.PENDING)
570
+
571
+ # First check if already completed (idempotent)
572
+ if prev_state == PhaseState.COMPLETED:
573
+ return PhaseResult(
574
+ success=True,
575
+ phase_id=phase_id,
576
+ previous_state=prev_state,
577
+ new_state=PhaseState.COMPLETED,
578
+ gate_result=GateResult(passed=True, verdict="APPROVE"),
579
+ )
580
+
581
+ # Check dependencies first (before gate)
582
+ phase_def = self.get_phase(phase_id)
583
+ unmet_deps = []
584
+ if phase_def:
585
+ unmet_deps = [d for d in phase_def.dependencies if d not in self._completed_phases]
586
+ if unmet_deps:
587
+ result = PhaseResult(
588
+ success=False,
589
+ phase_id=phase_id,
590
+ previous_state=prev_state,
591
+ new_state=PhaseState.BLOCKED,
592
+ gate_result=GateResult(
593
+ passed=False,
594
+ verdict="CONDITIONAL",
595
+ missing_evidence=[{"dependency": d} for d in unmet_deps],
596
+ gap_report=f"Unmet dependencies: {', '.join(unmet_deps)}",
597
+ ),
598
+ error=f"Unmet dependencies: {unmet_deps}",
599
+ )
600
+
601
+ # Auto-save state on block
602
+ self.save_state()
603
+ return result
604
+
605
+ # Run gate check using unified engine or fallback
606
+ gate_result = self.check_gate(phase_id)
607
+ if not gate_result.passed and gate_result.verdict == "REJECT":
608
+ self._phase_states[phase_id] = PhaseState.BLOCKED
609
+ result = PhaseResult(
610
+ success=False,
611
+ phase_id=phase_id,
612
+ previous_state=prev_state,
613
+ new_state=PhaseState.BLOCKED,
614
+ gate_result=gate_result,
615
+ error=f"Gate rejected: {gate_result.gap_report}",
616
+ )
617
+
618
+ # Auto-save state on rejection
619
+ self.save_state()
620
+ return result
621
+
622
+ # Advance to RUNNING state
623
+ self._current_phase = phase_id
624
+ self._phase_states[phase_id] = PhaseState.RUNNING
625
+ logger.debug("Advanced to phase %s, _current_phase=%s", phase_id, self._current_phase)
626
+
627
+ # Auto-save state on successful advance
628
+ self.save_state()
629
+
630
+ return PhaseResult(
631
+ success=True,
632
+ phase_id=phase_id,
633
+ previous_state=prev_state,
634
+ new_state=PhaseState.RUNNING,
635
+ gate_result=gate_result,
636
+ )
637
+
638
+ def complete_phase(self, phase_id: str) -> None:
639
+ """Mark a phase as completed."""
640
+ self._phase_states[phase_id] = PhaseState.COMPLETED
641
+ if phase_id not in self._completed_phases:
642
+ self._completed_phases.append(phase_id)
643
+
644
+ # Auto-save state on completion
645
+ self.save_state()
646
+
647
+ def check_gate(self, phase_id: Optional[str] = None) -> GateResult:
648
+ target = phase_id or self._current_phase
649
+ if not target:
650
+ return GateResult(passed=True, verdict="APPROVE")
651
+
652
+ phase_def = self.get_phase(target)
653
+ if not phase_def:
654
+ return GateResult(passed=False, verdict="REJECT", gap_report=f"Phase {target} not found")
655
+
656
+ # Use UnifiedGateEngine if available
657
+ if self._use_unified_gate and self._gate_engine:
658
+ try:
659
+ return self._check_gate_with_unified_engine(target, phase_def)
660
+ except Exception as e:
661
+ logger.warning("UnifiedGateEngine failed, falling back: %s", e)
662
+
663
+ # Fallback to basic gate checks
664
+ return self._check_gate_basic(target, phase_def)
665
+
666
+ def _check_gate_with_unified_engine(
667
+ self,
668
+ target: str,
669
+ phase_def: PhaseDefinition,
670
+ ) -> GateResult:
671
+ """Check gate using UnifiedGateEngine."""
672
+ # Build phase context
673
+ unmet_deps = [d for d in phase_def.dependencies if d not in self._completed_phases]
674
+
675
+ context = self._PhaseGateContext(
676
+ phase_id=target,
677
+ phase_name=phase_def.name,
678
+ current_state=self._phase_states.get(target, PhaseState.PENDING).value,
679
+ target_state="running",
680
+ dependencies_met=len(unmet_deps) == 0,
681
+ completed_phases=self._completed_phases.copy(),
682
+ unmet_dependencies=unmet_deps,
683
+ )
684
+
685
+ # Run unified gate check
686
+ unified_result = self._gate_engine.check(
687
+ gate_type=self._GateType.PHASE_TRANSITION,
688
+ context=context,
689
+ )
690
+
691
+ # Convert UnifiedGateResult to GateResult
692
+ return GateResult(
693
+ passed=unified_result.passed,
694
+ verdict=unified_result.verdict,
695
+ red_flags=[
696
+ {"id": issue.get("code", "unknown"), "severity": "critical", **issue}
697
+ for issue in unified_result.critical_issues
698
+ ],
699
+ missing_evidence=[
700
+ {"key": ev, "required": True}
701
+ for ev in unified_result.evidence_required
702
+ ],
703
+ gap_report="\n".join([
704
+ f"- [{issue.get('severity', 'unknown')}] {issue.get('message', '')}"
705
+ for issue in unified_result.critical_issues + unified_result.warnings
706
+ ]) if (unified_result.critical_issues or unified_result.warnings) else "",
707
+ )
708
+
709
+ def _check_gate_basic(
710
+ self,
711
+ target: str,
712
+ phase_def: PhaseDefinition,
713
+ ) -> GateResult:
714
+ """Basic fallback gate check without UnifiedGateEngine."""
715
+ state = self._phase_states.get(target, PhaseState.PENDING)
716
+ if state == PhaseState.BLOCKED:
717
+ return GateResult(passed=False, verdict="REJECT", gap_report="Phase is blocked")
718
+ if state == PhaseState.COMPLETED:
719
+ return GateResult(passed=True, verdict="APPROVE")
720
+
721
+ # Check dependencies
722
+ unmet_deps = [d for d in phase_def.dependencies if d not in self._completed_phases]
723
+ if unmet_deps:
724
+ return GateResult(
725
+ passed=False,
726
+ verdict="CONDITIONAL",
727
+ missing_evidence=[{"dependency": d} for d in unmet_deps],
728
+ gap_report=f"Unmet dependencies: {', '.join(unmet_deps)}",
729
+ )
730
+
731
+ return GateResult(passed=True, verdict="APPROVE")
732
+
733
+ def get_status(self) -> LifecycleStatus:
734
+ all_phases = self.get_all_phases()
735
+ total = len(all_phases)
736
+ completed = len(self._completed_phases)
737
+ progress = (completed / total * 100) if total > 0 else 0.0
738
+
739
+ next_phase = None
740
+ can_advance = True
741
+ for p in all_phases:
742
+ pid = p.phase_id
743
+ state = self._phase_states.get(pid, PhaseState.PENDING)
744
+ if state == PhaseState.PENDING and not next_phase:
745
+ next_phase = pid
746
+ if state == PhaseState.BLOCKED:
747
+ can_advance = False
748
+
749
+ return LifecycleStatus(
750
+ mode=self._mode,
751
+ current_phase=self._current_phase,
752
+ completed_phases=self._completed_phases.copy(),
753
+ failed_phases=[pid for pid, s in self._phase_states.items() if s == PhaseState.FAILED],
754
+ blocked_phases=[pid for pid, s in self._phase_states.items() if s == PhaseState.BLOCKED],
755
+ progress_percent=progress,
756
+ can_advance=can_advance,
757
+ next_phase=next_phase,
758
+ )
759
+
760
+ def get_view_mapping(self, command: str) -> Optional[ViewMapping]:
761
+ return VIEW_MAPPINGS.get(command)
762
+
763
+ def resolve_command_to_phases(self, command: str) -> List[PhaseDefinition]:
764
+ mapping = VIEW_MAPPINGS.get(command)
765
+ if not mapping:
766
+ return []
767
+
768
+ return [p for p in self.get_all_phases() if mapping.covers_phase(p.phase_id)]
769
+
770
+ def _get_current_mapping(self) -> Optional[ViewMapping]:
771
+ if self._current_phase:
772
+ for mapping in VIEW_MAPPINGS.values():
773
+ if mapping.covers_phase(self._current_phase):
774
+ return mapping
775
+ return None
776
+
777
+ def init_spec(
778
+ self,
779
+ template_id: str = "requirements",
780
+ project_info: Optional[Dict[str, Any]] = None,
781
+ ) -> Dict[str, Any]:
782
+ """
783
+ Initialize a specification document from template.
784
+
785
+ Args:
786
+ template_id: Template type (requirements/architecture/technical)
787
+ project_info: Optional project information to pre-fill
788
+
789
+ Returns:
790
+ Dict with success status and spec document structure
791
+ """
792
+ template = SPEC_TEMPLATES.get(template_id)
793
+ if not template:
794
+ return {"success": False, "error": f"Unknown template: {template_id}"}
795
+
796
+ doc = {
797
+ "template_id": template_id,
798
+ "name": template.name,
799
+ "phase_id": template.phase_id,
800
+ "sections": {},
801
+ }
802
+ for section in template.sections:
803
+ doc["sections"][section["title"]] = {
804
+ "description": section["description"],
805
+ "content": "",
806
+ "status": "draft",
807
+ }
808
+
809
+ if project_info:
810
+ doc["project_info"] = project_info
811
+
812
+ return {"success": True, "spec": doc, "template_id": template_id}
813
+
814
+ def analyze_spec(
815
+ self,
816
+ source_dir: str = ".",
817
+ template_id: str = "requirements",
818
+ ) -> Dict[str, Any]:
819
+ """
820
+ Analyze existing codebase to generate specification draft.
821
+
822
+ Uses CodeMapGenerator for multi-language code analysis.
823
+
824
+ Args:
825
+ source_dir: Root directory to analyze
826
+ template_id: Template to use for structuring the analysis
827
+
828
+ Returns:
829
+ Dict with success status and analysis results
830
+ """
831
+ try:
832
+ from scripts.collaboration.code_map_generator import CodeMapGenerator
833
+ from scripts.collaboration.language_parsers import DEFAULT_PARSERS
834
+
835
+ gen = CodeMapGenerator(project_root=source_dir, parsers=DEFAULT_PARSERS)
836
+ code_map = gen.generate_map(output_format="dict")
837
+ dep_graph = gen.get_dependency_graph()
838
+
839
+ analysis = {
840
+ "modules": list(code_map.keys()),
841
+ "total_modules": len(code_map),
842
+ "total_classes": sum(
843
+ m.get("total_classes", 0) for m in code_map.values()
844
+ ),
845
+ "total_functions": sum(
846
+ m.get("total_functions", 0) for m in code_map.values()
847
+ ),
848
+ "dependencies": dep_graph,
849
+ "languages": list(
850
+ set(
851
+ m.get("language", "python")
852
+ for m in code_map.values()
853
+ if isinstance(m, dict)
854
+ )
855
+ ),
856
+ }
857
+
858
+ return {"success": True, "analysis": analysis, "template_id": template_id}
859
+ except Exception as e:
860
+ return {"success": False, "error": str(e)}
861
+
862
+ def validate_spec(
863
+ self,
864
+ spec_path: Optional[str] = None,
865
+ template_id: Optional[str] = None,
866
+ spec_data: Optional[Dict[str, Any]] = None,
867
+ ) -> Dict[str, Any]:
868
+ """
869
+ Validate specification completeness and consistency.
870
+
871
+ Args:
872
+ spec_path: Path to specification file (optional)
873
+ template_id: Template to validate against
874
+ spec_data: Specification data dict to validate (optional)
875
+
876
+ Returns:
877
+ Dict with validation results
878
+ """
879
+ results: Dict[str, Any] = {"valid": True, "errors": [], "warnings": []}
880
+
881
+ template = SPEC_TEMPLATES.get(template_id or "requirements")
882
+ if not template:
883
+ return {
884
+ "valid": False,
885
+ "errors": [{"message": f"Unknown template: {template_id}"}],
886
+ "warnings": [],
887
+ }
888
+
889
+ if spec_data is None:
890
+ if spec_path:
891
+ try:
892
+ raw = Path(spec_path).read_text(encoding="utf-8")
893
+ spec_data = json.loads(raw)
894
+ except Exception:
895
+ spec_data = {}
896
+ else:
897
+ spec_data = {}
898
+
899
+ sections = spec_data.get("sections", spec_data)
900
+
901
+ for field_name in template.required_fields:
902
+ value = sections.get(field_name, spec_data.get(field_name, ""))
903
+ if not value or (isinstance(value, str) and not value.strip()):
904
+ results["errors"].append(
905
+ {
906
+ "field": field_name,
907
+ "message": f"Required field '{field_name}' is missing or empty",
908
+ "severity": "critical",
909
+ }
910
+ )
911
+ results["valid"] = False
912
+
913
+ for rule in template.validation_rules:
914
+ field_name = rule.get("field", "")
915
+ check = rule.get("check", "")
916
+ severity = rule.get("severity", "warning")
917
+ value = sections.get(field_name, spec_data.get(field_name, ""))
918
+ if check == "not_empty" and (not value or (isinstance(value, str) and not value.strip())):
919
+ results["warnings"].append(
920
+ {
921
+ "field": field_name,
922
+ "message": f"Field '{field_name}' should not be empty",
923
+ "severity": severity,
924
+ }
925
+ )
926
+
927
+ return results
928
+
929
+
930
+ def create_lifecycle_protocol(mode: LifecycleMode = LifecycleMode.SHORTCUT) -> LifecycleProtocol:
931
+ """Factory function to create appropriate protocol implementation."""
932
+ if mode == LifecycleMode.FULL:
933
+ return FullLifecycleAdapter()
934
+ else:
935
+ adapter = ShortcutLifecycleAdapter()
936
+ adapter.set_mode(mode)
937
+ return adapter
938
+
939
+
940
+ def get_shared_protocol() -> LifecycleProtocol:
941
+ """Get shared singleton instance of lifecycle protocol."""
942
+ if not hasattr(get_shared_protocol, "_instance"):
943
+ get_shared_protocol._instance = create_lifecycle_protocol()
944
+ return get_shared_protocol._instance
945
+
946
+
947
+ class FullLifecycleAdapter(LifecycleProtocol):
948
+ """
949
+ Full 11-phase lifecycle adapter for complete project lifecycles.
950
+
951
+ Implements LifecycleProtocol with FULL mode, supporting all P1-P11 phases
952
+ with automatic dependency resolution and phase ordering.
953
+
954
+ Features:
955
+ - Complete P1-P11 phase support
956
+ - Automatic dependency resolution
957
+ - Phase ordering based on dependency graph
958
+ - Optional phase skipping (for non-required phases)
959
+ - Integration with UnifiedGateEngine and CheckpointManager
960
+
961
+ Use Cases:
962
+ - Large projects requiring full lifecycle management
963
+ - Compliance-critical workflows (all phases mandatory)
964
+ - Projects with complex dependencies between phases
965
+ """
966
+
967
+ def __init__(self, use_unified_gate: bool = True):
968
+ self._mode = LifecycleMode.FULL
969
+ self._current_phase: Optional[str] = None
970
+ self._completed_phases: List[str] = []
971
+ self._phase_states: Dict[str, PhaseState] = {}
972
+ self._use_unified_gate = use_unified_gate
973
+ self._gate_engine = None
974
+ self._checkpoint_manager = None
975
+ self._task_id: Optional[str] = None
976
+ self._skip_optional: bool = False
977
+ self._execution_order: List[str] = []
978
+
979
+ # Initialize unified gate engine if requested
980
+ if use_unified_gate:
981
+ try:
982
+ from scripts.collaboration.unified_gate_engine import (
983
+ UnifiedGateEngine,
984
+ GateType,
985
+ PhaseGateContext,
986
+ get_shared_gate_engine,
987
+ )
988
+ self._gate_engine = get_shared_gate_engine()
989
+ self._GateType = GateType
990
+ self._PhaseGateContext = PhaseGateContext
991
+ logger.debug("UnifiedGateEngine initialized for FullLifecycleAdapter")
992
+ except ImportError as e:
993
+ logger.warning("UnifiedGateEngine not available: %s", e)
994
+ self._use_unified_gate = False
995
+
996
+ # Build execution order from dependencies
997
+ self._build_execution_order()
998
+
999
+ def _build_execution_order(self) -> None:
1000
+ """Build topological execution order based on phase dependencies."""
1001
+ from scripts.collaboration.workflow_engine import PHASE_TEMPLATES
1002
+
1003
+ # Simple topological sort
1004
+ visited = set()
1005
+ order = []
1006
+
1007
+ def visit(phase_id: str):
1008
+ if phase_id in visited:
1009
+ return
1010
+ visited.add(phase_id)
1011
+
1012
+ phase = PHASE_TEMPLATES.get(phase_id, {})
1013
+ for dep in phase.get("dependencies", []):
1014
+ visit(dep)
1015
+
1016
+ order.append(phase_id)
1017
+
1018
+ # Visit all phases in sorted order for deterministic output
1019
+ for pid in sorted(PHASE_TEMPLATES.keys()):
1020
+ visit(pid)
1021
+
1022
+ self._execution_order = order
1023
+ logger.debug("Full execution order: %s", self._execution_order)
1024
+
1025
+ def set_task_id(self, task_id: str) -> None:
1026
+ """Set task ID for checkpoint integration."""
1027
+ self._task_id = task_id
1028
+ if not self._checkpoint_manager:
1029
+ try:
1030
+ from scripts.collaboration.checkpoint_manager import CheckpointManager
1031
+ self._checkpoint_manager = CheckpointManager()
1032
+ logger.info("CheckpointManager initialized for task %s", task_id)
1033
+ except ImportError as e:
1034
+ logger.warning("CheckpointManager not available: %s", e)
1035
+
1036
+ def enable_checkpoint_integration(self, storage_path: str = "./checkpoints") -> bool:
1037
+ """Enable checkpoint manager integration for state persistence."""
1038
+ try:
1039
+ from scripts.collaboration.checkpoint_manager import CheckpointManager
1040
+ self._checkpoint_manager = CheckpointManager(storage_path=storage_path)
1041
+ logger.info("CheckpointManager enabled at %s", storage_path)
1042
+ return True
1043
+ except Exception as e:
1044
+ logger.warning("Failed to enable checkpoint integration: %s", e)
1045
+ return False
1046
+
1047
+ def set_skip_optional(self, skip: bool) -> None:
1048
+ """Configure whether to skip optional phases (P4, P5, P6, P11)."""
1049
+ self._skip_optional = skip
1050
+ logger.info("Skip optional phases: %s", skip)
1051
+
1052
+ def save_state(self) -> bool:
1053
+ """Save current lifecycle state to checkpoint manager."""
1054
+ if self._checkpoint_manager and self._task_id:
1055
+ try:
1056
+ phase_states_str = {
1057
+ pid: (state.value if hasattr(state, 'value') else str(state))
1058
+ for pid, state in self._phase_states.items()
1059
+ }
1060
+ mode_str = self._mode.value if hasattr(self._mode, 'value') else str(self._mode)
1061
+
1062
+ return self._checkpoint_manager.save_lifecycle_state(
1063
+ task_id=self._task_id,
1064
+ current_phase=self._current_phase,
1065
+ phase_states=phase_states_str,
1066
+ completed_phases=self._completed_phases.copy(),
1067
+ mode=mode_str,
1068
+ metadata={
1069
+ "adapter_type": "full",
1070
+ "skip_optional": self._skip_optional,
1071
+ "execution_order": self._execution_order,
1072
+ },
1073
+ )
1074
+ except Exception as e:
1075
+ logger.warning("Failed to save lifecycle state: %s", e)
1076
+ return False
1077
+ return False
1078
+
1079
+ def restore_state(self) -> bool:
1080
+ """Restore lifecycle state from checkpoint manager."""
1081
+ if self._checkpoint_manager and self._task_id:
1082
+ try:
1083
+ state = self._checkpoint_manager.load_lifecycle_state(self._task_id)
1084
+ if state:
1085
+ self._current_phase = state.get("current_phase")
1086
+ self._completed_phases = state.get("completed_phases", [])
1087
+
1088
+ phase_states_raw = state.get("phase_states", {})
1089
+ self._phase_states = {}
1090
+ for pid, pstate in phase_states_raw.items():
1091
+ try:
1092
+ self._phase_states[pid] = PhaseState(pstate)
1093
+ except ValueError:
1094
+ self._phase_states[pid] = PhaseState.PENDING
1095
+
1096
+ mode_raw = state.get("mode", "full")
1097
+ try:
1098
+ self._mode = LifecycleMode(mode_raw)
1099
+ except ValueError:
1100
+ pass
1101
+
1102
+ # Restore metadata
1103
+ metadata = state.get("metadata", {})
1104
+ self._skip_optional = metadata.get("skip_optional", False)
1105
+ self._execution_order = metadata.get("execution_order", self._execution_order)
1106
+
1107
+ logger.info(
1108
+ "Restored lifecycle state: task=%s, phase=%s",
1109
+ self._task_id,
1110
+ self._current_phase,
1111
+ )
1112
+ return True
1113
+ except Exception as e:
1114
+ logger.warning("Failed to restore lifecycle state: %s", e)
1115
+ return False
1116
+ return False
1117
+
1118
+ def get_mode(self) -> LifecycleMode:
1119
+ return self._mode
1120
+
1121
+ def set_mode(self, mode: LifecycleMode) -> None:
1122
+ self._mode = mode
1123
+
1124
+ def get_all_phases(self) -> List[PhaseDefinition]:
1125
+ from scripts.collaboration.workflow_engine import PHASE_TEMPLATES
1126
+
1127
+ phases = []
1128
+ for pid, ptmpl in PHASE_TEMPLATES.items():
1129
+ phases.append(PhaseDefinition(
1130
+ phase_id=pid,
1131
+ name=ptmpl.get("name", pid),
1132
+ description=ptmpl.get("description", ""),
1133
+ role_id=ptmpl.get("role_id", ""),
1134
+ dependencies=ptmpl.get("dependencies", []),
1135
+ artifacts_in=ptmpl.get("artifacts_in", ""),
1136
+ artifacts_out=ptmpl.get("artifacts_out", ""),
1137
+ gate_condition=ptmpl.get("gate_condition", ""),
1138
+ reviewers=ptmpl.get("reviewers", []),
1139
+ optional=ptmpl.get("optional", False),
1140
+ ))
1141
+ return sorted(phases, key=lambda p: p.phase_id)
1142
+
1143
+ def get_active_phases(self) -> List[PhaseDefinition]:
1144
+ all_phases = self.get_all_phases()
1145
+ if self._skip_optional:
1146
+ return [p for p in all_phases if not p.optional]
1147
+ return all_phases
1148
+
1149
+ def get_phase(self, phase_id: str) -> Optional[PhaseDefinition]:
1150
+ for p in self.get_all_phases():
1151
+ if p.phase_id == phase_id:
1152
+ return p
1153
+ return None
1154
+
1155
+ def get_current_phase(self) -> Optional[PhaseDefinition]:
1156
+ if self._current_phase:
1157
+ return self.get_phase(self._current_phase)
1158
+ return None
1159
+
1160
+ def advance_to_phase(self, phase_id: str) -> PhaseResult:
1161
+ prev_state = self._phase_states.get(phase_id, PhaseState.PENDING)
1162
+
1163
+ # Check if already completed (idempotent)
1164
+ if prev_state == PhaseState.COMPLETED:
1165
+ return PhaseResult(
1166
+ success=True,
1167
+ phase_id=phase_id,
1168
+ previous_state=prev_state,
1169
+ new_state=PhaseState.COMPLETED,
1170
+ gate_result=GateResult(passed=True, verdict="APPROVE"),
1171
+ )
1172
+
1173
+ # Get phase definition
1174
+ phase_def = self.get_phase(phase_id)
1175
+ if not phase_def:
1176
+ result = PhaseResult(
1177
+ success=False,
1178
+ phase_id=phase_id,
1179
+ previous_state=prev_state,
1180
+ new_state=PhaseState.BLOCKED,
1181
+ gate_result=GateResult(
1182
+ passed=False,
1183
+ verdict="REJECT",
1184
+ gap_report=f"Phase {phase_id} not found in 11-phase model",
1185
+ ),
1186
+ error=f"Unknown phase: {phase_id}",
1187
+ )
1188
+ self.save_state()
1189
+ return result
1190
+
1191
+ # Check if optional and should be skipped
1192
+ if self._skip_optional and phase_def.optional:
1193
+ self._phase_states[phase_id] = PhaseState.SKIPPED
1194
+ logger.info("Skipping optional phase: %s", phase_id)
1195
+ return PhaseResult(
1196
+ success=True,
1197
+ phase_id=phase_id,
1198
+ previous_state=prev_state,
1199
+ new_state=PhaseState.SKIPPED,
1200
+ gate_result=GateResult(passed=True, verdict="APPROVE"),
1201
+ )
1202
+
1203
+ # Check dependencies first (strict in FULL mode)
1204
+ unmet_deps = [d for d in phase_def.dependencies if d not in self._completed_phases]
1205
+ if unmet_deps:
1206
+ result = PhaseResult(
1207
+ success=False,
1208
+ phase_id=phase_id,
1209
+ previous_state=prev_state,
1210
+ new_state=PhaseState.BLOCKED,
1211
+ gate_result=GateResult(
1212
+ passed=False,
1213
+ verdict="CONDITIONAL",
1214
+ missing_evidence=[{"dependency": d} for d in unmet_deps],
1215
+ gap_report=f"Unmet dependencies: {', '.join(unmet_deps)}",
1216
+ ),
1217
+ error=f"Unmet dependencies: {unmet_deps}",
1218
+ )
1219
+ self.save_state()
1220
+ return result
1221
+
1222
+ # Run gate check using unified engine or fallback
1223
+ gate_result = self.check_gate(phase_id)
1224
+ if not gate_result.passed and gate_result.verdict == "REJECT":
1225
+ self._phase_states[phase_id] = PhaseState.BLOCKED
1226
+ result = PhaseResult(
1227
+ success=False,
1228
+ phase_id=phase_id,
1229
+ previous_state=prev_state,
1230
+ new_state=PhaseState.BLOCKED,
1231
+ gate_result=gate_result,
1232
+ error=f"Gate rejected: {gate_result.gap_report}",
1233
+ )
1234
+ self.save_state()
1235
+ return result
1236
+
1237
+ # Advance to RUNNING state
1238
+ self._current_phase = phase_id
1239
+ self._phase_states[phase_id] = PhaseState.RUNNING
1240
+ logger.debug("Full mode advanced to phase %s", phase_id)
1241
+ self.save_state()
1242
+
1243
+ return PhaseResult(
1244
+ success=True,
1245
+ phase_id=phase_id,
1246
+ previous_state=prev_state,
1247
+ new_state=PhaseState.RUNNING,
1248
+ gate_result=gate_result,
1249
+ )
1250
+
1251
+ def complete_phase(self, phase_id: str) -> None:
1252
+ """Mark a phase as completed."""
1253
+ self._phase_states[phase_id] = PhaseState.COMPLETED
1254
+ if phase_id not in self._completed_phases:
1255
+ self._completed_phases.append(phase_id)
1256
+ self.save_state()
1257
+
1258
+ def check_gate(self, phase_id: Optional[str] = None) -> GateResult:
1259
+ target = phase_id or self._current_phase
1260
+ if not target:
1261
+ return GateResult(passed=True, verdict="APPROVE")
1262
+
1263
+ phase_def = self.get_phase(target)
1264
+ if not phase_def:
1265
+ return GateResult(passed=False, verdict="REJECT", gap_report=f"Phase {target} not found")
1266
+
1267
+ # Use UnifiedGateEngine if available
1268
+ if self._use_unified_gate and self._gate_engine:
1269
+ try:
1270
+ return self._check_gate_with_unified_engine(target, phase_def)
1271
+ except Exception as e:
1272
+ logger.warning("UnifiedGateEngine failed, falling back: %s", e)
1273
+
1274
+ # Fallback to basic gate checks
1275
+ return self._check_gate_basic(target, phase_def)
1276
+
1277
+ def _check_gate_with_unified_engine(self, target: str, phase_def: PhaseDefinition) -> GateResult:
1278
+ """Check gate using UnifiedGateEngine."""
1279
+ unmet_deps = [d for d in phase_def.dependencies if d not in self._completed_phases]
1280
+
1281
+ context = self._PhaseGateContext(
1282
+ phase_id=target,
1283
+ phase_name=phase_def.name,
1284
+ current_state=self._phase_states.get(target, PhaseState.PENDING).value,
1285
+ target_state="running",
1286
+ dependencies_met=len(unmet_deps) == 0,
1287
+ completed_phases=self._completed_phases.copy(),
1288
+ unmet_dependencies=unmet_deps,
1289
+ )
1290
+
1291
+ unified_result = self._gate_engine.check(
1292
+ gate_type=self._GateType.PHASE_TRANSITION,
1293
+ context=context,
1294
+ )
1295
+
1296
+ return GateResult(
1297
+ passed=unified_result.passed,
1298
+ verdict=unified_result.verdict,
1299
+ red_flags=[
1300
+ {"id": issue.get("code", "unknown"), "severity": "critical", **issue}
1301
+ for issue in unified_result.critical_issues
1302
+ ],
1303
+ missing_evidence=[
1304
+ {"key": ev, "required": True}
1305
+ for ev in unified_result.evidence_required
1306
+ ],
1307
+ gap_report="\n".join([
1308
+ f"- [{issue.get('severity', 'unknown')}] {issue.get('message', '')}"
1309
+ for issue in unified_result.critical_issues + unified_result.warnings
1310
+ ]) if (unified_result.critical_issues or unified_result.warnings) else "",
1311
+ )
1312
+
1313
+ def _check_gate_basic(self, target: str, phase_def: PhaseDefinition) -> GateResult:
1314
+ """Basic fallback gate check without UnifiedGateEngine."""
1315
+ if not phase_def.optional:
1316
+ for dep in phase_def.dependencies:
1317
+ if dep not in self._completed_phases:
1318
+ dep_state = self._phase_states.get(dep, PhaseState.PENDING)
1319
+ if dep_state != PhaseState.COMPLETED:
1320
+ return GateResult(
1321
+ passed=False,
1322
+ verdict="BLOCKED",
1323
+ gap_report=f"Unmet dependencies: {dep}",
1324
+ )
1325
+ current_state = self._phase_states.get(target, PhaseState.PENDING)
1326
+ if current_state == PhaseState.BLOCKED:
1327
+ return GateResult(passed=False, verdict="BLOCKED", gap_report="Phase is blocked")
1328
+ return GateResult(passed=True, verdict="APPROVE")
1329
+
1330
+ def get_status(self) -> LifecycleStatus:
1331
+ all_phases = self.get_active_phases()
1332
+ total = len(all_phases)
1333
+ completed = len([p for p in all_phases if p.phase_id in self._completed_phases])
1334
+ progress = (completed / total * 100) if total > 0 else 0.0
1335
+
1336
+ next_phase = None
1337
+ can_advance = True
1338
+ for pid in self._execution_order:
1339
+ phase_def = self.get_phase(pid)
1340
+ if not phase_def:
1341
+ continue
1342
+ if self._skip_optional and phase_def.optional:
1343
+ continue
1344
+ state = self._phase_states.get(pid, PhaseState.PENDING)
1345
+ if state == PhaseState.PENDING and not next_phase:
1346
+ next_phase = pid
1347
+ if state == PhaseState.BLOCKED:
1348
+ can_advance = False
1349
+
1350
+ return LifecycleStatus(
1351
+ mode=self._mode,
1352
+ current_phase=self._current_phase,
1353
+ completed_phases=self._completed_phases.copy(),
1354
+ failed_phases=[pid for pid, s in self._phase_states.items() if s == PhaseState.FAILED],
1355
+ blocked_phases=[pid for pid, s in self._phase_states.items() if s == PhaseState.BLOCKED],
1356
+ progress_percent=progress,
1357
+ can_advance=can_advance,
1358
+ next_phase=next_phase,
1359
+ )
1360
+
1361
+ def get_view_mapping(self, command: str) -> Optional[ViewMapping]:
1362
+ return VIEW_MAPPINGS.get(command)
1363
+
1364
+ def resolve_command_to_phases(self, command: str) -> List[PhaseDefinition]:
1365
+ mapping = VIEW_MAPPINGS.get(command)
1366
+ if not mapping:
1367
+ return []
1368
+ return [p for p in self.get_all_phases() if mapping.covers_phase(p.phase_id)]
1369
+
1370
+ def get_next_phase(self) -> Optional[str]:
1371
+ """Get the next phase that should be executed based on execution order."""
1372
+ for pid in self._execution_order:
1373
+ state = self._phase_states.get(pid, PhaseState.PENDING)
1374
+ if state == PhaseState.PENDING:
1375
+ phase_def = self.get_phase(pid)
1376
+ if phase_def and (not self._skip_optional or not phase_def.optional):
1377
+ return pid
1378
+ return None
1379
+
1380
+ def auto_advance(self) -> PhaseResult:
1381
+ """Automatically advance to the next pending phase."""
1382
+ next_phase = self.get_next_phase()
1383
+ if not next_phase:
1384
+ return PhaseResult(
1385
+ success=False,
1386
+ phase_id="",
1387
+ previous_state=PhaseState.PENDING,
1388
+ new_state=PhaseState.PENDING,
1389
+ error="No more phases to execute",
1390
+ )
1391
+ return self.advance_to_phase(next_phase)
1392
+
1393
+ def get_execution_progress(self) -> Dict[str, Any]:
1394
+ """Get detailed execution progress information."""
1395
+ phases_info = []
1396
+ for pid in self._execution_order:
1397
+ phase_def = self.get_phase(pid)
1398
+ if not phase_def:
1399
+ continue
1400
+ state = self._phase_states.get(pid, PhaseState.PENDING)
1401
+ phases_info.append({
1402
+ "phase_id": pid,
1403
+ "name": phase_def.name,
1404
+ "state": state.value if hasattr(state, 'value') else str(state),
1405
+ "optional": phase_def.optional,
1406
+ "role": phase_def.role_id,
1407
+ "completed": pid in self._completed_phases,
1408
+ })
1409
+
1410
+ status = self.get_status()
1411
+ return {
1412
+ "total_phases": len(self.get_active_phases()),
1413
+ "completed_phases": len(status.completed_phases),
1414
+ "progress_percent": status.progress_percent,
1415
+ "current_phase": status.current_phase,
1416
+ "next_phase": status.next_phase,
1417
+ "can_advance": status.can_advance,
1418
+ "phases": phases_info,
1419
+ "execution_order": self._execution_order,
1420
+ "skip_optional": self._skip_optional,
1421
+ }
1422
+
1423
+ def init_spec(
1424
+ self,
1425
+ template_id: str = "requirements",
1426
+ project_info: Optional[Dict[str, Any]] = None,
1427
+ ) -> Dict[str, Any]:
1428
+ shortcut = ShortcutLifecycleAdapter(use_unified_gate=self._use_unified_gate)
1429
+ return shortcut.init_spec(template_id, project_info)
1430
+
1431
+ def analyze_spec(
1432
+ self,
1433
+ source_dir: str = ".",
1434
+ template_id: str = "requirements",
1435
+ ) -> Dict[str, Any]:
1436
+ shortcut = ShortcutLifecycleAdapter(use_unified_gate=self._use_unified_gate)
1437
+ return shortcut.analyze_spec(source_dir, template_id)
1438
+
1439
+ def validate_spec(
1440
+ self,
1441
+ spec_path: Optional[str] = None,
1442
+ template_id: Optional[str] = None,
1443
+ spec_data: Optional[Dict[str, Any]] = None,
1444
+ ) -> Dict[str, Any]:
1445
+ shortcut = ShortcutLifecycleAdapter(use_unified_gate=self._use_unified_gate)
1446
+ return shortcut.validate_spec(spec_path, template_id, spec_data)