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,308 @@
1
+ """
2
+ Project Continuum - Confidence Calculation Engine
3
+ =================================================
4
+ Milestone 3 - Phase 8: Confidence Calculation & Status Evaluation.
5
+ Calculates explainable confidence metrics (0.0% to 100.0%) grounded in physical proof
6
+ and governed by contradiction caps and hierarchy constraints.
7
+ """
8
+
9
+ from typing import Any, Dict, List, Optional, Set, Tuple
10
+
11
+ from core.enums import EvidenceLevel, EvidenceType, NodeType, Status
12
+ from core.evidence import Evidence
13
+ from core.interfaces import IConfidenceCalculator
14
+ from core.state_models import (
15
+ CanonicalProjectState,
16
+ ContradictionRecord,
17
+ GraphNode,
18
+ ProjectState,
19
+ )
20
+ from confidence.models import ConfidenceBreakdown
21
+
22
+
23
+ class ConfidenceCalculator(IConfidenceCalculator):
24
+ """
25
+ Computes grounded confidence scores for nodes, components, tasks, and entire projects.
26
+
27
+ Scoring Dimensions:
28
+ - Implementation Presence (Level 2): 0 to 35 pts
29
+ - Test Existence: 0 to 15 pts
30
+ - Test Execution & Passing Status (Level 1): 0 to 30 pts
31
+ - Git Working Tree Consistency (Level 3): 0 to 10 pts
32
+ - Documentation Presence (Level 4): 0 to 10 pts
33
+
34
+ Hard Upper Caps:
35
+ - High-Severity Contradiction on target: Capped at MAX 20.0%
36
+ - Medium-Severity Contradiction on target: Capped at MAX 50.0%
37
+ - Conversational Claim only (0 physical evidence): Capped at MAX 15.0%
38
+ """
39
+
40
+ def calculate_node_confidence(
41
+ self,
42
+ node: GraphNode,
43
+ evidence_pool: Dict[str, Evidence],
44
+ contradictions: List[ContradictionRecord]
45
+ ) -> Tuple[float, Dict[str, Any]]:
46
+ """
47
+ Calculates confidence score for a GraphNode.
48
+ Implements IConfidenceCalculator Protocol.
49
+ """
50
+ # Gather relevant evidence for this node
51
+ node_evidence: List[Evidence] = []
52
+ for ev_id in node.evidence_ids:
53
+ if ev_id in evidence_pool:
54
+ node_evidence.append(evidence_pool[ev_id])
55
+
56
+ # Also search pool if evidence_ids was empty
57
+ if not node_evidence:
58
+ node_name_lower = node.name.lower()
59
+ for ev in evidence_pool.values():
60
+ if node_name_lower in ev.summary.lower() or node_name_lower in str(ev.raw_payload).lower():
61
+ node_evidence.append(ev)
62
+
63
+ breakdown = self.evaluate_component_confidence(
64
+ target_id=node.id,
65
+ target_name=node.name,
66
+ evidence_list=node_evidence,
67
+ contradictions=contradictions
68
+ )
69
+
70
+ node.confidence_score = breakdown.final_score
71
+ return breakdown.final_score, breakdown.to_dict()
72
+
73
+ def evaluate_component_confidence(
74
+ self,
75
+ target_id: str,
76
+ target_name: str,
77
+ evidence_list: List[Evidence],
78
+ contradictions: List[ContradictionRecord]
79
+ ) -> ConfidenceBreakdown:
80
+ """
81
+ Evaluates multi-dimensional confidence breakdown for any target identifier/name.
82
+ """
83
+ breakdown = ConfidenceBreakdown(target_id=target_id)
84
+ breakdown.contributing_evidence_ids = [e.id for e in evidence_list]
85
+
86
+ if not evidence_list:
87
+ # Check if there are active contradictions
88
+ relevant_contradictions = self._get_relevant_contradictions(target_id, target_name, contradictions)
89
+ breakdown.active_contradiction_ids = [c.id for c in relevant_contradictions]
90
+ breakdown.final_score = 0.0
91
+ breakdown.explanation = f"Target '{target_name or target_id}' has zero evidence. Confidence: 0.0%."
92
+ return breakdown
93
+
94
+ # Check for pure conversational evidence
95
+ has_physical_evidence = any(
96
+ e.level in {EvidenceLevel.LEVEL_1_RUNTIME_TEST, EvidenceLevel.LEVEL_2_CODE_AST, EvidenceLevel.LEVEL_3_GIT_STATE}
97
+ for e in evidence_list
98
+ )
99
+
100
+ # 1. Implementation Presence (Max 35)
101
+ for ev in evidence_list:
102
+ if ev.type in {EvidenceType.SOURCE_CODE, EvidenceType.AST_SYMBOL}:
103
+ # If valid AST without syntax errors
104
+ if not ev.raw_payload.get("errors"):
105
+ breakdown.implementation_presence = 35.0
106
+ else:
107
+ breakdown.implementation_presence = 15.0 # Partial due to errors
108
+ elif ev.type == EvidenceType.CONFIG_FILE:
109
+ breakdown.implementation_presence = max(breakdown.implementation_presence, 25.0)
110
+
111
+ # 2. Test Existence (Max 15)
112
+ has_tests = any(
113
+ ev.type == EvidenceType.TEST_RUN or "test" in ev.summary.lower() or "test" in ev.raw_payload.get("file_path", "").lower()
114
+ for ev in evidence_list
115
+ )
116
+ if has_tests:
117
+ breakdown.test_existence = 15.0
118
+
119
+ # 3. Test Execution Pass (Max 30)
120
+ for ev in evidence_list:
121
+ if ev.type in {EvidenceType.TEST_RUN, EvidenceType.BUILD_LOG, EvidenceType.RUNTIME_LOG}:
122
+ exit_code = ev.raw_payload.get("exit_code", 0)
123
+ if exit_code == 0:
124
+ breakdown.test_execution_pass = 30.0
125
+ else:
126
+ breakdown.test_execution_pass = 0.0
127
+ breakdown.test_existence = 15.0 # Test exists but failed
128
+
129
+ # 4. Git Consistency (Max 10)
130
+ for ev in evidence_list:
131
+ if ev.type == EvidenceType.GIT_STATUS:
132
+ is_dirty = ev.raw_payload.get("is_dirty", False)
133
+ if not is_dirty:
134
+ breakdown.git_consistency = 10.0
135
+ else:
136
+ breakdown.git_consistency = 4.0
137
+ elif ev.type in {EvidenceType.GIT_COMMIT, EvidenceType.GIT_DIFF}:
138
+ breakdown.git_consistency = max(breakdown.git_consistency, 8.0)
139
+
140
+ # 5. Documentation Presence (Max 10)
141
+ for ev in evidence_list:
142
+ if ev.type == EvidenceType.DOCUMENTATION or ev.level == EvidenceLevel.LEVEL_4_DOCUMENTATION:
143
+ breakdown.documentation_presence = 10.0
144
+
145
+ # Sum raw score
146
+ raw = (
147
+ breakdown.implementation_presence
148
+ + breakdown.test_existence
149
+ + breakdown.test_execution_pass
150
+ + breakdown.git_consistency
151
+ + breakdown.documentation_presence
152
+ )
153
+ breakdown.raw_score = raw
154
+
155
+ # 6. Apply Contradiction Penalties and Hard Caps
156
+ relevant_contradictions = self._get_relevant_contradictions(target_id, target_name, contradictions)
157
+ breakdown.active_contradiction_ids = [c.id for c in relevant_contradictions]
158
+
159
+ score = raw
160
+ cap: Optional[float] = None
161
+
162
+ if not has_physical_evidence:
163
+ # Pure conversational claims without physical grounding capped at 15.0%
164
+ cap = 15.0
165
+ breakdown.contradiction_penalties += 20.0
166
+
167
+ for contra in relevant_contradictions:
168
+ if contra.severity == "HIGH":
169
+ cap = min(cap, 20.0) if cap is not None else 20.0
170
+ breakdown.contradiction_penalties += 50.0
171
+ elif contra.severity == "MEDIUM":
172
+ cap = min(cap, 50.0) if cap is not None else 50.0
173
+ breakdown.contradiction_penalties += 25.0
174
+ elif contra.severity == "LOW":
175
+ breakdown.contradiction_penalties += 10.0
176
+
177
+ score = max(0.0, score - breakdown.contradiction_penalties)
178
+ if cap is not None:
179
+ score = min(score, cap)
180
+ breakdown.contradiction_cap = cap
181
+
182
+ breakdown.final_score = round(min(100.0, score), 1)
183
+
184
+ # 7. Compose detailed explainability rationale
185
+ reasons = []
186
+ if breakdown.implementation_presence > 0:
187
+ reasons.append(f"Physical implementation verified (+{breakdown.implementation_presence:.0f}%)")
188
+ if breakdown.test_existence > 0:
189
+ reasons.append(f"Test suite exists (+{breakdown.test_existence:.0f}%)")
190
+ if breakdown.test_execution_pass > 0:
191
+ reasons.append(f"Automated tests passed (+{breakdown.test_execution_pass:.0f}%)")
192
+ if breakdown.git_consistency > 0:
193
+ reasons.append(f"Git state recorded (+{breakdown.git_consistency:.0f}%)")
194
+ if breakdown.documentation_presence > 0:
195
+ reasons.append(f"Documentation verified (+{breakdown.documentation_presence:.0f}%)")
196
+
197
+ if not has_physical_evidence:
198
+ reasons.append("Unbacked conversational claim capped at 15.0%")
199
+
200
+ if cap is not None and cap < raw:
201
+ reasons.append(f"Capped at {cap:.0f}% due to active contradictions ({len(relevant_contradictions)} detected)")
202
+
203
+ breakdown.explanation = (
204
+ f"Confidence {breakdown.final_score:.1f}% for '{target_name or target_id}'. "
205
+ + "; ".join(reasons)
206
+ + "."
207
+ )
208
+
209
+ return breakdown
210
+
211
+ def calculate_project_confidence(
212
+ self,
213
+ canonical_state: CanonicalProjectState
214
+ ) -> Tuple[float, Dict[str, Any]]:
215
+ """
216
+ Calculates global aggregate project confidence and detailed telemetry.
217
+ Implements IConfidenceCalculator Protocol.
218
+ """
219
+ scores: List[float] = []
220
+ node_breakdowns: Dict[str, Any] = {}
221
+
222
+ # 1. Evaluate registered graph nodes
223
+ for node in canonical_state.graph_nodes.values():
224
+ sc, bd = self.calculate_node_confidence(
225
+ node=node,
226
+ evidence_pool=canonical_state.evidence_pool,
227
+ contradictions=canonical_state.contradictions
228
+ )
229
+ scores.append(sc)
230
+ node_breakdowns[node.id] = bd
231
+
232
+ # 2. Evaluate User Requirements
233
+ req_scores = []
234
+ for req in canonical_state.conversational_state.user_requirements:
235
+ matching_ev = [
236
+ ev for ev in canonical_state.evidence_pool.values()
237
+ if req.title.lower() in ev.summary.lower()
238
+ or (req.evidence_id and ev.id == req.evidence_id)
239
+ ]
240
+ bd = self.evaluate_component_confidence(
241
+ target_id=req.id,
242
+ target_name=req.title,
243
+ evidence_list=matching_ev,
244
+ contradictions=canonical_state.contradictions
245
+ )
246
+ req_scores.append(bd.final_score)
247
+ scores.append(bd.final_score)
248
+
249
+ # 3. Factor in Project Physical State metrics
250
+ pstate = canonical_state.project_state
251
+ has_code = len(pstate.files) > 0
252
+ has_symbols = len(pstate.symbols) > 0
253
+ test_pass_rate = 0.0
254
+ if pstate.test_results:
255
+ passed_tests = sum(1 for t in pstate.test_results if t.status == Status.VERIFIED)
256
+ test_pass_rate = (passed_tests / len(pstate.test_results)) * 100.0
257
+
258
+ if not scores:
259
+ # Baseline calculation from project state if graph is not yet populated
260
+ base_score = 0.0
261
+ if has_code:
262
+ base_score += 35.0
263
+ if has_symbols:
264
+ base_score += 15.0
265
+ if pstate.test_results:
266
+ base_score += (test_pass_rate * 0.35)
267
+ if pstate.git_state.is_repo and not pstate.git_state.is_dirty:
268
+ base_score += 15.0
269
+
270
+ # Contradiction discount
271
+ unresolved_high = sum(1 for c in canonical_state.contradictions if c.severity == "HIGH" and not c.resolved)
272
+ unresolved_med = sum(1 for c in canonical_state.contradictions if c.severity == "MEDIUM" and not c.resolved)
273
+ penalty = (unresolved_high * 25.0) + (unresolved_med * 10.0)
274
+ final_project_score = round(max(0.0, min(100.0, base_score - penalty)), 1)
275
+ else:
276
+ final_project_score = round(sum(scores) / len(scores), 1)
277
+
278
+ summary_data = {
279
+ "project_confidence": final_project_score,
280
+ "evaluated_node_count": len(scores),
281
+ "unresolved_contradictions": len([c for c in canonical_state.contradictions if not c.resolved]),
282
+ "test_pass_rate": round(test_pass_rate, 1),
283
+ "has_physical_code": has_code,
284
+ "has_ast_symbols": has_symbols,
285
+ "node_breakdowns": node_breakdowns
286
+ }
287
+
288
+ return final_project_score, summary_data
289
+
290
+ def _get_relevant_contradictions(
291
+ self,
292
+ target_id: str,
293
+ target_name: str,
294
+ contradictions: List[ContradictionRecord]
295
+ ) -> List[ContradictionRecord]:
296
+ """Filters contradictions related to the specific target."""
297
+ relevant = []
298
+ target_lower = (target_name or target_id).lower()
299
+
300
+ for c in contradictions:
301
+ if c.resolved:
302
+ continue
303
+ if c.claim_id == target_id:
304
+ relevant.append(c)
305
+ elif target_lower and (target_lower in c.explanation.lower() or target_lower in c.claim_text.lower()):
306
+ relevant.append(c)
307
+
308
+ return relevant
confidence/models.py ADDED
@@ -0,0 +1,40 @@
1
+ """
2
+ Project Continuum - Confidence Metrics & Breakdown Models
3
+ =========================================================
4
+ Milestone 3 - Phase 8: Confidence Calculation & Status Evaluation.
5
+ Defines structured multi-dimensional confidence breakdown schemas.
6
+ """
7
+
8
+ from dataclasses import dataclass, field, asdict
9
+ from typing import Any, Dict, List, Optional
10
+
11
+
12
+ @dataclass
13
+ class ConfidenceBreakdown:
14
+ """
15
+ Explainable multi-dimensional confidence scoring breakdown.
16
+ Total potential raw score: 100.0%.
17
+ """
18
+ target_id: str
19
+ implementation_presence: float = 0.0 # Max 35.0 (AST symbols, files, exports)
20
+ test_existence: float = 0.0 # Max 15.0 (Test files / suites present)
21
+ test_execution_pass: float = 0.0 # Max 30.0 (Passing test exit codes)
22
+ git_consistency: float = 0.0 # Max 10.0 (Clean working tree & commits)
23
+ documentation_presence: float = 0.0 # Max 10.0 (Docstrings / Markdown)
24
+
25
+ # Penalties & Contradiction Caps:
26
+ raw_score: float = 0.0
27
+ contradiction_penalties: float = 0.0
28
+ contradiction_cap: Optional[float] = None
29
+ final_score: float = 0.0 # Clamped 0.0 - 100.0%
30
+
31
+ explanation: str = ""
32
+ contributing_evidence_ids: List[str] = field(default_factory=list)
33
+ active_contradiction_ids: List[str] = field(default_factory=list)
34
+
35
+ def to_dict(self) -> Dict[str, Any]:
36
+ return asdict(self)
37
+
38
+ @classmethod
39
+ def from_dict(cls, data: Dict[str, Any]) -> "ConfidenceBreakdown":
40
+ return cls(**data)
context/__init__.py ADDED
@@ -0,0 +1,17 @@
1
+ """
2
+ Project Continuum - Context Selection Subsystem Package
3
+ ========================================================
4
+ Milestone 5 - Phases 12 & 13.
5
+ """
6
+
7
+ from context.models import TaskContext
8
+ from context.selector import TaskContextSelector
9
+ from context.pruner import ContextPruner, ContextPriorityTier, PrunedContextResult
10
+
11
+ __all__ = [
12
+ "TaskContext",
13
+ "TaskContextSelector",
14
+ "ContextPruner",
15
+ "ContextPriorityTier",
16
+ "PrunedContextResult",
17
+ ]
context/models.py ADDED
@@ -0,0 +1,108 @@
1
+ """
2
+ Project Continuum - Task Context Models
3
+ =======================================
4
+ Milestone 5 - Phase 12: Task-Driven Context Selection.
5
+ Defines the focused, task-scoped representation of project truth.
6
+ """
7
+
8
+ from dataclasses import dataclass, field, asdict
9
+ from typing import Any, Dict, List, Optional
10
+
11
+ from core.state_models import (
12
+ ArchitecturalDecision,
13
+ AstSymbol,
14
+ ContradictionRecord,
15
+ GraphNode,
16
+ TestResult,
17
+ )
18
+
19
+
20
+ @dataclass
21
+ class TaskContext:
22
+ """
23
+ Focused, high-signal project slice containing only information relevant to a target task.
24
+ """
25
+ task_description: str
26
+ primary_nodes: List[GraphNode] = field(default_factory=list)
27
+ dependency_nodes: List[GraphNode] = field(default_factory=list)
28
+ relevant_files: List[str] = field(default_factory=list)
29
+ relevant_symbols: List[AstSymbol] = field(default_factory=list)
30
+ relevant_tests: List[TestResult] = field(default_factory=list)
31
+ active_constraints: List[str] = field(default_factory=list)
32
+ relevant_decisions: List[ArchitecturalDecision] = field(default_factory=list)
33
+ known_blockers: List[ContradictionRecord] = field(default_factory=list)
34
+ verification_status_summary: Dict[str, Any] = field(default_factory=dict)
35
+ omitted_node_count: int = 0
36
+
37
+ def to_dict(self) -> Dict[str, Any]:
38
+ return {
39
+ "task_description": self.task_description,
40
+ "primary_nodes": [n.to_dict() for n in self.primary_nodes],
41
+ "dependency_nodes": [n.to_dict() for n in self.dependency_nodes],
42
+ "relevant_files": self.relevant_files,
43
+ "relevant_symbols": [s.to_dict() for s in self.relevant_symbols],
44
+ "relevant_tests": [t.to_dict() for t in self.relevant_tests],
45
+ "active_constraints": self.active_constraints,
46
+ "relevant_decisions": [d.to_dict() for d in self.relevant_decisions],
47
+ "known_blockers": [b.to_dict() for b in self.known_blockers],
48
+ "verification_status_summary": self.verification_status_summary,
49
+ "omitted_node_count": self.omitted_node_count
50
+ }
51
+
52
+ def to_markdown(self) -> str:
53
+ """
54
+ Formats the focused task context into a high-density, LLM-ready prompt briefing.
55
+ """
56
+ lines = [
57
+ f"# Task Briefing: {self.task_description}",
58
+ "",
59
+ "## ๐ŸŽฏ Primary Components",
60
+ ]
61
+
62
+ if self.primary_nodes:
63
+ for n in self.primary_nodes:
64
+ lines.append(f"- **{n.name}** (`{n.node_type.value}`): Status `[{n.status.value}]` (Confidence: {n.confidence_score:.0f}%)")
65
+ else:
66
+ lines.append("- *No specific primary nodes identified.*")
67
+
68
+ if self.dependency_nodes:
69
+ lines.append("\n## ๐Ÿ”— Dependencies & Direct Prerequisites")
70
+ for d in self.dependency_nodes:
71
+ lines.append(f"- **{d.name}** (`{d.node_type.value}`): Status `[{d.status.value}]`")
72
+
73
+ if self.relevant_symbols:
74
+ lines.append("\n## ๐Ÿงฌ Relevant Code Symbols & Interfaces")
75
+ for s in self.relevant_symbols:
76
+ lines.append(f"- `{s.kind} {s.name}` in `{s.file_path}` (lines {s.line_start}-{s.line_end})")
77
+
78
+ if self.relevant_tests:
79
+ lines.append("\n## ๐Ÿงช Verification & Test Status")
80
+ for t in self.relevant_tests:
81
+ status_icon = "โœ…" if t.status.value == "VERIFIED" else "โŒ"
82
+ lines.append(f"- {status_icon} **{t.name}** (`{t.suite}`): `{t.status.value}` (exit code {t.exit_code})")
83
+ if t.error_message:
84
+ lines.append(f" - *Error*: `{t.error_message}`")
85
+
86
+ if self.known_blockers:
87
+ lines.append("\n## โš ๏ธ Known Blockers & Active Discrepancies")
88
+ for b in self.known_blockers:
89
+ lines.append(f"- **[{b.severity}]**: {b.explanation}")
90
+
91
+ if self.active_constraints:
92
+ lines.append("\n## ๐Ÿ›ก๏ธ Active Constraints")
93
+ for c in self.active_constraints:
94
+ lines.append(f"- {c}")
95
+
96
+ if self.relevant_decisions:
97
+ lines.append("\n## ๐Ÿ“ Architectural Decisions")
98
+ for d in self.relevant_decisions:
99
+ lines.append(f"- **{d.title}**: {d.rationale}")
100
+
101
+ lines.append(f"\n*(Project Context Optimized: {self.omitted_node_count} unrelated nodes omitted)*")
102
+ return "\n".join(lines)
103
+
104
+ def prune(self, token_budget: int) -> Any:
105
+ """Prunes this task context to fit within a specific token budget."""
106
+ from context.pruner import ContextPruner
107
+ pruner = ContextPruner()
108
+ return pruner.prune_to_budget(self, token_budget=token_budget)