continuum-toolkit 1.0.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 (72) hide show
  1. cli/__init__.py +12 -0
  2. cli/main.py +415 -0
  3. confidence/__init__.py +13 -0
  4. confidence/calculator.py +308 -0
  5. confidence/models.py +40 -0
  6. context/__init__.py +17 -0
  7. context/models.py +108 -0
  8. context/pruner.py +228 -0
  9. context/selector.py +193 -0
  10. continuum_toolkit-1.0.0.dist-info/METADATA +511 -0
  11. continuum_toolkit-1.0.0.dist-info/RECORD +72 -0
  12. continuum_toolkit-1.0.0.dist-info/WHEEL +5 -0
  13. continuum_toolkit-1.0.0.dist-info/entry_points.txt +2 -0
  14. continuum_toolkit-1.0.0.dist-info/licenses/LICENSE +21 -0
  15. continuum_toolkit-1.0.0.dist-info/top_level.txt +13 -0
  16. contradictions/__init__.py +19 -0
  17. contradictions/detector.py +442 -0
  18. contradictions/models.py +75 -0
  19. core/__init__.py +97 -0
  20. core/enums.py +130 -0
  21. core/evidence.py +117 -0
  22. core/interfaces.py +209 -0
  23. core/schema.py +391 -0
  24. core/serializer.py +84 -0
  25. core/state_models.py +530 -0
  26. daemon/__init__.py +12 -0
  27. daemon/service.py +170 -0
  28. extractors/__init__.py +51 -0
  29. extractors/base.py +117 -0
  30. extractors/config/parsers.py +288 -0
  31. extractors/config/secret_sanitizer.py +91 -0
  32. extractors/config_extractor.py +172 -0
  33. extractors/conversation/analyzers.py +193 -0
  34. extractors/conversation/models.py +148 -0
  35. extractors/conversation_extractor.py +166 -0
  36. extractors/git_extractor.py +305 -0
  37. extractors/parsers/base.py +91 -0
  38. extractors/parsers/comment_parser.py +51 -0
  39. extractors/parsers/js_ts_parser.py +171 -0
  40. extractors/parsers/python_parser.py +180 -0
  41. extractors/verification/runners.py +278 -0
  42. extractors/verification_extractor.py +263 -0
  43. extractors/workspace_extractor.py +221 -0
  44. graph/__init__.py +24 -0
  45. graph/diff.py +109 -0
  46. graph/manager.py +473 -0
  47. graph/models.py +62 -0
  48. graph/propagator.py +194 -0
  49. graph/query.py +86 -0
  50. graph/snapshot.py +85 -0
  51. handoff/__init__.py +28 -0
  52. handoff/adapters/__init__.py +45 -0
  53. handoff/adapters/base.py +90 -0
  54. handoff/adapters/claude_adapter.py +176 -0
  55. handoff/adapters/codex_gpt_adapter.py +151 -0
  56. handoff/adapters/gemini_adapter.py +151 -0
  57. handoff/adapters/local_model_adapter.py +130 -0
  58. handoff/models.py +88 -0
  59. handoff/packager.py +288 -0
  60. pipeline/__init__.py +9 -0
  61. pipeline/orchestrator.py +260 -0
  62. resolution/__init__.py +15 -0
  63. resolution/resolver.py +311 -0
  64. storage/__init__.py +18 -0
  65. storage/hooks.py +125 -0
  66. storage/manager.py +127 -0
  67. storage/models.py +39 -0
  68. storage/recovery.py +98 -0
  69. watcher/__init__.py +17 -0
  70. watcher/detector.py +136 -0
  71. watcher/models.py +62 -0
  72. watcher/updater.py +163 -0
