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
@@ -0,0 +1,442 @@
1
+ """
2
+ Project Continuum - Contradiction Detection Engine
3
+ ==================================================
4
+ Milestone 3 - Phase 7: Automated Discrepancy Identification.
5
+ Detects mismatches between:
6
+ 1. Conversational claims vs Missing physical AST/file implementations (HIGH).
7
+ 2. Conversational claims vs Failing automated test results (HIGH).
8
+ 3. Documentation references vs Missing code symbols/types (MEDIUM).
9
+ 4. Manifest dependencies vs Missing lockfiles/imports (MEDIUM).
10
+ 5. Conversational claims of clean state vs Dirty Git working trees (LOW/MEDIUM).
11
+ """
12
+
13
+ from datetime import datetime, timezone
14
+ import re
15
+ from typing import Any, Dict, List, Optional, Set
16
+ import uuid
17
+
18
+ from core.enums import EvidenceLevel, EvidenceType, Status
19
+ from core.evidence import Evidence
20
+ from core.interfaces import IContradictionDetector
21
+ from core.state_models import (
22
+ AgentClaim,
23
+ AstSymbol,
24
+ CanonicalProjectState,
25
+ ContradictionRecord,
26
+ ConversationalState,
27
+ GitState,
28
+ ProjectState,
29
+ TestResult,
30
+ )
31
+ from contradictions.models import ContradictionSeverity, ContradictionType, DiscrepancyLedger
32
+
33
+
34
+ class ContradictionDetector(IContradictionDetector):
35
+ """
36
+ Identifies and records discrepancies across ProjectState, ConversationalState,
37
+ and the global Evidence pool.
38
+ """
39
+
40
+ COMPLETION_PATTERNS = [
41
+ re.compile(r"\b(completed?|implemented?|finished?|done|created|built|ready)\b", re.IGNORECASE),
42
+ re.compile(r"\b(working|passes?|passed|passing|verified)\b", re.IGNORECASE),
43
+ re.compile(r"\b(all tests (pass|passed|are passing))\b", re.IGNORECASE),
44
+ re.compile(r"\b(100%|completely|fully implemented)\b", re.IGNORECASE),
45
+ ]
46
+
47
+ DOC_SYMBOL_PATTERN = re.compile(r"`([A-Za-z_][A-Za-z0-9_]{2,})`|\bclass\s+([A-Za-z_][A-Za-z0-9_]+)\b|\bdef\s+([A-Za-z_][A-Za-z0-9_]+)\b")
48
+
49
+ def detect_contradictions(
50
+ self,
51
+ project_state: ProjectState,
52
+ conversational_state: ConversationalState,
53
+ evidence_pool: Dict[str, Evidence]
54
+ ) -> List[ContradictionRecord]:
55
+ """
56
+ Analyzes all state dimensions and returns structured ContradictionRecord items.
57
+ Implements IContradictionDetector Protocol.
58
+ """
59
+ contradictions: List[ContradictionRecord] = []
60
+
61
+ # 1. Detect claims of completion vs missing implementation (AST / Files)
62
+ claim_impl_contradictions = self._detect_claims_vs_missing_implementation(
63
+ conversational_state.agent_claims,
64
+ project_state.symbols,
65
+ project_state.files,
66
+ evidence_pool
67
+ )
68
+ contradictions.extend(claim_impl_contradictions)
69
+
70
+ # 2. Detect claims of passing tests vs failing test results
71
+ claim_test_contradictions = self._detect_claims_vs_failing_tests(
72
+ conversational_state.agent_claims,
73
+ project_state.test_results,
74
+ evidence_pool
75
+ )
76
+ contradictions.extend(claim_test_contradictions)
77
+
78
+ # 3. Detect documentation referencing non-existent code symbols
79
+ doc_symbol_contradictions = self._detect_documentation_vs_missing_symbols(
80
+ evidence_pool,
81
+ project_state.symbols
82
+ )
83
+ contradictions.extend(doc_symbol_contradictions)
84
+
85
+ # 4. Detect claims of clean repository vs dirty Git state
86
+ git_contradictions = self._detect_stale_git_state(
87
+ conversational_state.agent_claims,
88
+ project_state.git_state,
89
+ evidence_pool
90
+ )
91
+ contradictions.extend(git_contradictions)
92
+
93
+ # 5. Detect misleading commit messages claiming non-existent implementations
94
+ commit_contradictions = self._detect_misleading_commits(
95
+ project_state.git_state,
96
+ project_state.symbols,
97
+ project_state.files
98
+ )
99
+ contradictions.extend(commit_contradictions)
100
+
101
+ return contradictions
102
+
103
+ def _detect_claims_vs_missing_implementation(
104
+ self,
105
+ claims: List[AgentClaim],
106
+ symbols: List[AstSymbol],
107
+ files: List[str],
108
+ evidence_pool: Dict[str, Evidence]
109
+ ) -> List[ContradictionRecord]:
110
+ """
111
+ Detects claims asserting a component, service, or function is complete/implemented
112
+ when no matching AST symbol or file exists in the physical workspace.
113
+ """
114
+ records: List[ContradictionRecord] = []
115
+ symbol_names = {s.name.lower() for s in symbols}
116
+ file_names = {f.replace("\\", "/").lower() for f in files}
117
+
118
+ # If claim is specifically about test pass/fail, avoid duplicate missing-impl contradiction
119
+ test_assertion_pattern = re.compile(r"\b(tests? (pass|passed|passing)|all tests pass)\b", re.IGNORECASE)
120
+
121
+ for claim in claims:
122
+ target = claim.target_component or ""
123
+ claim_text = claim.claim_text.strip()
124
+
125
+ # If claim is strictly a test execution claim, let _detect_claims_vs_failing_tests handle it
126
+ if test_assertion_pattern.search(claim_text) and not any(k in claim_text.lower() for k in ["implemented", "complete", "finished", "created", "built"]):
127
+ continue
128
+
129
+ is_completion_claim = (
130
+ claim.claimed_status == Status.VERIFIED
131
+ or any(p.search(claim_text) for p in self.COMPLETION_PATTERNS)
132
+ )
133
+
134
+ if not is_completion_claim:
135
+ continue
136
+
137
+ # Extract possible component identifiers from claim_text or target_component
138
+ candidates = self._extract_identifier_candidates(target, claim_text)
139
+ if not candidates:
140
+ continue
141
+
142
+ # Check if any candidate exists in AST symbols or file paths
143
+ has_match = False
144
+ for cand in candidates:
145
+ cand_lower = cand.lower()
146
+ if cand_lower in symbol_names or any(cand_lower in s for s in symbol_names):
147
+ has_match = True
148
+ break
149
+ if any(cand_lower in f for f in file_names):
150
+ has_match = True
151
+ break
152
+
153
+ if not has_match:
154
+ record = ContradictionRecord(
155
+ id=f"contra_{uuid.uuid4().hex[:8]}",
156
+ severity=ContradictionSeverity.HIGH.value,
157
+ claim_id=claim.id,
158
+ claim_text=claim_text,
159
+ physical_evidence_id=None,
160
+ explanation=(
161
+ f"Agent claimed '{claim_text}' (target: '{target or candidates[0]}'), "
162
+ f"but no matching AST symbols ({', '.join(candidates[:3])}) or source files "
163
+ f"exist in the workspace."
164
+ ),
165
+ detected_at=datetime.now(timezone.utc).isoformat(),
166
+ resolved=False
167
+ )
168
+ records.append(record)
169
+
170
+ return records
171
+
172
+ def _detect_claims_vs_failing_tests(
173
+ self,
174
+ claims: List[AgentClaim],
175
+ test_results: List[TestResult],
176
+ evidence_pool: Dict[str, Evidence]
177
+ ) -> List[ContradictionRecord]:
178
+ """
179
+ Detects claims asserting tests pass or features are verified when actual
180
+ recorded test executions failed (exit code != 0).
181
+ """
182
+ records: List[ContradictionRecord] = []
183
+ failing_tests = [t for t in test_results if t.status == Status.FAILED or t.exit_code != 0]
184
+
185
+ # Also check evidence pool for Level 1 test runs
186
+ failing_test_ev = [
187
+ ev for ev in evidence_pool.values()
188
+ if ev.type == EvidenceType.TEST_RUN and ev.raw_payload.get("exit_code", 0) != 0
189
+ ]
190
+
191
+ if not failing_tests and not failing_test_ev:
192
+ return records
193
+
194
+ test_pass_regex = re.compile(r"\b(tests? (pass|passed|passing|green|succeeded)|all tests|100% pass|verified)\b", re.IGNORECASE)
195
+
196
+ for claim in claims:
197
+ claim_text = claim.claim_text
198
+ target = (claim.target_component or "").lower()
199
+
200
+ claims_passing = (
201
+ bool(test_pass_regex.search(claim_text))
202
+ or claim.claimed_status == Status.VERIFIED
203
+ )
204
+
205
+ if not claims_passing:
206
+ continue
207
+
208
+ # Identify matching failing test
209
+ matching_failing_test = None
210
+ for ft in failing_tests:
211
+ if not target or target in ft.name.lower() or target in ft.suite.lower() or "test" in claim_text.lower():
212
+ matching_failing_test = ft
213
+ break
214
+
215
+ matching_ev = None
216
+ for ev in failing_test_ev:
217
+ if not target or target in ev.summary.lower() or target in str(ev.raw_payload).lower() or "test" in claim_text.lower():
218
+ matching_ev = ev
219
+ break
220
+
221
+ if matching_failing_test or matching_ev:
222
+ ev_id = matching_failing_test.evidence_id if matching_failing_test else (matching_ev.id if matching_ev else None)
223
+ fail_detail = (
224
+ f"test '{matching_failing_test.name}' failed with exit code {matching_failing_test.exit_code}"
225
+ if matching_failing_test
226
+ else f"test suite '{matching_ev.summary}' failed (exit code {matching_ev.raw_payload.get('exit_code')})"
227
+ )
228
+
229
+ record = ContradictionRecord(
230
+ id=f"contra_{uuid.uuid4().hex[:8]}",
231
+ severity=ContradictionSeverity.HIGH.value,
232
+ claim_id=claim.id,
233
+ claim_text=claim_text,
234
+ physical_evidence_id=ev_id,
235
+ explanation=(
236
+ f"Agent claimed '{claim_text}', but physical verification recorded a failure: {fail_detail}."
237
+ ),
238
+ detected_at=datetime.now(timezone.utc).isoformat(),
239
+ resolved=False
240
+ )
241
+ records.append(record)
242
+
243
+ return records
244
+
245
+ def _detect_documentation_vs_missing_symbols(
246
+ self,
247
+ evidence_pool: Dict[str, Evidence],
248
+ symbols: List[AstSymbol]
249
+ ) -> List[ContradictionRecord]:
250
+ """
251
+ Detects documentation evidence (markdown, comments) referencing classes or functions
252
+ that do not exist anywhere in the extracted AST symbol table.
253
+ """
254
+ records: List[ContradictionRecord] = []
255
+ symbol_names = {s.name for s in symbols}
256
+ doc_evidences = [
257
+ ev for ev in evidence_pool.values()
258
+ if ev.level == EvidenceLevel.LEVEL_4_DOCUMENTATION or ev.type == EvidenceType.DOCUMENTATION
259
+ ]
260
+
261
+ # Standard built-in names to ignore
262
+ BUILTIN_IGNORE = {
263
+ "str", "int", "float", "bool", "dict", "list", "set", "tuple", "None", "Optional",
264
+ "Any", "List", "Dict", "Set", "Tuple", "Union", "Protocol", "Enum", "True", "False",
265
+ "self", "cls", "return", "import", "from", "class", "def", "async", "await",
266
+ "JSON", "HTTP", "REST", "API", "URL", "UUID", "SHA", "README", "TODO", "FIXME",
267
+ "GET", "POST", "PUT", "DELETE", "UTF-8", "ASCII", "Linux", "Windows", "MacOS"
268
+ }
269
+
270
+ for ev in doc_evidences:
271
+ content = str(ev.raw_payload.get("content", "")) or ev.summary
272
+ referenced_identifiers: Set[str] = set()
273
+
274
+ for match in self.DOC_SYMBOL_PATTERN.finditer(content):
275
+ for group in match.groups():
276
+ if group and len(group) > 3 and group not in BUILTIN_IGNORE:
277
+ referenced_identifiers.add(group)
278
+
279
+ missing_symbols = [
280
+ ident for ident in referenced_identifiers
281
+ if ident not in symbol_names and not any(ident.lower() == s.lower() for s in symbol_names)
282
+ ]
283
+
284
+ # Filter out likely non-code nouns (keep CamelCase and snake_case with underscores)
285
+ suspicious_missing = [
286
+ m for m in missing_symbols
287
+ if ("_" in m or (m[0].isupper() and any(c.islower() for c in m[1:])))
288
+ ]
289
+
290
+ if suspicious_missing:
291
+ record = ContradictionRecord(
292
+ id=f"contra_{uuid.uuid4().hex[:8]}",
293
+ severity=ContradictionSeverity.MEDIUM.value,
294
+ claim_id=None,
295
+ claim_text=f"Documentation in '{ev.summary}'",
296
+ physical_evidence_id=ev.id,
297
+ explanation=(
298
+ f"Documentation ({ev.summary}) references code symbol(s) '{', '.join(suspicious_missing[:3])}' "
299
+ f"which are absent from physical AST symbols."
300
+ ),
301
+ detected_at=datetime.now(timezone.utc).isoformat(),
302
+ resolved=False
303
+ )
304
+ records.append(record)
305
+
306
+ return records
307
+
308
+ def _detect_stale_git_state(
309
+ self,
310
+ claims: List[AgentClaim],
311
+ git_state: GitState,
312
+ evidence_pool: Dict[str, Evidence]
313
+ ) -> List[ContradictionRecord]:
314
+ """
315
+ Detects claims asserting the workspace is committed, clean, or ready for release
316
+ when Git state is dirty or has untracked / uncommitted changes.
317
+ """
318
+ records: List[ContradictionRecord] = []
319
+ if not git_state.is_repo or not git_state.is_dirty:
320
+ return records
321
+
322
+ clean_claim_regex = re.compile(r"\b(committed|all changes committed|clean working tree|ready to ship|pushed)\b", re.IGNORECASE)
323
+
324
+ for claim in claims:
325
+ if clean_claim_regex.search(claim.claim_text):
326
+ dirty_summary = f"{len(git_state.staged_files)} staged, {len(git_state.unstaged_files)} unstaged, {len(git_state.untracked_files)} untracked files"
327
+ record = ContradictionRecord(
328
+ id=f"contra_{uuid.uuid4().hex[:8]}",
329
+ severity=ContradictionSeverity.MEDIUM.value,
330
+ claim_id=claim.id,
331
+ claim_text=claim.claim_text,
332
+ physical_evidence_id=git_state.evidence_ids[0] if git_state.evidence_ids else None,
333
+ explanation=(
334
+ f"Agent claimed '{claim.claim_text}', but physical Git repository is DIRTY ({dirty_summary})."
335
+ ),
336
+ detected_at=datetime.now(timezone.utc).isoformat(),
337
+ resolved=False
338
+ )
339
+ records.append(record)
340
+
341
+ return records
342
+
343
+ def _detect_misleading_commits(
344
+ self,
345
+ git_state: GitState,
346
+ symbols: List[AstSymbol],
347
+ files: List[str]
348
+ ) -> List[ContradictionRecord]:
349
+ """
350
+ Detects commits with messages claiming to have added/implemented components
351
+ that do not actually exist in the physical AST or file system.
352
+ """
353
+ records: List[ContradictionRecord] = []
354
+ if not git_state.is_repo or not git_state.recent_commits:
355
+ return records
356
+
357
+ symbol_names = {s.name.lower() for s in symbols}
358
+ file_names = {f.replace("\\", "/").lower() for f in files}
359
+
360
+ for commit in git_state.recent_commits:
361
+ msg = commit.get("message", "") or commit.get("summary", "")
362
+ if not msg:
363
+ continue
364
+
365
+ candidates = self._extract_identifier_candidates("", msg)
366
+ if not candidates:
367
+ continue
368
+
369
+ # Check if this commit claims creation/implementation of a specific component
370
+ if any(p.search(msg) for p in self.COMPLETION_PATTERNS) or msg.lower().startswith(("feat:", "add ", "implement ")):
371
+ for cand in candidates:
372
+ cand_lower = cand.lower()
373
+ if len(cand) < 4 or cand_lower in {"test", "tests", "code", "file", "feat", "docs", "fix"}:
374
+ continue
375
+ has_match = (
376
+ cand_lower in symbol_names
377
+ or any(cand_lower in s for s in symbol_names)
378
+ or any(cand_lower in f for f in file_names)
379
+ )
380
+ if not has_match:
381
+ records.append(ContradictionRecord(
382
+ id=f"contra_{uuid.uuid4().hex[:8]}",
383
+ severity=ContradictionSeverity.MEDIUM.value,
384
+ claim_id=None,
385
+ claim_text=f"Git commit '{commit.get('hash', 'head')[:7]}': {msg}",
386
+ physical_evidence_id=git_state.evidence_ids[0] if git_state.evidence_ids else None,
387
+ explanation=(
388
+ f"Git commit message '{msg}' implies implementation of '{cand}', "
389
+ f"but no matching AST symbol or source file exists in the repository."
390
+ ),
391
+ detected_at=datetime.now(timezone.utc).isoformat(),
392
+ resolved=False
393
+ ))
394
+
395
+ return records
396
+
397
+ def _extract_identifier_candidates(self, target: str, text: str) -> List[str]:
398
+ """Extracts potential class, function, or module names from claim text."""
399
+ candidates = []
400
+ if target:
401
+ candidates.append(target)
402
+
403
+ # Look for words in quotes or CamelCase or snake_case
404
+ quoted = re.findall(r"['\"`]([A-Za-z0-9_\-\.]+)['\"`]", text)
405
+ candidates.extend(quoted)
406
+
407
+ # Look for explicit CamelCase identifiers (e.g. KafkaEventStreamingProducer)
408
+ camel_cases = re.findall(r"\b([A-Z][a-z0-9]+(?:[A-Z][a-z0-9]+)+)\b", text)
409
+ candidates.extend(camel_cases)
410
+
411
+ # Look for targets following feat:, add, implement, create
412
+ verb_match = re.findall(r"\b(?:implement|add|create|feat:)\s+([A-Za-z0-9_]+)\b", text, re.IGNORECASE)
413
+ candidates.extend(verb_match)
414
+
415
+ # Look for tokens before keywords like "service", "handler", "manager", "parser", "client", "controller"
416
+ comp_match = re.findall(r"\b([A-Za-z0-9_]+(?:\s+[A-Za-z0-9_]+)?\s+(?:service|handler|manager|parser|client|controller|engine|module|endpoint|function|producer|consumer))\b", text, re.IGNORECASE)
417
+ for m in comp_match:
418
+ cleaned = m.replace(" ", "_").lower()
419
+ candidates.append(cleaned)
420
+ candidates.append(m)
421
+
422
+ # Strip duplicates while preserving order
423
+ unique: List[str] = []
424
+ for c in candidates:
425
+ if c and c not in unique:
426
+ unique.append(c)
427
+
428
+ return unique
429
+
430
+ def audit_canonical_state(self, canonical_state: CanonicalProjectState) -> DiscrepancyLedger:
431
+ """
432
+ Executes full contradiction audit on a CanonicalProjectState,
433
+ populates its discrepancies ledger, and returns the DiscrepancyLedger.
434
+ """
435
+ detected = self.detect_contradictions(
436
+ project_state=canonical_state.project_state,
437
+ conversational_state=canonical_state.conversational_state,
438
+ evidence_pool=canonical_state.evidence_pool
439
+ )
440
+
441
+ canonical_state.contradictions = detected
442
+ return DiscrepancyLedger(records=detected)
@@ -0,0 +1,75 @@
1
+ """
2
+ Project Continuum - Contradiction & Discrepancy Models
3
+ ======================================================
4
+ Milestone 3 - Phase 7: Contradiction Detection Engine.
5
+ Defines contradiction categories, discrepancy severities, and the discrepancy ledger.
6
+ """
7
+
8
+ from dataclasses import dataclass, field, asdict
9
+ from datetime import datetime, timezone
10
+ from enum import Enum
11
+ from typing import Any, Dict, List, Optional
12
+ import uuid
13
+
14
+ from core.state_models import ContradictionRecord
15
+
16
+
17
+ class ContradictionType(str, Enum):
18
+ """Categories of discrepancies identified across Continuum's state boundaries."""
19
+ CLAIM_VS_MISSING_IMPLEMENTATION = "claim_vs_missing_implementation"
20
+ CLAIM_VS_FAILING_TEST = "claim_vs_failing_test"
21
+ DOC_VS_MISSING_SYMBOL = "doc_vs_missing_symbol"
22
+ INCOMPLETE_DEPENDENCY = "incomplete_dependency"
23
+ STALE_PROJECT_INFO = "stale_project_info"
24
+ UNVERIFIED_CLAIM = "unverified_claim"
25
+
26
+
27
+ class ContradictionSeverity(str, Enum):
28
+ """Severity levels for detected contradictions."""
29
+ HIGH = "HIGH" # Direct falsehood (e.g. claiming tests pass when they fail)
30
+ MEDIUM = "MEDIUM" # Significant drift (e.g. docs reference non-existent symbols)
31
+ LOW = "LOW" # Minor divergence (e.g. uncommitted changes after claiming done)
32
+
33
+
34
+ @dataclass
35
+ class DiscrepancyLedger:
36
+ """
37
+ Maintains an audit ledger of all unresolved and resolved discrepancies.
38
+ Ensures complete transparency for developers and subsequent AI agents.
39
+ """
40
+ records: List[ContradictionRecord] = field(default_factory=list)
41
+
42
+ def add_record(self, record: ContradictionRecord) -> None:
43
+ """Appends a new contradiction record if not already present."""
44
+ if not any(r.id == record.id for r in self.records):
45
+ self.records.append(record)
46
+
47
+ def get_unresolved(self) -> List[ContradictionRecord]:
48
+ """Returns all currently unresolved contradictions."""
49
+ return [r for r in self.records if not r.resolved]
50
+
51
+ def get_by_severity(self, severity: str) -> List[ContradictionRecord]:
52
+ """Filters contradictions by severity (HIGH, MEDIUM, LOW)."""
53
+ return [r for r in self.records if r.severity == severity]
54
+
55
+ def resolve_record(self, record_id: str) -> bool:
56
+ """Marks a contradiction as resolved."""
57
+ for r in self.records:
58
+ if r.id == record_id:
59
+ r.resolved = True
60
+ return True
61
+ return False
62
+
63
+ def to_dict(self) -> Dict[str, Any]:
64
+ return {
65
+ "unresolved_count": len(self.get_unresolved()),
66
+ "high_severity_count": len(self.get_by_severity("HIGH")),
67
+ "medium_severity_count": len(self.get_by_severity("MEDIUM")),
68
+ "low_severity_count": len(self.get_by_severity("LOW")),
69
+ "records": [r.to_dict() for r in self.records]
70
+ }
71
+
72
+ @classmethod
73
+ def from_dict(cls, data: Dict[str, Any]) -> "DiscrepancyLedger":
74
+ records = [ContradictionRecord.from_dict(r) for r in data.get("records", [])]
75
+ return cls(records=records)
core/__init__.py ADDED
@@ -0,0 +1,97 @@
1
+ """
2
+ Project Continuum - Core Package Facade
3
+ ======================================
4
+ Exposes primary models, enums, interfaces, and serializers.
5
+ """
6
+
7
+ from core.enums import (
8
+ Status,
9
+ EvidenceType,
10
+ EvidenceLevel,
11
+ NodeType,
12
+ RelationType,
13
+ TargetModel,
14
+ )
15
+ from core.evidence import (
16
+ Evidence,
17
+ EvidenceProvenance,
18
+ )
19
+ from core.state_models import (
20
+ ProjectState,
21
+ ConversationalState,
22
+ AgentExecutionState,
23
+ CanonicalProjectState,
24
+ AstSymbol,
25
+ TestResult,
26
+ GitState,
27
+ ManifestInfo,
28
+ Requirement,
29
+ ArchitecturalDecision,
30
+ AgentClaim,
31
+ UnresolvedQuestion,
32
+ TacticalTask,
33
+ NextActionRecommendation,
34
+ ContradictionRecord,
35
+ GraphNode,
36
+ GraphEdge,
37
+ )
38
+ from core.interfaces import (
39
+ IEvidenceExtractor,
40
+ IEvidenceResolver,
41
+ IContradictionDetector,
42
+ IConfidenceCalculator,
43
+ IStateGraphManager,
44
+ IContextSelector,
45
+ IModelAdapter,
46
+ )
47
+ from core.schema import (
48
+ CANONICAL_STATE_SCHEMA_VERSION,
49
+ get_canonical_project_state_schema,
50
+ validate_canonical_state_dict,
51
+ )
52
+ from core.serializer import (
53
+ CanonicalStateSerializer,
54
+ StateSerializationError,
55
+ StateValidationError,
56
+ )
57
+
58
+ __all__ = [
59
+ "Status",
60
+ "EvidenceType",
61
+ "EvidenceLevel",
62
+ "NodeType",
63
+ "RelationType",
64
+ "TargetModel",
65
+ "Evidence",
66
+ "EvidenceProvenance",
67
+ "ProjectState",
68
+ "ConversationalState",
69
+ "AgentExecutionState",
70
+ "CanonicalProjectState",
71
+ "AstSymbol",
72
+ "TestResult",
73
+ "GitState",
74
+ "ManifestInfo",
75
+ "Requirement",
76
+ "ArchitecturalDecision",
77
+ "AgentClaim",
78
+ "UnresolvedQuestion",
79
+ "TacticalTask",
80
+ "NextActionRecommendation",
81
+ "ContradictionRecord",
82
+ "GraphNode",
83
+ "GraphEdge",
84
+ "IEvidenceExtractor",
85
+ "IEvidenceResolver",
86
+ "IContradictionDetector",
87
+ "IConfidenceCalculator",
88
+ "IStateGraphManager",
89
+ "IContextSelector",
90
+ "IModelAdapter",
91
+ "CANONICAL_STATE_SCHEMA_VERSION",
92
+ "get_canonical_project_state_schema",
93
+ "validate_canonical_state_dict",
94
+ "CanonicalStateSerializer",
95
+ "StateSerializationError",
96
+ "StateValidationError",
97
+ ]