htmlgraph 0.20.1__py3-none-any.whl → 0.27.5__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 (304) hide show
  1. htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
  2. htmlgraph/.htmlgraph/agents.json +72 -0
  3. htmlgraph/.htmlgraph/htmlgraph.db +0 -0
  4. htmlgraph/__init__.py +51 -1
  5. htmlgraph/__init__.pyi +123 -0
  6. htmlgraph/agent_detection.py +26 -10
  7. htmlgraph/agent_registry.py +2 -1
  8. htmlgraph/analytics/__init__.py +8 -1
  9. htmlgraph/analytics/cli.py +86 -20
  10. htmlgraph/analytics/cost_analyzer.py +391 -0
  11. htmlgraph/analytics/cost_monitor.py +664 -0
  12. htmlgraph/analytics/cost_reporter.py +675 -0
  13. htmlgraph/analytics/cross_session.py +617 -0
  14. htmlgraph/analytics/dependency.py +10 -6
  15. htmlgraph/analytics/pattern_learning.py +771 -0
  16. htmlgraph/analytics/session_graph.py +707 -0
  17. htmlgraph/analytics/strategic/__init__.py +80 -0
  18. htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
  19. htmlgraph/analytics/strategic/pattern_detector.py +876 -0
  20. htmlgraph/analytics/strategic/preference_manager.py +709 -0
  21. htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
  22. htmlgraph/analytics/work_type.py +67 -27
  23. htmlgraph/analytics_index.py +53 -20
  24. htmlgraph/api/__init__.py +3 -0
  25. htmlgraph/api/cost_alerts_websocket.py +416 -0
  26. htmlgraph/api/main.py +2498 -0
  27. htmlgraph/api/static/htmx.min.js +1 -0
  28. htmlgraph/api/static/style-redesign.css +1344 -0
  29. htmlgraph/api/static/style.css +1079 -0
  30. htmlgraph/api/templates/dashboard-redesign.html +1366 -0
  31. htmlgraph/api/templates/dashboard.html +794 -0
  32. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  33. htmlgraph/api/templates/partials/activity-feed.html +1100 -0
  34. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  35. htmlgraph/api/templates/partials/agents.html +317 -0
  36. htmlgraph/api/templates/partials/event-traces.html +373 -0
  37. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  38. htmlgraph/api/templates/partials/features.html +578 -0
  39. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  40. htmlgraph/api/templates/partials/metrics.html +346 -0
  41. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  42. htmlgraph/api/templates/partials/orchestration.html +198 -0
  43. htmlgraph/api/templates/partials/spawners.html +375 -0
  44. htmlgraph/api/templates/partials/work-items.html +613 -0
  45. htmlgraph/api/websocket.py +538 -0
  46. htmlgraph/archive/__init__.py +24 -0
  47. htmlgraph/archive/bloom.py +234 -0
  48. htmlgraph/archive/fts.py +297 -0
  49. htmlgraph/archive/manager.py +583 -0
  50. htmlgraph/archive/search.py +244 -0
  51. htmlgraph/atomic_ops.py +560 -0
  52. htmlgraph/attribute_index.py +2 -1
  53. htmlgraph/bounded_paths.py +539 -0
  54. htmlgraph/builders/base.py +57 -2
  55. htmlgraph/builders/bug.py +19 -3
  56. htmlgraph/builders/chore.py +19 -3
  57. htmlgraph/builders/epic.py +19 -3
  58. htmlgraph/builders/feature.py +27 -3
  59. htmlgraph/builders/insight.py +2 -1
  60. htmlgraph/builders/metric.py +2 -1
  61. htmlgraph/builders/pattern.py +2 -1
  62. htmlgraph/builders/phase.py +19 -3
  63. htmlgraph/builders/spike.py +29 -3
  64. htmlgraph/builders/track.py +42 -1
  65. htmlgraph/cigs/__init__.py +81 -0
  66. htmlgraph/cigs/autonomy.py +385 -0
  67. htmlgraph/cigs/cost.py +475 -0
  68. htmlgraph/cigs/messages_basic.py +472 -0
  69. htmlgraph/cigs/messaging.py +365 -0
  70. htmlgraph/cigs/models.py +771 -0
  71. htmlgraph/cigs/pattern_storage.py +427 -0
  72. htmlgraph/cigs/patterns.py +503 -0
  73. htmlgraph/cigs/posttool_analyzer.py +234 -0
  74. htmlgraph/cigs/reporter.py +818 -0
  75. htmlgraph/cigs/tracker.py +317 -0
  76. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  77. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  78. htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
  79. htmlgraph/cli/__init__.py +42 -0
  80. htmlgraph/cli/__main__.py +6 -0
  81. htmlgraph/cli/analytics.py +1424 -0
  82. htmlgraph/cli/base.py +685 -0
  83. htmlgraph/cli/constants.py +206 -0
  84. htmlgraph/cli/core.py +954 -0
  85. htmlgraph/cli/main.py +147 -0
  86. htmlgraph/cli/models.py +475 -0
  87. htmlgraph/cli/templates/__init__.py +1 -0
  88. htmlgraph/cli/templates/cost_dashboard.py +399 -0
  89. htmlgraph/cli/work/__init__.py +239 -0
  90. htmlgraph/cli/work/browse.py +115 -0
  91. htmlgraph/cli/work/features.py +568 -0
  92. htmlgraph/cli/work/orchestration.py +676 -0
  93. htmlgraph/cli/work/report.py +728 -0
  94. htmlgraph/cli/work/sessions.py +466 -0
  95. htmlgraph/cli/work/snapshot.py +559 -0
  96. htmlgraph/cli/work/tracks.py +486 -0
  97. htmlgraph/cli_commands/__init__.py +1 -0
  98. htmlgraph/cli_commands/feature.py +195 -0
  99. htmlgraph/cli_framework.py +115 -0
  100. htmlgraph/collections/__init__.py +2 -0
  101. htmlgraph/collections/base.py +197 -14
  102. htmlgraph/collections/bug.py +2 -1
  103. htmlgraph/collections/chore.py +2 -1
  104. htmlgraph/collections/epic.py +2 -1
  105. htmlgraph/collections/feature.py +2 -1
  106. htmlgraph/collections/insight.py +2 -1
  107. htmlgraph/collections/metric.py +2 -1
  108. htmlgraph/collections/pattern.py +2 -1
  109. htmlgraph/collections/phase.py +2 -1
  110. htmlgraph/collections/session.py +194 -0
  111. htmlgraph/collections/spike.py +13 -2
  112. htmlgraph/collections/task_delegation.py +241 -0
  113. htmlgraph/collections/todo.py +14 -1
  114. htmlgraph/collections/traces.py +487 -0
  115. htmlgraph/config/cost_models.json +56 -0
  116. htmlgraph/config.py +190 -0
  117. htmlgraph/context_analytics.py +2 -1
  118. htmlgraph/converter.py +116 -7
  119. htmlgraph/cost_analysis/__init__.py +5 -0
  120. htmlgraph/cost_analysis/analyzer.py +438 -0
  121. htmlgraph/dashboard.html +2246 -248
  122. htmlgraph/dashboard.html.backup +6592 -0
  123. htmlgraph/dashboard.html.bak +7181 -0
  124. htmlgraph/dashboard.html.bak2 +7231 -0
  125. htmlgraph/dashboard.html.bak3 +7232 -0
  126. htmlgraph/db/__init__.py +38 -0
  127. htmlgraph/db/queries.py +790 -0
  128. htmlgraph/db/schema.py +1788 -0
  129. htmlgraph/decorators.py +317 -0
  130. htmlgraph/dependency_models.py +2 -1
  131. htmlgraph/deploy.py +26 -27
  132. htmlgraph/docs/API_REFERENCE.md +841 -0
  133. htmlgraph/docs/HTTP_API.md +750 -0
  134. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  135. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
  136. htmlgraph/docs/README.md +532 -0
  137. htmlgraph/docs/__init__.py +77 -0
  138. htmlgraph/docs/docs_version.py +55 -0
  139. htmlgraph/docs/metadata.py +93 -0
  140. htmlgraph/docs/migrations.py +232 -0
  141. htmlgraph/docs/template_engine.py +143 -0
  142. htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
  143. htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
  144. htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
  145. htmlgraph/docs/templates/base_agents.md.j2 +78 -0
  146. htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
  147. htmlgraph/docs/version_check.py +163 -0
  148. htmlgraph/edge_index.py +2 -1
  149. htmlgraph/error_handler.py +544 -0
  150. htmlgraph/event_log.py +86 -37
  151. htmlgraph/event_migration.py +2 -1
  152. htmlgraph/file_watcher.py +12 -8
  153. htmlgraph/find_api.py +2 -1
  154. htmlgraph/git_events.py +67 -9
  155. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  156. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  157. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  158. htmlgraph/hooks/__init__.py +8 -0
  159. htmlgraph/hooks/bootstrap.py +169 -0
  160. htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
  161. htmlgraph/hooks/concurrent_sessions.py +208 -0
  162. htmlgraph/hooks/context.py +350 -0
  163. htmlgraph/hooks/drift_handler.py +525 -0
  164. htmlgraph/hooks/event_tracker.py +790 -99
  165. htmlgraph/hooks/git_commands.py +175 -0
  166. htmlgraph/hooks/installer.py +5 -1
  167. htmlgraph/hooks/orchestrator.py +327 -76
  168. htmlgraph/hooks/orchestrator_reflector.py +31 -4
  169. htmlgraph/hooks/post_tool_use_failure.py +32 -7
  170. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  171. htmlgraph/hooks/posttooluse.py +92 -19
  172. htmlgraph/hooks/pretooluse.py +527 -7
  173. htmlgraph/hooks/prompt_analyzer.py +637 -0
  174. htmlgraph/hooks/session_handler.py +668 -0
  175. htmlgraph/hooks/session_summary.py +395 -0
  176. htmlgraph/hooks/state_manager.py +504 -0
  177. htmlgraph/hooks/subagent_detection.py +202 -0
  178. htmlgraph/hooks/subagent_stop.py +369 -0
  179. htmlgraph/hooks/task_enforcer.py +99 -4
  180. htmlgraph/hooks/validator.py +212 -91
  181. htmlgraph/ids.py +2 -1
  182. htmlgraph/learning.py +125 -100
  183. htmlgraph/mcp_server.py +2 -1
  184. htmlgraph/models.py +217 -18
  185. htmlgraph/operations/README.md +62 -0
  186. htmlgraph/operations/__init__.py +79 -0
  187. htmlgraph/operations/analytics.py +339 -0
  188. htmlgraph/operations/bootstrap.py +289 -0
  189. htmlgraph/operations/events.py +244 -0
  190. htmlgraph/operations/fastapi_server.py +231 -0
  191. htmlgraph/operations/hooks.py +350 -0
  192. htmlgraph/operations/initialization.py +597 -0
  193. htmlgraph/operations/initialization.py.backup +228 -0
  194. htmlgraph/operations/server.py +303 -0
  195. htmlgraph/orchestration/__init__.py +58 -0
  196. htmlgraph/orchestration/claude_launcher.py +179 -0
  197. htmlgraph/orchestration/command_builder.py +72 -0
  198. htmlgraph/orchestration/headless_spawner.py +281 -0
  199. htmlgraph/orchestration/live_events.py +377 -0
  200. htmlgraph/orchestration/model_selection.py +327 -0
  201. htmlgraph/orchestration/plugin_manager.py +140 -0
  202. htmlgraph/orchestration/prompts.py +137 -0
  203. htmlgraph/orchestration/spawner_event_tracker.py +383 -0
  204. htmlgraph/orchestration/spawners/__init__.py +16 -0
  205. htmlgraph/orchestration/spawners/base.py +194 -0
  206. htmlgraph/orchestration/spawners/claude.py +173 -0
  207. htmlgraph/orchestration/spawners/codex.py +435 -0
  208. htmlgraph/orchestration/spawners/copilot.py +294 -0
  209. htmlgraph/orchestration/spawners/gemini.py +471 -0
  210. htmlgraph/orchestration/subprocess_runner.py +36 -0
  211. htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
  212. htmlgraph/orchestration.md +563 -0
  213. htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
  214. htmlgraph/orchestrator.py +2 -1
  215. htmlgraph/orchestrator_config.py +357 -0
  216. htmlgraph/orchestrator_mode.py +115 -4
  217. htmlgraph/parallel.py +2 -1
  218. htmlgraph/parser.py +86 -6
  219. htmlgraph/path_query.py +608 -0
  220. htmlgraph/pattern_matcher.py +636 -0
  221. htmlgraph/pydantic_models.py +476 -0
  222. htmlgraph/quality_gates.py +350 -0
  223. htmlgraph/query_builder.py +2 -1
  224. htmlgraph/query_composer.py +509 -0
  225. htmlgraph/reflection.py +443 -0
  226. htmlgraph/refs.py +344 -0
  227. htmlgraph/repo_hash.py +512 -0
  228. htmlgraph/repositories/__init__.py +292 -0
  229. htmlgraph/repositories/analytics_repository.py +455 -0
  230. htmlgraph/repositories/analytics_repository_standard.py +628 -0
  231. htmlgraph/repositories/feature_repository.py +581 -0
  232. htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
  233. htmlgraph/repositories/feature_repository_memory.py +607 -0
  234. htmlgraph/repositories/feature_repository_sqlite.py +858 -0
  235. htmlgraph/repositories/filter_service.py +620 -0
  236. htmlgraph/repositories/filter_service_standard.py +445 -0
  237. htmlgraph/repositories/shared_cache.py +621 -0
  238. htmlgraph/repositories/shared_cache_memory.py +395 -0
  239. htmlgraph/repositories/track_repository.py +552 -0
  240. htmlgraph/repositories/track_repository_htmlfile.py +619 -0
  241. htmlgraph/repositories/track_repository_memory.py +508 -0
  242. htmlgraph/repositories/track_repository_sqlite.py +711 -0
  243. htmlgraph/sdk/__init__.py +398 -0
  244. htmlgraph/sdk/__init__.pyi +14 -0
  245. htmlgraph/sdk/analytics/__init__.py +19 -0
  246. htmlgraph/sdk/analytics/engine.py +155 -0
  247. htmlgraph/sdk/analytics/helpers.py +178 -0
  248. htmlgraph/sdk/analytics/registry.py +109 -0
  249. htmlgraph/sdk/base.py +484 -0
  250. htmlgraph/sdk/constants.py +216 -0
  251. htmlgraph/sdk/core.pyi +308 -0
  252. htmlgraph/sdk/discovery.py +120 -0
  253. htmlgraph/sdk/help/__init__.py +12 -0
  254. htmlgraph/sdk/help/mixin.py +699 -0
  255. htmlgraph/sdk/mixins/__init__.py +15 -0
  256. htmlgraph/sdk/mixins/attribution.py +113 -0
  257. htmlgraph/sdk/mixins/mixin.py +410 -0
  258. htmlgraph/sdk/operations/__init__.py +12 -0
  259. htmlgraph/sdk/operations/mixin.py +427 -0
  260. htmlgraph/sdk/orchestration/__init__.py +17 -0
  261. htmlgraph/sdk/orchestration/coordinator.py +203 -0
  262. htmlgraph/sdk/orchestration/spawner.py +204 -0
  263. htmlgraph/sdk/planning/__init__.py +19 -0
  264. htmlgraph/sdk/planning/bottlenecks.py +93 -0
  265. htmlgraph/sdk/planning/mixin.py +211 -0
  266. htmlgraph/sdk/planning/parallel.py +186 -0
  267. htmlgraph/sdk/planning/queue.py +210 -0
  268. htmlgraph/sdk/planning/recommendations.py +87 -0
  269. htmlgraph/sdk/planning/smart_planning.py +319 -0
  270. htmlgraph/sdk/session/__init__.py +19 -0
  271. htmlgraph/sdk/session/continuity.py +57 -0
  272. htmlgraph/sdk/session/handoff.py +110 -0
  273. htmlgraph/sdk/session/info.py +309 -0
  274. htmlgraph/sdk/session/manager.py +103 -0
  275. htmlgraph/sdk/strategic/__init__.py +26 -0
  276. htmlgraph/sdk/strategic/mixin.py +563 -0
  277. htmlgraph/server.py +295 -107
  278. htmlgraph/session_hooks.py +300 -0
  279. htmlgraph/session_manager.py +285 -3
  280. htmlgraph/session_registry.py +587 -0
  281. htmlgraph/session_state.py +436 -0
  282. htmlgraph/session_warning.py +2 -1
  283. htmlgraph/sessions/__init__.py +23 -0
  284. htmlgraph/sessions/handoff.py +756 -0
  285. htmlgraph/system_prompts.py +450 -0
  286. htmlgraph/templates/orchestration-view.html +350 -0
  287. htmlgraph/track_builder.py +33 -1
  288. htmlgraph/track_manager.py +38 -0
  289. htmlgraph/transcript.py +18 -5
  290. htmlgraph/validation.py +115 -0
  291. htmlgraph/watch.py +2 -1
  292. htmlgraph/work_type_utils.py +2 -1
  293. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
  294. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
  295. htmlgraph-0.27.5.dist-info/RECORD +337 -0
  296. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
  297. htmlgraph/cli.py +0 -4839
  298. htmlgraph/sdk.py +0 -2359
  299. htmlgraph-0.20.1.dist-info/RECORD +0 -118
  300. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
  301. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  302. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  303. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  304. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