core/state_models.py ADDED
@@ -0,0 +1,530 @@
1
+ """
2
+ Project Continuum - Three-State Models & Canonical State
3
+ ========================================================
4
+ Implements the core ontological separation between:
5
+ 1. Project State (Physical Ground Truth)
6
+ 2. Conversational State (Intent & Reasoning Claims)
7
+ 3. Agent Execution State (Tactical In-Flight Intent)
8
+ and the aggregated CanonicalProjectState container.
9
+ """
10
+
11
+ from dataclasses import dataclass, field, asdict
12
+ from datetime import datetime, timezone
13
+ from typing import Any, Dict, List, Optional
14
+ import uuid
15
+
16
+ from core.enums import Status, NodeType, RelationType, EvidenceLevel
17
+ from core.evidence import Evidence
18
+
19
+
20
+ # ==============================================================================
21
+ # 1. PROJECT STATE (PHYSICAL GROUND TRUTH)
22
+ # ==============================================================================
23
+
24
+ @dataclass
25
+ class AstSymbol:
26
+ """Represents a code symbol extracted from AST (class, function, method, interface)."""
27
+ name: str
28
+ kind: str # function, class, interface, method, variable
29
+ file_path: str
30
+ line_start: int
31
+ line_end: int
32
+ exported: bool = False
33
+ docstring: Optional[str] = None
34
+ parameters: List[str] = field(default_factory=list)
35
+ return_type: Optional[str] = None
36
+ evidence_id: Optional[str] = None
37
+
38
+ def to_dict(self) -> Dict[str, Any]:
39
+ return asdict(self)
40
+
41
+ @classmethod
42
+ def from_dict(cls, data: Dict[str, Any]) -> "AstSymbol":
43
+ return cls(**data)
44
+
45
+
46
+ @dataclass
47
+ class TestResult:
48
+ """Represents a verifiable test execution result."""
49
+ test_id: str
50
+ name: str
51
+ suite: str
52
+ status: Status # VERIFIED (passed) or FAILED
53
+ exit_code: int
54
+ duration_ms: float
55
+ output_snippet: str = ""
56
+ error_message: Optional[str] = None
57
+ evidence_id: Optional[str] = None
58
+
59
+ def to_dict(self) -> Dict[str, Any]:
60
+ d = asdict(self)
61
+ d["status"] = self.status.value
62
+ return d
63
+
64
+ @classmethod
65
+ def from_dict(cls, data: Dict[str, Any]) -> "TestResult":
66
+ data_copy = dict(data)
67
+ data_copy["status"] = Status(data_copy["status"])
68
+ return cls(**data_copy)
69
+
70
+
71
+ @dataclass
72
+ class GitState:
73
+ """Represents verifiable Git repository state."""
74
+ is_repo: bool = False
75
+ branch: Optional[str] = None
76
+ head_commit: Optional[str] = None
77
+ is_dirty: bool = False
78
+ staged_files: List[str] = field(default_factory=list)
79
+ unstaged_files: List[str] = field(default_factory=list)
80
+ untracked_files: List[str] = field(default_factory=list)
81
+ recent_commits: List[Dict[str, Any]] = field(default_factory=list)
82
+ evidence_ids: List[str] = field(default_factory=list)
83
+
84
+ def to_dict(self) -> Dict[str, Any]:
85
+ return asdict(self)
86
+
87
+ @classmethod
88
+ def from_dict(cls, data: Dict[str, Any]) -> "GitState":
89
+ return cls(**data)
90
+
91
+
92
+ @dataclass
93
+ class ManifestInfo:
94
+ """Represents project manifests, package definitions, dependencies, and scripts."""
95
+ manifest_type: str # package.json, pyproject.toml, Cargo.toml, etc.
96
+ file_path: str
97
+ project_name: Optional[str] = None
98
+ version: Optional[str] = None
99
+ dependencies: Dict[str, str] = field(default_factory=dict)
100
+ dev_dependencies: Dict[str, str] = field(default_factory=dict)
101
+ scripts: Dict[str, str] = field(default_factory=dict)
102
+ evidence_id: Optional[str] = None
103
+
104
+ def to_dict(self) -> Dict[str, Any]:
105
+ return asdict(self)
106
+
107
+ @classmethod
108
+ def from_dict(cls, data: Dict[str, Any]) -> "ManifestInfo":
109
+ return cls(**data)
110
+
111
+
112
+ @dataclass
113
+ class ProjectState:
114
+ """
115
+ State 1: Physical Reality of the Project.
116
+ Contains solely physical facts derived from disk, Git, AST, tests, and builds.
117
+ """
118
+ root_path: str
119
+ detected_languages: List[str] = field(default_factory=list)
120
+ files: List[str] = field(default_factory=list)
121
+ manifests: List[ManifestInfo] = field(default_factory=list)
122
+ symbols: List[AstSymbol] = field(default_factory=list)
123
+ git_state: GitState = field(default_factory=GitState)
124
+ test_results: List[TestResult] = field(default_factory=list)
125
+ build_status: Status = Status.UNKNOWN
126
+ todo_markers: List[Dict[str, Any]] = field(default_factory=list)
127
+ evidence_ids: List[str] = field(default_factory=list)
128
+ last_scanned_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
129
+
130
+ def to_dict(self) -> Dict[str, Any]:
131
+ return {
132
+ "root_path": self.root_path,
133
+ "detected_languages": self.detected_languages,
134
+ "files": self.files,
135
+ "manifests": [m.to_dict() for m in self.manifests],
136
+ "symbols": [s.to_dict() for s in self.symbols],
137
+ "git_state": self.git_state.to_dict(),
138
+ "test_results": [t.to_dict() for t in self.test_results],
139
+ "build_status": self.build_status.value,
140
+ "todo_markers": self.todo_markers,
141
+ "evidence_ids": self.evidence_ids,
142
+ "last_scanned_at": self.last_scanned_at
143
+ }
144
+
145
+ @classmethod
146
+ def from_dict(cls, data: Dict[str, Any]) -> "ProjectState":
147
+ return cls(
148
+ root_path=data.get("root_path", ""),
149
+ detected_languages=data.get("detected_languages", []),
150
+ files=data.get("files", []),
151
+ manifests=[ManifestInfo.from_dict(m) for m in data.get("manifests", [])],
152
+ symbols=[AstSymbol.from_dict(s) for s in data.get("symbols", [])],
153
+ git_state=GitState.from_dict(data.get("git_state", {})),
154
+ test_results=[TestResult.from_dict(t) for t in data.get("test_results", [])],
155
+ build_status=Status(data.get("build_status", Status.UNKNOWN.value)),
156
+ todo_markers=data.get("todo_markers", []),
157
+ evidence_ids=data.get("evidence_ids", []),
158
+ last_scanned_at=data.get("last_scanned_at", datetime.now(timezone.utc).isoformat())
159
+ )
160
+
161
+
162
+ # ==============================================================================
163
+ # 2. CONVERSATIONAL STATE (INTENT & REASONING CLAIMS)
164
+ # ==============================================================================
165
+
166
+ @dataclass
167
+ class Requirement:
168
+ id: str
169
+ title: str
170
+ description: str
171
+ source_turn: Optional[int] = None
172
+ status: Status = Status.PENDING
173
+ evidence_id: Optional[str] = None
174
+
175
+ def to_dict(self) -> Dict[str, Any]:
176
+ d = asdict(self)
177
+ d["status"] = self.status.value
178
+ return d
179
+
180
+ @classmethod
181
+ def from_dict(cls, data: Dict[str, Any]) -> "Requirement":
182
+ d = dict(data)
183
+ d["status"] = Status(d["status"])
184
+ return cls(**d)
185
+
186
+
187
+ @dataclass
188
+ class ArchitecturalDecision:
189
+ id: str
190
+ title: str
191
+ rationale: str
192
+ constraints: List[str] = field(default_factory=list)
193
+ recorded_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
194
+ evidence_id: Optional[str] = None
195
+
196
+ def to_dict(self) -> Dict[str, Any]:
197
+ return asdict(self)
198
+
199
+ @classmethod
200
+ def from_dict(cls, data: Dict[str, Any]) -> "ArchitecturalDecision":
201
+ return cls(**data)
202
+
203
+
204
+ @dataclass
205
+ class AgentClaim:
206
+ """An unverified claim made by an AI model in chat (e.g. 'Payment service complete')."""
207
+ id: str
208
+ claim_text: str
209
+ target_component: Optional[str] = None
210
+ claimed_status: Status = Status.VERIFIED
211
+ source_agent: str = "unknown_agent"
212
+ turn_id: Optional[int] = None
213
+ confidence_claimed: Optional[float] = None
214
+ evidence_id: Optional[str] = None
215
+
216
+ def to_dict(self) -> Dict[str, Any]:
217
+ d = asdict(self)
218
+ d["claimed_status"] = self.claimed_status.value
219
+ return d
220
+
221
+ @classmethod
222
+ def from_dict(cls, data: Dict[str, Any]) -> "AgentClaim":
223
+ d = dict(data)
224
+ d["claimed_status"] = Status(d["claimed_status"])
225
+ return cls(**d)
226
+
227
+
228
+ @dataclass
229
+ class UnresolvedQuestion:
230
+ id: str
231
+ question: str
232
+ context: str = ""
233
+ blocking: bool = False
234
+ asked_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
235
+ evidence_id: Optional[str] = None
236
+
237
+ def to_dict(self) -> Dict[str, Any]:
238
+ return asdict(self)
239
+
240
+ @classmethod
241
+ def from_dict(cls, data: Dict[str, Any]) -> "UnresolvedQuestion":
242
+ return cls(**data)
243
+
244
+
245
+ @dataclass
246
+ class ConversationalState:
247
+ """
248
+ State 2: Intent, Decisions, and Claims from conversations.
249
+ Kept strictly segregated from physical reality.
250
+ """
251
+ session_id: str = field(default_factory=lambda: f"sess_{uuid.uuid4().hex[:8]}")
252
+ user_requirements: List[Requirement] = field(default_factory=list)
253
+ architectural_decisions: List[ArchitecturalDecision] = field(default_factory=list)
254
+ agent_claims: List[AgentClaim] = field(default_factory=list)
255
+ assumptions: List[str] = field(default_factory=list)
256
+ unresolved_questions: List[UnresolvedQuestion] = field(default_factory=list)
257
+ transcript_provenance: List[str] = field(default_factory=list)
258
+
259
+ def to_dict(self) -> Dict[str, Any]:
260
+ return {
261
+ "session_id": self.session_id,
262
+ "user_requirements": [r.to_dict() for r in self.user_requirements],
263
+ "architectural_decisions": [d.to_dict() for d in self.architectural_decisions],
264
+ "agent_claims": [c.to_dict() for c in self.agent_claims],
265
+ "assumptions": self.assumptions,
266
+ "unresolved_questions": [q.to_dict() for q in self.unresolved_questions],
267
+ "transcript_provenance": self.transcript_provenance
268
+ }
269
+
270
+ @classmethod
271
+ def from_dict(cls, data: Dict[str, Any]) -> "ConversationalState":
272
+ return cls(
273
+ session_id=data.get("session_id", f"sess_{uuid.uuid4().hex[:8]}"),
274
+ user_requirements=[Requirement.from_dict(r) for r in data.get("user_requirements", [])],
275
+ architectural_decisions=[ArchitecturalDecision.from_dict(d) for d in data.get("architectural_decisions", [])],
276
+ agent_claims=[AgentClaim.from_dict(c) for c in data.get("agent_claims", [])],
277
+ assumptions=data.get("assumptions", []),
278
+ unresolved_questions=[UnresolvedQuestion.from_dict(q) for q in data.get("unresolved_questions", [])],
279
+ transcript_provenance=data.get("transcript_provenance", [])
280
+ )
281
+
282
+
283
+ # ==============================================================================
284
+ # 3. AGENT EXECUTION STATE (TACTICAL IN-FLIGHT INTENT)
285
+ # ==============================================================================
286
+
287
+ @dataclass
288
+ class TacticalTask:
289
+ id: str
290
+ title: str
291
+ status: Status = Status.IN_PROGRESS
292
+ target_files: List[str] = field(default_factory=list)
293
+ target_symbols: List[str] = field(default_factory=list)
294
+ notes: str = ""
295
+
296
+ def to_dict(self) -> Dict[str, Any]:
297
+ d = asdict(self)
298
+ d["status"] = self.status.value
299
+ return d
300
+
301
+ @classmethod
302
+ def from_dict(cls, data: Dict[str, Any]) -> "TacticalTask":
303
+ d = dict(data)
304
+ d["status"] = Status(d["status"])
305
+ return cls(**d)
306
+
307
+
308
+ @dataclass
309
+ class NextActionRecommendation:
310
+ action_type: str # e.g., "EDIT_FILE", "RUN_TEST", "FIX_SYNTAX", "RESOLVE_CONTRADICTION"
311
+ target_uri: str
312
+ description: str
313
+ prerequisites: List[str] = field(default_factory=list)
314
+
315
+ def to_dict(self) -> Dict[str, Any]:
316
+ return asdict(self)
317
+
318
+ @classmethod
319
+ def from_dict(cls, data: Dict[str, Any]) -> "NextActionRecommendation":
320
+ return cls(**data)
321
+
322
+
323
+ @dataclass
324
+ class AgentExecutionState:
325
+ """
326
+ State 3: What the previous AI was in the middle of executing.
327
+ Tracks in-flight refactorings, modified files, transient errors, and next actions.
328
+ """
329
+ agent_id: str = "agent_unknown"
330
+ model_name: str = "unknown"
331
+ active_tasks: List[TacticalTask] = field(default_factory=list)
332
+ modified_files_in_flight: List[str] = field(default_factory=list)
333
+ last_error_encountered: Optional[str] = None
334
+ next_action: Optional[NextActionRecommendation] = None
335
+ execution_context_metadata: Dict[str, Any] = field(default_factory=dict)
336
+ updated_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
337
+
338
+ def to_dict(self) -> Dict[str, Any]:
339
+ return {
340
+ "agent_id": self.agent_id,
341
+ "model_name": self.model_name,
342
+ "active_tasks": [t.to_dict() for t in self.active_tasks],
343
+ "modified_files_in_flight": self.modified_files_in_flight,
344
+ "last_error_encountered": self.last_error_encountered,
345
+ "next_action": self.next_action.to_dict() if self.next_action else None,
346
+ "execution_context_metadata": self.execution_context_metadata,
347
+ "updated_at": self.updated_at
348
+ }
349
+
350
+ @classmethod
351
+ def from_dict(cls, data: Dict[str, Any]) -> "AgentExecutionState":
352
+ next_act = NextActionRecommendation.from_dict(data["next_action"]) if data.get("next_action") else None
353
+ return cls(
354
+ agent_id=data.get("agent_id", "agent_unknown"),
355
+ model_name=data.get("model_name", "unknown"),
356
+ active_tasks=[TacticalTask.from_dict(t) for t in data.get("active_tasks", [])],
357
+ modified_files_in_flight=data.get("modified_files_in_flight", []),
358
+ last_error_encountered=data.get("last_error_encountered"),
359
+ next_action=next_act,
360
+ execution_context_metadata=data.get("execution_context_metadata", {}),
361
+ updated_at=data.get("updated_at", datetime.now(timezone.utc).isoformat())
362
+ )
363
+
364
+
365
+ # ==============================================================================
366
+ # CANONICAL GRAPH NODES & CONTRADICTION MODELS
367
+ # ==============================================================================
368
+
369
+ @dataclass
370
+ class ContradictionRecord:
371
+ """
372
+ Represents an explicit discrepancy between claims/docs and physical reality.
373
+ """
374
+ id: str
375
+ severity: str # HIGH, MEDIUM, LOW
376
+ claim_id: Optional[str] = None
377
+ claim_text: str = ""
378
+ physical_evidence_id: Optional[str] = None
379
+ explanation: str = ""
380
+ detected_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
381
+ resolved: bool = False
382
+
383
+ def to_dict(self) -> Dict[str, Any]:
384
+ return asdict(self)
385
+
386
+ @classmethod
387
+ def from_dict(cls, data: Dict[str, Any]) -> "ContradictionRecord":
388
+ return cls(**data)
389
+
390
+
391
+ @dataclass
392
+ class GraphNode:
393
+ """Node in the Canonical State Graph (DAG)."""
394
+ id: str
395
+ name: str
396
+ node_type: NodeType
397
+ status: Status = Status.UNKNOWN
398
+ confidence_score: float = 0.0 # 0.0 to 100.0
399
+ evidence_ids: List[str] = field(default_factory=list)
400
+ metadata: Dict[str, Any] = field(default_factory=dict)
401
+
402
+ def to_dict(self) -> Dict[str, Any]:
403
+ return {
404
+ "id": self.id,
405
+ "name": self.name,
406
+ "node_type": self.node_type.value,
407
+ "status": self.status.value,
408
+ "confidence_score": self.confidence_score,
409
+ "evidence_ids": self.evidence_ids,
410
+ "metadata": self.metadata
411
+ }
412
+
413
+ @classmethod
414
+ def from_dict(cls, data: Dict[str, Any]) -> "GraphNode":
415
+ return cls(
416
+ id=data["id"],
417
+ name=data["name"],
418
+ node_type=NodeType(data["node_type"]),
419
+ status=Status(data.get("status", Status.UNKNOWN.value)),
420
+ confidence_score=float(data.get("confidence_score", 0.0)),
421
+ evidence_ids=data.get("evidence_ids", []),
422
+ metadata=data.get("metadata", {})
423
+ )
424
+
425
+
426
+ @dataclass
427
+ class GraphEdge:
428
+ """Directed edge in the Canonical State Graph."""
429
+ source_id: str
430
+ target_id: str
431
+ relation: RelationType
432
+
433
+ def to_dict(self) -> Dict[str, Any]:
434
+ return {
435
+ "source_id": self.source_id,
436
+ "target_id": self.target_id,
437
+ "relation": self.relation.value
438
+ }
439
+
440
+ @classmethod
441
+ def from_dict(cls, data: Dict[str, Any]) -> "GraphEdge":
442
+ return cls(
443
+ source_id=data["source_id"],
444
+ target_id=data["target_id"],
445
+ relation=RelationType(data["relation"])
446
+ )
447
+
448
+
449
+ # ==============================================================================
450
+ # AGGREGATE CANONICAL PROJECT STATE (ROOT CONTAINER)
451
+ # ==============================================================================
452
+
453
+ @dataclass
454
+ class CanonicalProjectState:
455
+ """
456
+ Root container for the complete verified state of Project Continuum.
457
+ Preserves versioning, strict state isolation, the canonical graph,
458
+ contradictions, and all referenced evidence.
459
+ """
460
+ schema_version: str = "1.0.0"
461
+ project_id: str = field(default_factory=lambda: f"proj_{uuid.uuid4().hex[:8]}")
462
+ created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
463
+ updated_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
464
+
465
+ # The 3 segregated states:
466
+ project_state: ProjectState = field(default_factory=lambda: ProjectState(root_path=""))
467
+ conversational_state: ConversationalState = field(default_factory=ConversationalState)
468
+ agent_execution_state: AgentExecutionState = field(default_factory=AgentExecutionState)
469
+
470
+ # Global evidence pool indexed by evidence.id:
471
+ evidence_pool: Dict[str, Evidence] = field(default_factory=dict)
472
+
473
+ # Canonical State Graph (DAG):
474
+ graph_nodes: Dict[str, GraphNode] = field(default_factory=dict)
475
+ graph_edges: List[GraphEdge] = field(default_factory=list)
476
+
477
+ # Discrepancies and Contradictions Ledger:
478
+ contradictions: List[ContradictionRecord] = field(default_factory=list)
479
+
480
+ def add_evidence(self, evidence: Evidence) -> str:
481
+ """Registers a piece of evidence in the global pool and returns its ID."""
482
+ self.evidence_pool[evidence.id] = evidence
483
+ return evidence.id
484
+
485
+ def get_evidence(self, evidence_id: str) -> Optional[Evidence]:
486
+ return self.evidence_pool.get(evidence_id)
487
+
488
+ def to_dict(self) -> Dict[str, Any]:
489
+ return {
490
+ "schema_version": self.schema_version,
491
+ "project_id": self.project_id,
492
+ "created_at": self.created_at,
493
+ "updated_at": self.updated_at,
494
+ "project_state": self.project_state.to_dict(),
495
+ "conversational_state": self.conversational_state.to_dict(),
496
+ "agent_execution_state": self.agent_execution_state.to_dict(),
497
+ "evidence_pool": {k: v.to_dict() for k, v in self.evidence_pool.items()},
498
+ "graph_nodes": {k: v.to_dict() for k, v in self.graph_nodes.items()},
499
+ "graph_edges": [e.to_dict() for e in self.graph_edges],
500
+ "contradictions": [c.to_dict() for c in self.contradictions]
501
+ }
502
+
503
+ @classmethod
504
+ def from_dict(cls, data: Dict[str, Any]) -> "CanonicalProjectState":
505
+ ev_pool = {
506
+ k: Evidence.from_dict(v) for k, v in data.get("evidence_pool", {}).items()
507
+ }
508
+ g_nodes = {
509
+ k: GraphNode.from_dict(v) for k, v in data.get("graph_nodes", {}).items()
510
+ }
511
+ g_edges = [
512
+ GraphEdge.from_dict(e) for e in data.get("graph_edges", [])
513
+ ]
514
+ contradictions = [
515
+ ContradictionRecord.from_dict(c) for c in data.get("contradictions", [])
516
+ ]
517
+
518
+ return cls(
519
+ schema_version=data.get("schema_version", "1.0.0"),
520
+ project_id=data.get("project_id", f"proj_{uuid.uuid4().hex[:8]}"),
521
+ created_at=data.get("created_at", datetime.now(timezone.utc).isoformat()),
522
+ updated_at=data.get("updated_at", datetime.now(timezone.utc).isoformat()),
523
+ project_state=ProjectState.from_dict(data.get("project_state", {})),
524
+ conversational_state=ConversationalState.from_dict(data.get("conversational_state", {})),
525
+ agent_execution_state=AgentExecutionState.from_dict(data.get("agent_execution_state", {})),
526
+ evidence_pool=ev_pool,
527
+ graph_nodes=g_nodes,
528
+ graph_edges=g_edges,
529
+ contradictions=contradictions
530
+ )
daemon/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ """
2
+ Project Continuum - Daemon Package Exports
3
+ ==========================================
4
+ Milestone 7 - Phase 18: Continuum Daemon & CLI.
5
+ """
6
+
7
+ from daemon.service import ContinuumDaemon, DaemonStatus
8
+
9
+ __all__ = [
10
+ "ContinuumDaemon",
11
+ "DaemonStatus",
12
+ ]