@@ -0,0 +1,771 @@
1
+ """
2
+ CIGS Data Models - Violation tracking and cost analysis.
3
+
4
+ Provides comprehensive data structures for tracking delegation violations,
5
+ pattern detection, autonomy management, and cost accounting in the
6
+ Computational Imperative Guidance System.
7
+
8
+ Classes:
9
+ - ViolationType: Enum of violation categories
10
+ - ViolationRecord: Single violation with context and cost impact
11
+ - SessionViolationSummary: Aggregated session metrics
12
+ - PatternRecord: Detected behavioral patterns
13
+ - AutonomyLevel: Agent autonomy recommendations
14
+ - CostMetrics: Token cost analysis per session
15
+ - TokenCost: Per-operation token costs
16
+ - CostPrediction: Cost projection for operations
17
+ - OperationClassification: Tool operation classification
18
+
19
+ Design Reference:
20
+ Part 3, Section 3.1 of computational-imperative-guidance-system-design.md
21
+ """
22
+
23
+ import json
24
+ from dataclasses import asdict, dataclass, field
25
+ from datetime import datetime
26
+ from enum import Enum
27
+
28
+
29
+ class ViolationType(Enum):
30
+ """Types of delegation violations.
31
+
32
+ Categories:
33
+ - DIRECT_EXPLORATION: Read/Grep/Glob when should delegate to spawn_gemini()
34
+ - DIRECT_IMPLEMENTATION: Edit/Write when should delegate to spawn_codex()
35
+ - DIRECT_TESTING: pytest/npm test directly instead of via Task()
36
+ - DIRECT_GIT: git commands directly instead of via spawn_copilot()
37
+ - EXPLORATION_SEQUENCE: 3+ exploration tools in sequence (indicates research work)
38
+ - IGNORED_WARNING: Proceeded after imperative warning from PreToolUse hook
39
+ """
40
+
41
+ DIRECT_EXPLORATION = "direct_exploration"
42
+ DIRECT_IMPLEMENTATION = "direct_implementation"
43
+ DIRECT_TESTING = "direct_testing"
44
+ DIRECT_GIT = "direct_git"
45
+ EXPLORATION_SEQUENCE = "exploration_sequence"
46
+ IGNORED_WARNING = "ignored_warning"
47
+
48
+ def __str__(self) -> str:
49
+ """Return human-readable violation type name."""
50
+ names = {
51
+ ViolationType.DIRECT_EXPLORATION: "Direct Exploration",
52
+ ViolationType.DIRECT_IMPLEMENTATION: "Direct Implementation",
53
+ ViolationType.DIRECT_TESTING: "Direct Testing",
54
+ ViolationType.DIRECT_GIT: "Direct Git",
55
+ ViolationType.EXPLORATION_SEQUENCE: "Exploration Sequence",
56
+ ViolationType.IGNORED_WARNING: "Ignored Warning",
57
+ }
58
+ return names.get(self, self.value)
59
+
60
+
61
+ @dataclass
62
+ class ViolationRecord:
63
+ """Record of a single delegation violation.
64
+
65
+ Tracks a violation event including the tool used, context, cost impact,
66
+ and escalation level. Used for session analytics and pattern detection.
67
+
68
+ Attributes:
69
+ id: Unique violation ID (e.g., "viol-001")
70
+ session_id: Session where violation occurred
71
+ timestamp: When violation was recorded
72
+ tool: Tool name that was used directly (Read, Grep, Edit, etc.)
73
+ tool_params: Parameters passed to tool for context
74
+ violation_type: Category of violation (DIRECT_EXPLORATION, etc.)
75
+ context_before: Description of what Claude was trying to accomplish
76
+ should_have_delegated_to: Recommended delegation target (spawn_gemini, Task, etc.)
77
+ actual_cost_tokens: Tokens consumed by direct execution
78
+ optimal_cost_tokens: Tokens if delegated properly
79
+ waste_tokens: Difference (actual - optimal)
80
+ warning_level: Escalation level (1=first, 2=second, 3=circuit_breaker)
81
+ was_warned: Whether PreToolUse hook warned before execution
82
+ warning_ignored: Whether Claude proceeded despite warning
83
+ agent: Agent that caused violation (default: "claude-code")
84
+ feature_id: Feature ID if violation occurred during feature work
85
+ """
86
+
87
+ id: str
88
+ session_id: str
89
+ timestamp: datetime
90
+ tool: str
91
+ tool_params: dict
92
+ violation_type: ViolationType
93
+
94
+ context_before: str | None = None
95
+ should_have_delegated_to: str = ""
96
+ actual_cost_tokens: int = 0
97
+ optimal_cost_tokens: int = 0
98
+ waste_tokens: int = 0
99
+
100
+ warning_level: int = 1
101
+ was_warned: bool = False
102
+ warning_ignored: bool = False
103
+
104
+ agent: str = "claude-code"
105
+ feature_id: str | None = None
106
+
107
+ def to_dict(self) -> dict:
108
+ """Convert to dictionary, handling enum and datetime serialization."""
109
+ return {
110
+ "id": self.id,
111
+ "session_id": self.session_id,
112
+ "timestamp": self.timestamp.isoformat(),
113
+ "tool": self.tool,
114
+ "tool_params": self.tool_params,
115
+ "violation_type": self.violation_type.value,
116
+ "context_before": self.context_before,
117
+ "should_have_delegated_to": self.should_have_delegated_to,
118
+ "actual_cost_tokens": self.actual_cost_tokens,
119
+ "optimal_cost_tokens": self.optimal_cost_tokens,
120
+ "waste_tokens": self.waste_tokens,
121
+ "warning_level": self.warning_level,
122
+ "was_warned": self.was_warned,
123
+ "warning_ignored": self.warning_ignored,
124
+ "agent": self.agent,
125
+ "feature_id": self.feature_id,
126
+ }
127
+
128
+ def to_json(self) -> str:
129
+ """Convert to JSON string."""
130
+ return json.dumps(self.to_dict())
131
+
132
+ @classmethod
133
+ def from_dict(cls, data: dict) -> "ViolationRecord":
134
+ """Create from dictionary, handling enum and datetime deserialization."""
135
+ data = data.copy()
136
+ data["timestamp"] = (
137
+ datetime.fromisoformat(data["timestamp"])
138
+ if isinstance(data["timestamp"], str)
139
+ else data["timestamp"]
140
+ )
141
+ data["violation_type"] = ViolationType(data["violation_type"])
142
+ return cls(**data)
143
+
144
+ @classmethod
145
+ def from_json(cls, json_str: str) -> "ViolationRecord":
146
+ """Create from JSON string."""
147
+ return cls.from_dict(json.loads(json_str))
148
+
149
+ def __str__(self) -> str:
150
+ """Human-readable representation."""
151
+ return (
152
+ f"Violation({self.id}): {self.tool} for {self.violation_type}\n"
153
+ f" Context: {self.context_before}\n"
154
+ f" Waste: {self.waste_tokens} tokens (actual: {self.actual_cost_tokens}, "
155
+ f"optimal: {self.optimal_cost_tokens})\n"
156
+ f" Warning level: {self.warning_level}, Warned: {self.was_warned}"
157
+ )
158
+
159
+ def validate(self) -> tuple[bool, str]:
160
+ """Validate violation record integrity.
161
+
162
+ Returns:
163
+ Tuple of (is_valid, error_message)
164
+ """
165
+ if not self.id:
166
+ return False, "id cannot be empty"
167
+ if not self.session_id:
168
+ return False, "session_id cannot be empty"
169
+ if not self.tool:
170
+ return False, "tool cannot be empty"
171
+ if self.actual_cost_tokens < 0:
172
+ return False, "actual_cost_tokens cannot be negative"
173
+ if self.optimal_cost_tokens < 0:
174
+ return False, "optimal_cost_tokens cannot be negative"
175
+ if self.waste_tokens != (self.actual_cost_tokens - self.optimal_cost_tokens):
176
+ return False, "waste_tokens must equal actual_cost - optimal_cost"
177
+ if not (1 <= self.warning_level <= 3):
178
+ return False, "warning_level must be 1, 2, or 3"
179
+ if self.was_warned and self.warning_ignored:
180
+ if self.warning_level < 2:
181
+ return False, "warning_ignored requires warning_level >= 2"
182
+ return True, ""
183
+
184
+
185
+ @dataclass
186
+ class SessionViolationSummary:
187
+ """Summary of violations for a single session.
188
+
189
+ Aggregates all violations that occurred during a session with metrics
190
+ for compliance rate, cost efficiency, and pattern analysis.
191
+
192
+ Attributes:
193
+ session_id: Session identifier
194
+ total_violations: Total violation count for session
195
+ violations_by_type: Count per violation type
196
+ total_waste_tokens: Sum of waste_tokens across all violations
197
+ circuit_breaker_triggered: Whether circuit breaker activated (>= 3 violations)
198
+ compliance_rate: Delegation compliance as float 0.0-1.0
199
+ violations: List of individual violation records
200
+ """
201
+
202
+ session_id: str
203
+ total_violations: int
204
+ violations_by_type: dict[ViolationType, int]
205
+ total_waste_tokens: int
206
+ circuit_breaker_triggered: bool
207
+ compliance_rate: float
208
+ violations: list[ViolationRecord] = field(default_factory=list)
209
+
210
+ def summary(self) -> str:
211
+ """Return human-readable summary text.
212
+
213
+ Returns:
214
+ Formatted summary with key metrics
215
+ """
216
+ breaker_status = "YES 🚨" if self.circuit_breaker_triggered else "No"
217
+
218
+ violations_detail = ""
219
+ for vtype, count in self.violations_by_type.items():
220
+ violations_detail += f" • {vtype}: {count}\n"
221
+
222
+ return (
223
+ f"Session {self.session_id}\n"
224
+ f"─────────────────────────────────────\n"
225
+ f"Total Violations: {self.total_violations}\n"
226
+ f"\nViolation Breakdown:\n{violations_detail}"
227
+ f"Total Waste: {self.total_waste_tokens} tokens\n"
228
+ f"Compliance Rate: {self.compliance_rate:.1%}\n"
229
+ f"Circuit Breaker: {breaker_status}"
230
+ )
231
+
232
+ def to_dict(self) -> dict:
233
+ """Convert to dictionary."""
234
+ return {
235
+ "session_id": self.session_id,
236
+ "total_violations": self.total_violations,
237
+ "violations_by_type": {
238
+ k.value: v for k, v in self.violations_by_type.items()
239
+ },
240
+ "total_waste_tokens": self.total_waste_tokens,
241
+ "circuit_breaker_triggered": self.circuit_breaker_triggered,
242
+ "compliance_rate": self.compliance_rate,
243
+ "violations": [v.to_dict() for v in self.violations],
244
+ }
245
+
246
+ def to_json(self) -> str:
247
+ """Convert to JSON string."""
248
+ return json.dumps(self.to_dict())
249
+
250
+ @classmethod
251
+ def from_dict(cls, data: dict) -> "SessionViolationSummary":
252
+ """Create from dictionary."""
253
+ data = data.copy()
254
+ data["violations_by_type"] = {
255
+ ViolationType(k): v for k, v in data.get("violations_by_type", {}).items()
256
+ }
257
+ data["violations"] = [
258
+ ViolationRecord.from_dict(v) for v in data.get("violations", [])
259
+ ]
260
+ return cls(**data)
261
+
262
+ @classmethod
263
+ def from_json(cls, json_str: str) -> "SessionViolationSummary":
264
+ """Create from JSON string."""
265
+ return cls.from_dict(json.loads(json_str))
266
+
267
+ def __str__(self) -> str:
268
+ """Return summary representation."""
269
+ return (
270
+ f"SessionViolationSummary({self.session_id}): "
271
+ f"{self.total_violations} violations, "
272
+ f"{self.compliance_rate:.0%} compliant, "
273
+ f"waste: {self.total_waste_tokens} tokens"
274
+ )
275
+
276
+ def validate(self) -> tuple[bool, str]:
277
+ """Validate summary integrity.
278
+
279
+ Returns:
280
+ Tuple of (is_valid, error_message)
281
+ """
282
+ if not self.session_id:
283
+ return False, "session_id cannot be empty"
284
+ if self.total_violations < 0:
285
+ return False, "total_violations cannot be negative"
286
+ if not (0.0 <= self.compliance_rate <= 1.0):
287
+ return False, "compliance_rate must be between 0.0 and 1.0"
288
+ if self.total_waste_tokens < 0:
289
+ return False, "total_waste_tokens cannot be negative"
290
+ if self.circuit_breaker_triggered and self.total_violations < 3:
291
+ return False, "circuit_breaker_triggered requires >= 3 violations"
292
+
293
+ # Validate count sum matches total
294
+ count_sum = sum(self.violations_by_type.values())
295
+ if count_sum != self.total_violations:
296
+ return (
297
+ False,
298
+ f"violations_by_type sum ({count_sum}) != total_violations "
299
+ f"({self.total_violations})",
300
+ )
301
+
302
+ return True, ""
303
+
304
+ @property
305
+ def count(self) -> int:
306
+ """Total violation count."""
307
+ return self.total_violations
308
+
309
+
310
+ @dataclass
311
+ class TokenCost:
312
+ """Token cost breakdown for an operation or session.
313
+
314
+ Provides granular token accounting for cost analysis and efficiency
315
+ calculation.
316
+
317
+ Attributes:
318
+ total_tokens: Total tokens consumed
319
+ orchestrator_tokens: Tokens in orchestrator/main agent context
320
+ subagent_tokens: Tokens consumed by delegated subagents
321
+ estimated_savings: Estimated tokens saved via delegation
322
+ """
323
+
324
+ total_tokens: int
325
+ orchestrator_tokens: int
326
+ subagent_tokens: int
327
+ estimated_savings: int = 0
328
+
329
+ def to_dict(self) -> dict:
330
+ """Convert to dictionary."""
331
+ return asdict(self)
332
+
333
+ def to_json(self) -> str:
334
+ """Convert to JSON string."""
335
+ return json.dumps(self.to_dict())
336
+
337
+ @classmethod
338
+ def from_dict(cls, data: dict) -> "TokenCost":
339
+ """Create from dictionary."""
340
+ return cls(**data)
341
+
342
+ @classmethod
343
+ def from_json(cls, json_str: str) -> "TokenCost":
344
+ """Create from JSON string."""
345
+ return cls.from_dict(json.loads(json_str))
346
+
347
+ def __str__(self) -> str:
348
+ """Human-readable representation."""
349
+ return (
350
+ f"TokenCost: {self.total_tokens} total\n"
351
+ f" Orchestrator: {self.orchestrator_tokens}\n"
352
+ f" Subagents: {self.subagent_tokens}\n"
353
+ f" Estimated savings: {self.estimated_savings}"
354
+ )
355
+
356
+ def validate(self) -> tuple[bool, str]:
357
+ """Validate token cost integrity.
358
+
359
+ Returns:
360
+ Tuple of (is_valid, error_message)
361
+ """
362
+ if self.total_tokens < 0:
363
+ return False, "total_tokens cannot be negative"
364
+ if self.orchestrator_tokens < 0:
365
+ return False, "orchestrator_tokens cannot be negative"
366
+ if self.subagent_tokens < 0:
367
+ return False, "subagent_tokens cannot be negative"
368
+ if self.estimated_savings < 0:
369
+ return False, "estimated_savings cannot be negative"
370
+ if (self.orchestrator_tokens + self.subagent_tokens) > self.total_tokens:
371
+ return (
372
+ False,
373
+ "orchestrator_tokens + subagent_tokens cannot exceed total_tokens",
374
+ )
375
+ return True, ""
376
+
377
+
378
+ @dataclass
379
+ class CostPrediction:
380
+ """Prediction of cost impact for an operation.
381
+
382
+ Used by PreToolUse hook to estimate token cost of direct execution vs
383
+ optimal delegation approach.
384
+
385
+ Attributes:
386
+ should_delegate: Whether operation should be delegated
387
+ optimal_cost: Predicted cost if delegated
388
+ waste_percentage: Estimated waste as percentage
389
+ """
390
+
391
+ should_delegate: bool
392
+ optimal_cost: int
393
+ waste_percentage: float
394
+
395
+ def to_dict(self) -> dict:
396
+ """Convert to dictionary."""
397
+ return asdict(self)
398
+
399
+ def to_json(self) -> str:
400
+ """Convert to JSON string."""
401
+ return json.dumps(self.to_dict())
402
+
403
+ @classmethod
404
+ def from_dict(cls, data: dict) -> "CostPrediction":
405
+ """Create from dictionary."""
406
+ return cls(**data)
407
+
408
+ @classmethod
409
+ def from_json(cls, json_str: str) -> "CostPrediction":
410
+ """Create from JSON string."""
411
+ return cls.from_dict(json.loads(json_str))
412
+
413
+ def __str__(self) -> str:
414
+ """Human-readable representation."""
415
+ return (
416
+ f"CostPrediction: Should delegate: {self.should_delegate}\n"
417
+ f" Optimal cost: {self.optimal_cost} tokens\n"
418
+ f" Waste if direct: {self.waste_percentage:.1f}%"
419
+ )
420
+
421
+ def validate(self) -> tuple[bool, str]:
422
+ """Validate cost prediction integrity.
423
+
424
+ Returns:
425
+ Tuple of (is_valid, error_message)
426
+ """
427
+ if self.optimal_cost < 0:
428
+ return False, "optimal_cost cannot be negative"
429
+ if not (0.0 <= self.waste_percentage <= 100.0):
430
+ return False, "waste_percentage must be between 0 and 100"
431
+ return True, ""
432
+
433
+
434
+ @dataclass
435
+ class OperationClassification:
436
+ """Classification of a tool operation for delegation decisions.
437
+
438
+ Used by PreToolUse hook to classify operations and determine if delegation
439
+ is required. Combines tool category with pattern analysis.
440
+
441
+ Attributes:
442
+ tool: Tool name (Read, Edit, Bash, etc.)
443
+ category: Operation category (exploration, implementation, etc.)
444
+ should_delegate: Whether operation requires delegation
445
+ reason: Explanation for classification
446
+ is_exploration_sequence: Whether this is part of multi-operation sequence
447
+ suggested_delegation: Recommended delegation target
448
+ predicted_cost: Predicted tokens for direct execution
449
+ optimal_cost: Tokens if delegated
450
+ waste_percentage: Waste as percentage
451
+ """
452
+
453
+ tool: str
454
+ category: str
455
+ should_delegate: bool
456
+ reason: str
457
+ is_exploration_sequence: bool
458
+ suggested_delegation: str
459
+
460
+ predicted_cost: int = 0
461
+ optimal_cost: int = 0
462
+ waste_percentage: float = 0.0
463
+
464
+ VALID_CATEGORIES = {
465
+ "exploration",
466
+ "implementation",
467
+ "testing",
468
+ "git",
469
+ "allowed",
470
+ "edge_case",
471
+ }
472
+
473
+ def to_dict(self) -> dict:
474
+ """Convert to dictionary."""
475
+ return asdict(self)
476
+
477
+ def to_json(self) -> str:
478
+ """Convert to JSON string."""
479
+ return json.dumps(self.to_dict())
480
+
481
+ @classmethod
482
+ def from_dict(cls, data: dict) -> "OperationClassification":
483
+ """Create from dictionary."""
484
+ return cls(**data)
485
+
486
+ @classmethod
487
+ def from_json(cls, json_str: str) -> "OperationClassification":
488
+ """Create from JSON string."""
489
+ return cls.from_dict(json.loads(json_str))
490
+
491
+ def __str__(self) -> str:
492
+ """Human-readable representation."""
493
+ return (
494
+ f"OperationClassification: {self.tool} ({self.category})\n"
495
+ f" Should delegate: {self.should_delegate}\n"
496
+ f" Reason: {self.reason}\n"
497
+ f" Suggested: {self.suggested_delegation}\n"
498
+ f" Cost: {self.predicted_cost} → {self.optimal_cost} "
499
+ f"({self.waste_percentage:.1f}% waste)"
500
+ )
501
+
502
+ def validate(self) -> tuple[bool, str]:
503
+ """Validate operation classification integrity.
504
+
505
+ Returns:
506
+ Tuple of (is_valid, error_message)
507
+ """
508
+ if not self.tool:
509
+ return False, "tool cannot be empty"
510
+ if self.category not in self.VALID_CATEGORIES:
511
+ return False, f"category must be one of {self.VALID_CATEGORIES}"
512
+ if not self.reason:
513
+ return False, "reason cannot be empty"
514
+ if not self.suggested_delegation:
515
+ return False, "suggested_delegation cannot be empty"
516
+ if self.predicted_cost < 0:
517
+ return False, "predicted_cost cannot be negative"
518
+ if self.optimal_cost < 0:
519
+ return False, "optimal_cost cannot be negative"
520
+ if not (0.0 <= self.waste_percentage <= 100.0):
521
+ return False, "waste_percentage must be between 0 and 100"
522
+ return True, ""
523
+
524
+
525
+ @dataclass
526
+ class PatternRecord:
527
+ """Record of a detected behavioral pattern.
528
+
529
+ Tracks identified patterns (both good patterns and anti-patterns) for
530
+ learning and customizing guidance messages.
531
+
532
+ Attributes:
533
+ id: Unique pattern ID
534
+ pattern_type: "anti-pattern" or "good-pattern"
535
+ name: Human-readable pattern name
536
+ description: What the pattern represents
537
+ trigger_conditions: List of conditions that activate this pattern
538
+ example_sequence: Example tool sequence that triggers pattern
539
+ occurrence_count: How many times detected
540
+ sessions_affected: Sessions where pattern was detected
541
+ correct_approach: Recommended fix (for anti-patterns)
542
+ delegation_suggestion: What to delegate to instead
543
+ """
544
+
545
+ id: str
546
+ pattern_type: str
547
+ name: str
548
+ description: str
549
+ trigger_conditions: list[str]
550
+ example_sequence: list[str]
551
+
552
+ occurrence_count: int = 0
553
+ sessions_affected: list[str] = field(default_factory=list)
554
+
555
+ correct_approach: str | None = None
556
+ delegation_suggestion: str | None = None
557
+
558
+ def to_dict(self) -> dict:
559
+ """Convert to dictionary."""
560
+ return asdict(self)
561
+
562
+ def to_json(self) -> str:
563
+ """Convert to JSON string."""
564
+ return json.dumps(self.to_dict())
565
+
566
+ @classmethod
567
+ def from_dict(cls, data: dict) -> "PatternRecord":
568
+ """Create from dictionary."""
569
+ return cls(**data)
570
+
571
+ @classmethod
572
+ def from_json(cls, json_str: str) -> "PatternRecord":
573
+ """Create from JSON string."""
574
+ return cls.from_dict(json.loads(json_str))
575
+
576
+ def __str__(self) -> str:
577
+ """Human-readable representation."""
578
+ return (
579
+ f"PatternRecord({self.id}): {self.name} ({self.pattern_type})\n"
580
+ f" Description: {self.description}\n"
581
+ f" Occurrences: {self.occurrence_count} in {len(self.sessions_affected)} "
582
+ f"sessions"
583
+ )
584
+
585
+ def validate(self) -> tuple[bool, str]:
586
+ """Validate pattern record integrity.
587
+
588
+ Returns:
589
+ Tuple of (is_valid, error_message)
590
+ """
591
+ if not self.id:
592
+ return False, "id cannot be empty"
593
+ if self.pattern_type not in ("anti-pattern", "good-pattern"):
594
+ return False, "pattern_type must be 'anti-pattern' or 'good-pattern'"
595
+ if not self.name:
596
+ return False, "name cannot be empty"
597
+ if not self.description:
598
+ return False, "description cannot be empty"
599
+ if not self.trigger_conditions:
600
+ return False, "trigger_conditions cannot be empty"
601
+ if not self.example_sequence:
602
+ return False, "example_sequence cannot be empty"
603
+ if self.occurrence_count < 0:
604
+ return False, "occurrence_count cannot be negative"
605
+ if self.pattern_type == "anti-pattern" and not self.correct_approach:
606
+ return False, "anti-patterns must have correct_approach"
607
+ return True, ""
608
+
609
+
610
+ @dataclass
611
+ class AutonomyLevel:
612
+ """Recommendation for agent autonomy level.
613
+
614
+ Suggests appropriate autonomy settings based on demonstrated behavior and
615
+ violation history. Adapts guidance intensity to agent competence.
616
+
617
+ Levels:
618
+ - "observer": Minimal guidance, only watch
619
+ - "consultant": Moderate guidance, suggest alternatives
620
+ - "collaborator": Strong guidance, detailed explanations
621
+ - "operator": Maximal guidance, mandatory acknowledgments
622
+
623
+ Attributes:
624
+ level: One of "observer", "consultant", "collaborator", "operator"
625
+ messaging_intensity: "minimal", "moderate", "high", or "maximal"
626
+ enforcement_mode: "guidance" (no blocking) or "strict" (with acknowledgment)
627
+ reason: Explanation of why this level is recommended
628
+ based_on_violations: Count of violations influencing recommendation
629
+ based_on_patterns: List of patterns influencing recommendation
630
+ """
631
+
632
+ level: str
633
+ messaging_intensity: str
634
+ enforcement_mode: str
635
+
636
+ reason: str
637
+ based_on_violations: int = 0
638
+ based_on_patterns: list[str] = field(default_factory=list)
639
+
640
+ VALID_LEVELS = {"observer", "consultant", "collaborator", "operator"}
641
+ VALID_INTENSITIES = {"minimal", "moderate", "high", "maximal"}
642
+ VALID_MODES = {"guidance", "strict"}
643
+
644
+ def to_dict(self) -> dict:
645
+ """Convert to dictionary."""
646
+ return asdict(self)
647
+
648
+ def to_json(self) -> str:
649
+ """Convert to JSON string."""
650
+ return json.dumps(self.to_dict())
651
+
652
+ @classmethod
653
+ def from_dict(cls, data: dict) -> "AutonomyLevel":
654
+ """Create from dictionary."""
655
+ return cls(**data)
656
+
657
+ @classmethod
658
+ def from_json(cls, json_str: str) -> "AutonomyLevel":
659
+ """Create from JSON string."""
660
+ return cls.from_dict(json.loads(json_str))
661
+
662
+ def __str__(self) -> str:
663
+ """Human-readable representation."""
664
+ return (
665
+ f"AutonomyLevel: {self.level}\n"
666
+ f" Messaging: {self.messaging_intensity}\n"
667
+ f" Enforcement: {self.enforcement_mode}\n"
668
+ f" Reason: {self.reason}"
669
+ )
670
+
671
+ def validate(self) -> tuple[bool, str]:
672
+ """Validate autonomy level configuration.
673
+
674
+ Returns:
675
+ Tuple of (is_valid, error_message)
676
+ """
677
+ if self.level not in self.VALID_LEVELS:
678
+ return False, f"level must be one of {self.VALID_LEVELS}"
679
+ if self.messaging_intensity not in self.VALID_INTENSITIES:
680
+ return False, (
681
+ f"messaging_intensity must be one of {self.VALID_INTENSITIES}"
682
+ )
683
+ if self.enforcement_mode not in self.VALID_MODES:
684
+ return False, f"enforcement_mode must be one of {self.VALID_MODES}"
685
+ if not self.reason:
686
+ return False, "reason cannot be empty"
687
+ if self.based_on_violations < 0:
688
+ return False, "based_on_violations cannot be negative"
689
+ return True, ""
690
+
691
+
692
+ @dataclass
693
+ class CostMetrics:
694
+ """Comprehensive cost metrics for a session or operation.
695
+
696
+ Aggregates token costs with efficiency scoring and waste analysis.
697
+
698
+ Attributes:
699
+ total_tokens: Total tokens consumed
700
+ orchestrator_tokens: Tokens in main orchestrator context
701
+ subagent_tokens: Tokens in delegated subagent contexts
702
+ waste_tokens: Tokens wasted on suboptimal decisions
703
+ optimal_tokens: What it would have cost with optimal delegation
704
+ efficiency_score: 0-100 efficiency rating
705
+ waste_percentage: Waste as percentage of total
706
+ """
707
+
708
+ total_tokens: int
709
+ orchestrator_tokens: int
710
+ subagent_tokens: int
711
+
712
+ waste_tokens: int
713
+ optimal_tokens: int
714
+
715
+ efficiency_score: float = 0.0
716
+ waste_percentage: float = 0.0
717
+
718
+ def to_dict(self) -> dict:
719
+ """Convert to dictionary."""
720
+ return asdict(self)
721
+
722
+ def to_json(self) -> str:
723
+ """Convert to JSON string."""
724
+ return json.dumps(self.to_dict())
725
+
726
+ @classmethod
727
+ def from_dict(cls, data: dict) -> "CostMetrics":
728
+ """Create from dictionary."""
729
+ return cls(**data)
730
+
731
+ @classmethod
732
+ def from_json(cls, json_str: str) -> "CostMetrics":
733
+ """Create from JSON string."""
734
+ return cls.from_dict(json.loads(json_str))
735
+
736
+ def __str__(self) -> str:
737
+ """Human-readable representation."""
738
+ return (
739
+ f"CostMetrics: {self.total_tokens} tokens\n"
740
+ f" Orchestrator: {self.orchestrator_tokens}\n"
741
+ f" Subagents: {self.subagent_tokens}\n"
742
+ f" Waste: {self.waste_tokens} tokens ({self.waste_percentage:.1f}%)\n"
743
+ f" Efficiency: {self.efficiency_score}/100"
744
+ )
745
+
746
+ def validate(self) -> tuple[bool, str]:
747
+ """Validate cost metrics integrity.
748
+
749
+ Returns:
750
+ Tuple of (is_valid, error_message)
751
+ """
752
+ if self.total_tokens < 0:
753
+ return False, "total_tokens cannot be negative"
754
+ if self.orchestrator_tokens < 0:
755
+ return False, "orchestrator_tokens cannot be negative"
756
+ if self.subagent_tokens < 0:
757
+ return False, "subagent_tokens cannot be negative"
758
+ if self.waste_tokens < 0:
759
+ return False, "waste_tokens cannot be negative"
760
+ if self.optimal_tokens < 0:
761
+ return False, "optimal_tokens cannot be negative"
762
+ if not (0.0 <= self.efficiency_score <= 100.0):
763
+ return False, "efficiency_score must be between 0 and 100"
764
+ if not (0.0 <= self.waste_percentage <= 100.0):
765
+ return False, "waste_percentage must be between 0 and 100"
766
+ if (self.orchestrator_tokens + self.subagent_tokens) > self.total_tokens:
767
+ return (
768
+ False,
769
+ "orchestrator + subagent tokens cannot exceed total",
770
+ )
771
+ return True, ""