htmlgraph 0.9.3__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 (331) 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 +173 -17
  5. htmlgraph/__init__.pyi +123 -0
  6. htmlgraph/agent_detection.py +127 -0
  7. htmlgraph/agent_registry.py +45 -30
  8. htmlgraph/agents.py +160 -107
  9. htmlgraph/analytics/__init__.py +9 -2
  10. htmlgraph/analytics/cli.py +190 -51
  11. htmlgraph/analytics/cost_analyzer.py +391 -0
  12. htmlgraph/analytics/cost_monitor.py +664 -0
  13. htmlgraph/analytics/cost_reporter.py +675 -0
  14. htmlgraph/analytics/cross_session.py +617 -0
  15. htmlgraph/analytics/dependency.py +192 -100
  16. htmlgraph/analytics/pattern_learning.py +771 -0
  17. htmlgraph/analytics/session_graph.py +707 -0
  18. htmlgraph/analytics/strategic/__init__.py +80 -0
  19. htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
  20. htmlgraph/analytics/strategic/pattern_detector.py +876 -0
  21. htmlgraph/analytics/strategic/preference_manager.py +709 -0
  22. htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
  23. htmlgraph/analytics/work_type.py +190 -14
  24. htmlgraph/analytics_index.py +135 -51
  25. htmlgraph/api/__init__.py +3 -0
  26. htmlgraph/api/cost_alerts_websocket.py +416 -0
  27. htmlgraph/api/main.py +2498 -0
  28. htmlgraph/api/static/htmx.min.js +1 -0
  29. htmlgraph/api/static/style-redesign.css +1344 -0
  30. htmlgraph/api/static/style.css +1079 -0
  31. htmlgraph/api/templates/dashboard-redesign.html +1366 -0
  32. htmlgraph/api/templates/dashboard.html +794 -0
  33. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  34. htmlgraph/api/templates/partials/activity-feed.html +1100 -0
  35. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  36. htmlgraph/api/templates/partials/agents.html +317 -0
  37. htmlgraph/api/templates/partials/event-traces.html +373 -0
  38. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  39. htmlgraph/api/templates/partials/features.html +578 -0
  40. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  41. htmlgraph/api/templates/partials/metrics.html +346 -0
  42. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  43. htmlgraph/api/templates/partials/orchestration.html +198 -0
  44. htmlgraph/api/templates/partials/spawners.html +375 -0
  45. htmlgraph/api/templates/partials/work-items.html +613 -0
  46. htmlgraph/api/websocket.py +538 -0
  47. htmlgraph/archive/__init__.py +24 -0
  48. htmlgraph/archive/bloom.py +234 -0
  49. htmlgraph/archive/fts.py +297 -0
  50. htmlgraph/archive/manager.py +583 -0
  51. htmlgraph/archive/search.py +244 -0
  52. htmlgraph/atomic_ops.py +560 -0
  53. htmlgraph/attribute_index.py +208 -0
  54. htmlgraph/bounded_paths.py +539 -0
  55. htmlgraph/builders/__init__.py +14 -0
  56. htmlgraph/builders/base.py +118 -29
  57. htmlgraph/builders/bug.py +150 -0
  58. htmlgraph/builders/chore.py +119 -0
  59. htmlgraph/builders/epic.py +150 -0
  60. htmlgraph/builders/feature.py +31 -6
  61. htmlgraph/builders/insight.py +195 -0
  62. htmlgraph/builders/metric.py +217 -0
  63. htmlgraph/builders/pattern.py +202 -0
  64. htmlgraph/builders/phase.py +162 -0
  65. htmlgraph/builders/spike.py +52 -19
  66. htmlgraph/builders/track.py +148 -72
  67. htmlgraph/cigs/__init__.py +81 -0
  68. htmlgraph/cigs/autonomy.py +385 -0
  69. htmlgraph/cigs/cost.py +475 -0
  70. htmlgraph/cigs/messages_basic.py +472 -0
  71. htmlgraph/cigs/messaging.py +365 -0
  72. htmlgraph/cigs/models.py +771 -0
  73. htmlgraph/cigs/pattern_storage.py +427 -0
  74. htmlgraph/cigs/patterns.py +503 -0
  75. htmlgraph/cigs/posttool_analyzer.py +234 -0
  76. htmlgraph/cigs/reporter.py +818 -0
  77. htmlgraph/cigs/tracker.py +317 -0
  78. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  79. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  80. htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
  81. htmlgraph/cli/__init__.py +42 -0
  82. htmlgraph/cli/__main__.py +6 -0
  83. htmlgraph/cli/analytics.py +1424 -0
  84. htmlgraph/cli/base.py +685 -0
  85. htmlgraph/cli/constants.py +206 -0
  86. htmlgraph/cli/core.py +954 -0
  87. htmlgraph/cli/main.py +147 -0
  88. htmlgraph/cli/models.py +475 -0
  89. htmlgraph/cli/templates/__init__.py +1 -0
  90. htmlgraph/cli/templates/cost_dashboard.py +399 -0
  91. htmlgraph/cli/work/__init__.py +239 -0
  92. htmlgraph/cli/work/browse.py +115 -0
  93. htmlgraph/cli/work/features.py +568 -0
  94. htmlgraph/cli/work/orchestration.py +676 -0
  95. htmlgraph/cli/work/report.py +728 -0
  96. htmlgraph/cli/work/sessions.py +466 -0
  97. htmlgraph/cli/work/snapshot.py +559 -0
  98. htmlgraph/cli/work/tracks.py +486 -0
  99. htmlgraph/cli_commands/__init__.py +1 -0
  100. htmlgraph/cli_commands/feature.py +195 -0
  101. htmlgraph/cli_framework.py +115 -0
  102. htmlgraph/collections/__init__.py +18 -0
  103. htmlgraph/collections/base.py +415 -98
  104. htmlgraph/collections/bug.py +53 -0
  105. htmlgraph/collections/chore.py +53 -0
  106. htmlgraph/collections/epic.py +53 -0
  107. htmlgraph/collections/feature.py +12 -26
  108. htmlgraph/collections/insight.py +100 -0
  109. htmlgraph/collections/metric.py +92 -0
  110. htmlgraph/collections/pattern.py +97 -0
  111. htmlgraph/collections/phase.py +53 -0
  112. htmlgraph/collections/session.py +194 -0
  113. htmlgraph/collections/spike.py +56 -16
  114. htmlgraph/collections/task_delegation.py +241 -0
  115. htmlgraph/collections/todo.py +511 -0
  116. htmlgraph/collections/traces.py +487 -0
  117. htmlgraph/config/cost_models.json +56 -0
  118. htmlgraph/config.py +190 -0
  119. htmlgraph/context_analytics.py +344 -0
  120. htmlgraph/converter.py +216 -28
  121. htmlgraph/cost_analysis/__init__.py +5 -0
  122. htmlgraph/cost_analysis/analyzer.py +438 -0
  123. htmlgraph/dashboard.html +2406 -307
  124. htmlgraph/dashboard.html.backup +6592 -0
  125. htmlgraph/dashboard.html.bak +7181 -0
  126. htmlgraph/dashboard.html.bak2 +7231 -0
  127. htmlgraph/dashboard.html.bak3 +7232 -0
  128. htmlgraph/db/__init__.py +38 -0
  129. htmlgraph/db/queries.py +790 -0
  130. htmlgraph/db/schema.py +1788 -0
  131. htmlgraph/decorators.py +317 -0
  132. htmlgraph/dependency_models.py +19 -2
  133. htmlgraph/deploy.py +142 -125
  134. htmlgraph/deployment_models.py +474 -0
  135. htmlgraph/docs/API_REFERENCE.md +841 -0
  136. htmlgraph/docs/HTTP_API.md +750 -0
  137. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  138. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
  139. htmlgraph/docs/README.md +532 -0
  140. htmlgraph/docs/__init__.py +77 -0
  141. htmlgraph/docs/docs_version.py +55 -0
  142. htmlgraph/docs/metadata.py +93 -0
  143. htmlgraph/docs/migrations.py +232 -0
  144. htmlgraph/docs/template_engine.py +143 -0
  145. htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
  146. htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
  147. htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
  148. htmlgraph/docs/templates/base_agents.md.j2 +78 -0
  149. htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
  150. htmlgraph/docs/version_check.py +163 -0
  151. htmlgraph/edge_index.py +182 -27
  152. htmlgraph/error_handler.py +544 -0
  153. htmlgraph/event_log.py +100 -52
  154. htmlgraph/event_migration.py +13 -4
  155. htmlgraph/exceptions.py +49 -0
  156. htmlgraph/file_watcher.py +101 -28
  157. htmlgraph/find_api.py +75 -63
  158. htmlgraph/git_events.py +145 -63
  159. htmlgraph/graph.py +1122 -106
  160. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  161. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  162. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  163. htmlgraph/hooks/__init__.py +45 -0
  164. htmlgraph/hooks/bootstrap.py +169 -0
  165. htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
  166. htmlgraph/hooks/concurrent_sessions.py +208 -0
  167. htmlgraph/hooks/context.py +350 -0
  168. htmlgraph/hooks/drift_handler.py +525 -0
  169. htmlgraph/hooks/event_tracker.py +1314 -0
  170. htmlgraph/hooks/git_commands.py +175 -0
  171. htmlgraph/hooks/hooks-config.example.json +12 -0
  172. htmlgraph/hooks/installer.py +343 -0
  173. htmlgraph/hooks/orchestrator.py +674 -0
  174. htmlgraph/hooks/orchestrator_reflector.py +223 -0
  175. htmlgraph/hooks/post-checkout.sh +28 -0
  176. htmlgraph/hooks/post-commit.sh +24 -0
  177. htmlgraph/hooks/post-merge.sh +26 -0
  178. htmlgraph/hooks/post_tool_use_failure.py +273 -0
  179. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  180. htmlgraph/hooks/posttooluse.py +408 -0
  181. htmlgraph/hooks/pre-commit.sh +94 -0
  182. htmlgraph/hooks/pre-push.sh +28 -0
  183. htmlgraph/hooks/pretooluse.py +819 -0
  184. htmlgraph/hooks/prompt_analyzer.py +637 -0
  185. htmlgraph/hooks/session_handler.py +668 -0
  186. htmlgraph/hooks/session_summary.py +395 -0
  187. htmlgraph/hooks/state_manager.py +504 -0
  188. htmlgraph/hooks/subagent_detection.py +202 -0
  189. htmlgraph/hooks/subagent_stop.py +369 -0
  190. htmlgraph/hooks/task_enforcer.py +255 -0
  191. htmlgraph/hooks/task_validator.py +177 -0
  192. htmlgraph/hooks/validator.py +628 -0
  193. htmlgraph/ids.py +41 -27
  194. htmlgraph/index.d.ts +286 -0
  195. htmlgraph/learning.py +767 -0
  196. htmlgraph/mcp_server.py +69 -23
  197. htmlgraph/models.py +1586 -87
  198. htmlgraph/operations/README.md +62 -0
  199. htmlgraph/operations/__init__.py +79 -0
  200. htmlgraph/operations/analytics.py +339 -0
  201. htmlgraph/operations/bootstrap.py +289 -0
  202. htmlgraph/operations/events.py +244 -0
  203. htmlgraph/operations/fastapi_server.py +231 -0
  204. htmlgraph/operations/hooks.py +350 -0
  205. htmlgraph/operations/initialization.py +597 -0
  206. htmlgraph/operations/initialization.py.backup +228 -0
  207. htmlgraph/operations/server.py +303 -0
  208. htmlgraph/orchestration/__init__.py +58 -0
  209. htmlgraph/orchestration/claude_launcher.py +179 -0
  210. htmlgraph/orchestration/command_builder.py +72 -0
  211. htmlgraph/orchestration/headless_spawner.py +281 -0
  212. htmlgraph/orchestration/live_events.py +377 -0
  213. htmlgraph/orchestration/model_selection.py +327 -0
  214. htmlgraph/orchestration/plugin_manager.py +140 -0
  215. htmlgraph/orchestration/prompts.py +137 -0
  216. htmlgraph/orchestration/spawner_event_tracker.py +383 -0
  217. htmlgraph/orchestration/spawners/__init__.py +16 -0
  218. htmlgraph/orchestration/spawners/base.py +194 -0
  219. htmlgraph/orchestration/spawners/claude.py +173 -0
  220. htmlgraph/orchestration/spawners/codex.py +435 -0
  221. htmlgraph/orchestration/spawners/copilot.py +294 -0
  222. htmlgraph/orchestration/spawners/gemini.py +471 -0
  223. htmlgraph/orchestration/subprocess_runner.py +36 -0
  224. htmlgraph/orchestration/task_coordination.py +343 -0
  225. htmlgraph/orchestration.md +563 -0
  226. htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
  227. htmlgraph/orchestrator.py +669 -0
  228. htmlgraph/orchestrator_config.py +357 -0
  229. htmlgraph/orchestrator_mode.py +328 -0
  230. htmlgraph/orchestrator_validator.py +133 -0
  231. htmlgraph/parallel.py +646 -0
  232. htmlgraph/parser.py +160 -35
  233. htmlgraph/path_query.py +608 -0
  234. htmlgraph/pattern_matcher.py +636 -0
  235. htmlgraph/planning.py +147 -52
  236. htmlgraph/pydantic_models.py +476 -0
  237. htmlgraph/quality_gates.py +350 -0
  238. htmlgraph/query_builder.py +109 -72
  239. htmlgraph/query_composer.py +509 -0
  240. htmlgraph/reflection.py +443 -0
  241. htmlgraph/refs.py +344 -0
  242. htmlgraph/repo_hash.py +512 -0
  243. htmlgraph/repositories/__init__.py +292 -0
  244. htmlgraph/repositories/analytics_repository.py +455 -0
  245. htmlgraph/repositories/analytics_repository_standard.py +628 -0
  246. htmlgraph/repositories/feature_repository.py +581 -0
  247. htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
  248. htmlgraph/repositories/feature_repository_memory.py +607 -0
  249. htmlgraph/repositories/feature_repository_sqlite.py +858 -0
  250. htmlgraph/repositories/filter_service.py +620 -0
  251. htmlgraph/repositories/filter_service_standard.py +445 -0
  252. htmlgraph/repositories/shared_cache.py +621 -0
  253. htmlgraph/repositories/shared_cache_memory.py +395 -0
  254. htmlgraph/repositories/track_repository.py +552 -0
  255. htmlgraph/repositories/track_repository_htmlfile.py +619 -0
  256. htmlgraph/repositories/track_repository_memory.py +508 -0
  257. htmlgraph/repositories/track_repository_sqlite.py +711 -0
  258. htmlgraph/routing.py +8 -19
  259. htmlgraph/scripts/deploy.py +1 -2
  260. htmlgraph/sdk/__init__.py +398 -0
  261. htmlgraph/sdk/__init__.pyi +14 -0
  262. htmlgraph/sdk/analytics/__init__.py +19 -0
  263. htmlgraph/sdk/analytics/engine.py +155 -0
  264. htmlgraph/sdk/analytics/helpers.py +178 -0
  265. htmlgraph/sdk/analytics/registry.py +109 -0
  266. htmlgraph/sdk/base.py +484 -0
  267. htmlgraph/sdk/constants.py +216 -0
  268. htmlgraph/sdk/core.pyi +308 -0
  269. htmlgraph/sdk/discovery.py +120 -0
  270. htmlgraph/sdk/help/__init__.py +12 -0
  271. htmlgraph/sdk/help/mixin.py +699 -0
  272. htmlgraph/sdk/mixins/__init__.py +15 -0
  273. htmlgraph/sdk/mixins/attribution.py +113 -0
  274. htmlgraph/sdk/mixins/mixin.py +410 -0
  275. htmlgraph/sdk/operations/__init__.py +12 -0
  276. htmlgraph/sdk/operations/mixin.py +427 -0
  277. htmlgraph/sdk/orchestration/__init__.py +17 -0
  278. htmlgraph/sdk/orchestration/coordinator.py +203 -0
  279. htmlgraph/sdk/orchestration/spawner.py +204 -0
  280. htmlgraph/sdk/planning/__init__.py +19 -0
  281. htmlgraph/sdk/planning/bottlenecks.py +93 -0
  282. htmlgraph/sdk/planning/mixin.py +211 -0
  283. htmlgraph/sdk/planning/parallel.py +186 -0
  284. htmlgraph/sdk/planning/queue.py +210 -0
  285. htmlgraph/sdk/planning/recommendations.py +87 -0
  286. htmlgraph/sdk/planning/smart_planning.py +319 -0
  287. htmlgraph/sdk/session/__init__.py +19 -0
  288. htmlgraph/sdk/session/continuity.py +57 -0
  289. htmlgraph/sdk/session/handoff.py +110 -0
  290. htmlgraph/sdk/session/info.py +309 -0
  291. htmlgraph/sdk/session/manager.py +103 -0
  292. htmlgraph/sdk/strategic/__init__.py +26 -0
  293. htmlgraph/sdk/strategic/mixin.py +563 -0
  294. htmlgraph/server.py +685 -180
  295. htmlgraph/services/__init__.py +10 -0
  296. htmlgraph/services/claiming.py +199 -0
  297. htmlgraph/session_hooks.py +300 -0
  298. htmlgraph/session_manager.py +1392 -175
  299. htmlgraph/session_registry.py +587 -0
  300. htmlgraph/session_state.py +436 -0
  301. htmlgraph/session_warning.py +201 -0
  302. htmlgraph/sessions/__init__.py +23 -0
  303. htmlgraph/sessions/handoff.py +756 -0
  304. htmlgraph/setup.py +34 -17
  305. htmlgraph/spike_index.py +143 -0
  306. htmlgraph/sync_docs.py +12 -15
  307. htmlgraph/system_prompts.py +450 -0
  308. htmlgraph/templates/AGENTS.md.template +366 -0
  309. htmlgraph/templates/CLAUDE.md.template +97 -0
  310. htmlgraph/templates/GEMINI.md.template +87 -0
  311. htmlgraph/templates/orchestration-view.html +350 -0
  312. htmlgraph/track_builder.py +146 -15
  313. htmlgraph/track_manager.py +69 -21
  314. htmlgraph/transcript.py +890 -0
  315. htmlgraph/transcript_analytics.py +699 -0
  316. htmlgraph/types.py +323 -0
  317. htmlgraph/validation.py +115 -0
  318. htmlgraph/watch.py +8 -5
  319. htmlgraph/work_type_utils.py +3 -2
  320. {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
  321. htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
  322. htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
  323. htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
  324. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
  325. htmlgraph-0.27.5.dist-info/RECORD +337 -0
  326. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
  327. htmlgraph/cli.py +0 -2688
  328. htmlgraph/sdk.py +0 -709
  329. htmlgraph-0.9.3.dist-info/RECORD +0 -61
  330. {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
  331. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
htmlgraph/cigs/cost.py ADDED
@@ -0,0 +1,475 @@
1
+ """
2
+ CostCalculator for CIGS (Computational Imperative Guidance System).
3
+
4
+ Provides token cost prediction and actual cost tracking for tool operations.
5
+ Implements cost estimation heuristics based on tool type and operation complexity.
6
+
7
+ Design Reference:
8
+ - CIGS Design Doc: .htmlgraph/spikes/computational-imperative-guidance-system-design.md
9
+ - Part 5.2: Cost Efficiency Score
10
+ - Part 5.3: Cost estimation rules
11
+ """
12
+
13
+ from .models import (
14
+ CostMetrics,
15
+ OperationClassification,
16
+ TokenCost,
17
+ )
18
+
19
+
20
+ class CostCalculator:
21
+ """Calculate token costs for tool operations and delegations."""
22
+
23
+ # Cost estimation heuristics (tokens per operation)
24
+ # Based on typical token consumption patterns
25
+ COST_ESTIMATES = {
26
+ "Read": 5000, # Per file read
27
+ "Grep": 3000, # Per search result batch
28
+ "Glob": 2000, # Per glob pattern
29
+ "Edit": 4000, # Per edit operation
30
+ "Write": 4000, # Per write operation
31
+ "NotebookEdit": 4500, # Notebook cells are complex
32
+ "Delete": 2000, # File deletion
33
+ "Bash": { # Variable based on command type
34
+ "default": 2000,
35
+ "git": 1500,
36
+ "pytest": 5000,
37
+ "npm": 5000,
38
+ },
39
+ "Task": 500, # Orchestrator Task call (minimal context)
40
+ "AskUserQuestion": 1000, # User interaction
41
+ "TodoWrite": 500, # Tracking operations
42
+ }
43
+
44
+ # Subagent delegation costs (optimal path)
45
+ SUBAGENT_COSTS = {
46
+ "spawn_gemini": 500, # Explorer subagent cost
47
+ "spawn_codex": 800, # Coder subagent cost
48
+ "spawn_copilot": 600, # Git subagent cost
49
+ "Task": 500, # Orchestrator Task
50
+ }
51
+
52
+ def __init__(self) -> None:
53
+ """Initialize CostCalculator."""
54
+ self.tool_history: list[str] = [] # Track recent tool usage for patterns
55
+
56
+ def predict_cost(self, tool: str, params: dict) -> int:
57
+ """
58
+ Predict token cost for direct tool execution.
59
+
60
+ Args:
61
+ tool: Tool name (Read, Grep, Bash, etc.)
62
+ params: Tool parameters
63
+
64
+ Returns:
65
+ Estimated token cost
66
+ """
67
+ if tool not in self.COST_ESTIMATES:
68
+ return 2000 # Default estimate for unknown tools
69
+
70
+ estimate = self.COST_ESTIMATES[tool]
71
+
72
+ # Handle variable estimates
73
+ if isinstance(estimate, dict):
74
+ return self._estimate_variable_cost(tool, params, estimate)
75
+
76
+ # Apply modifiers based on operation complexity
77
+ assert isinstance(estimate, int)
78
+ return self._apply_complexity_modifiers(tool, params, estimate)
79
+
80
+ def _estimate_variable_cost(
81
+ self, tool: str, params: dict, estimates: dict[str, int]
82
+ ) -> int:
83
+ """Estimate cost for tools with variable pricing."""
84
+ if tool == "Bash":
85
+ command = params.get("command", "")
86
+
87
+ # Git operations
88
+ if any(cmd in command for cmd in ["git add", "git commit", "git push"]):
89
+ return int(estimates.get("git", estimates.get("default", 2000)))
90
+
91
+ # Test operations
92
+ if any(cmd in command for cmd in ["pytest", "uv run pytest"]):
93
+ return int(estimates.get("pytest", estimates.get("default", 5000)))
94
+
95
+ if "npm test" in command or "yarn test" in command:
96
+ return int(estimates.get("npm", estimates.get("default", 5000)))
97
+
98
+ # Default bash
99
+ return int(estimates.get("default", 2000))
100
+
101
+ return int(estimates.get("default", 2000))
102
+
103
+ def _apply_complexity_modifiers(
104
+ self, tool: str, params: dict, base_cost: int | dict[str, int]
105
+ ) -> int:
106
+ """Apply complexity modifiers to base cost estimate."""
107
+ # Handle dict base_cost (shouldn't happen but for safety)
108
+ if isinstance(base_cost, dict):
109
+ base_cost = base_cost.get("default", 2000)
110
+
111
+ modified_cost: float = float(base_cost)
112
+
113
+ if tool == "Read":
114
+ # Multiple files increase cost
115
+ if isinstance(params.get("file_path"), list):
116
+ modified_cost = base_cost * len(params["file_path"])
117
+ # Large offset/limits indicate large reads
118
+ elif params.get("limit", 2000) > 5000:
119
+ modified_cost = base_cost * 2
120
+
121
+ elif tool == "Grep":
122
+ # Complex regex patterns increase cost
123
+ pattern = params.get("pattern", "")
124
+ if len(pattern) > 100: # Complex pattern
125
+ modified_cost = base_cost * 1.5
126
+ # Multiline matching is more expensive
127
+ if params.get("multiline", False):
128
+ modified_cost = base_cost * 1.3
129
+
130
+ elif tool in ["Edit", "Write"]:
131
+ # Multiple edits or large content
132
+ if isinstance(params.get("file_path"), list):
133
+ modified_cost = base_cost * len(params["file_path"])
134
+ # Large content increases cost
135
+ content = params.get("content", params.get("new_string", ""))
136
+ if len(content) > 10000:
137
+ modified_cost = base_cost * 1.5
138
+
139
+ return int(modified_cost)
140
+
141
+ def optimal_cost(self, classification: OperationClassification) -> int:
142
+ """
143
+ Calculate optimal cost with proper delegation.
144
+
145
+ Args:
146
+ classification: OperationClassification with tool and category info
147
+
148
+ Returns:
149
+ Estimated token cost with optimal delegation strategy
150
+ """
151
+ tool = classification.tool
152
+
153
+ # Orchestrator tools already optimal
154
+ if tool in ["Task", "AskUserQuestion", "TodoWrite"]:
155
+ estimate = self.COST_ESTIMATES.get(tool, 500)
156
+ return int(estimate) if isinstance(estimate, int) else 500
157
+
158
+ # Map to subagent based on category
159
+ if classification.category == "exploration":
160
+ return int(self.SUBAGENT_COSTS["spawn_gemini"])
161
+ elif classification.category == "implementation":
162
+ return int(self.SUBAGENT_COSTS["spawn_codex"])
163
+ elif classification.category == "git":
164
+ return int(self.SUBAGENT_COSTS["spawn_copilot"])
165
+ elif classification.category == "testing":
166
+ return int(self.SUBAGENT_COSTS["Task"])
167
+
168
+ # Default to Task for unknown categories
169
+ return int(self.SUBAGENT_COSTS["Task"])
170
+
171
+ def calculate_actual_cost(self, tool: str, result: dict) -> TokenCost:
172
+ """
173
+ Calculate actual cost after tool execution.
174
+
175
+ Args:
176
+ tool: Tool that was executed
177
+ result: Result dictionary from tool execution
178
+
179
+ Returns:
180
+ TokenCost with actual metrics
181
+ """
182
+ # Get predicted cost for this tool
183
+ predicted_tokens = self._extract_predicted_cost(tool, result)
184
+
185
+ # Extract actual cost if available in result
186
+ actual_tokens = self._extract_actual_cost(tool, result)
187
+
188
+ # If no actual cost in result, use predicted
189
+ if actual_tokens is None:
190
+ actual_tokens = predicted_tokens
191
+
192
+ # Determine subagent cost based on tool type
193
+ subagent_tokens = self._get_subagent_cost(tool)
194
+
195
+ # Calculate orchestrator overhead
196
+ orchestrator_tokens = self._estimate_orchestrator_overhead(tool, result)
197
+
198
+ # Calculate savings
199
+ estimated_savings = max(
200
+ 0, actual_tokens - subagent_tokens - orchestrator_tokens
201
+ )
202
+
203
+ return TokenCost(
204
+ total_tokens=actual_tokens,
205
+ orchestrator_tokens=orchestrator_tokens,
206
+ subagent_tokens=subagent_tokens,
207
+ estimated_savings=estimated_savings,
208
+ )
209
+
210
+ def _extract_predicted_cost(self, tool: str, result: dict) -> int:
211
+ """Extract or estimate predicted cost from result."""
212
+ # Check if result contains cost metadata
213
+ if "predicted_cost" in result:
214
+ return int(result["predicted_cost"])
215
+
216
+ if "metadata" in result and "predicted_cost" in result["metadata"]:
217
+ return int(result["metadata"]["predicted_cost"])
218
+
219
+ # Fall back to default estimate
220
+ estimate = self.COST_ESTIMATES.get(tool, 2000)
221
+ return int(estimate) if isinstance(estimate, int) else 2000
222
+
223
+ def _extract_actual_cost(self, tool: str, result: dict) -> int | None:
224
+ """Extract actual cost from execution result if available."""
225
+ # Check standard cost fields
226
+ if "actual_cost" in result:
227
+ return int(result["actual_cost"])
228
+
229
+ if "cost" in result:
230
+ return int(result["cost"])
231
+
232
+ if "metadata" in result and "cost" in result["metadata"]:
233
+ return int(result["metadata"]["cost"])
234
+
235
+ if "tokens" in result:
236
+ return int(result["tokens"])
237
+
238
+ # Try to estimate from output size for Read operations
239
+ if tool == "Read" and "output" in result:
240
+ output = result["output"]
241
+ if isinstance(output, str):
242
+ # Rough estimate: ~4 tokens per line
243
+ lines = len(output.split("\n"))
244
+ return int(lines * 4)
245
+
246
+ return None
247
+
248
+ def _get_subagent_cost(self, tool: str) -> int:
249
+ """Get cost if this operation were delegated to subagent."""
250
+ if tool in ["Task", "AskUserQuestion", "TodoWrite"]:
251
+ return 0 # Already delegated
252
+
253
+ if tool in ["Read", "Grep", "Glob"]:
254
+ return self.SUBAGENT_COSTS["spawn_gemini"]
255
+
256
+ if tool in ["Edit", "Write", "NotebookEdit", "Delete"]:
257
+ return self.SUBAGENT_COSTS["spawn_codex"]
258
+
259
+ if tool == "Bash":
260
+ # Might be git or test - estimate higher
261
+ return self.SUBAGENT_COSTS["spawn_copilot"] # or Task
262
+
263
+ # Default
264
+ return self.SUBAGENT_COSTS["Task"]
265
+
266
+ def _estimate_orchestrator_overhead(self, tool: str, result: dict) -> int:
267
+ """Estimate orchestrator context overhead."""
268
+ # Orchestrator overhead is minimal for delegated operations
269
+ if tool in ["Task", "AskUserQuestion"]:
270
+ return 200 # Small overhead for delegation call
271
+
272
+ # Direct execution contributes full cost to orchestrator context
273
+ if tool == "Read":
274
+ # Context cost is proportional to file size
275
+ if "output" in result:
276
+ output = result["output"]
277
+ if isinstance(output, str):
278
+ # ~4 tokens per line
279
+ return int(len(output.split("\n")) * 4)
280
+ return 5000
281
+
282
+ # Other tools
283
+ return 1000 # Placeholder for other tools
284
+
285
+ def calculate_waste(self, actual_cost: int, optimal_cost: int) -> dict:
286
+ """
287
+ Calculate waste metrics comparing actual vs optimal cost.
288
+
289
+ Args:
290
+ actual_cost: Actual tokens consumed
291
+ optimal_cost: Tokens with optimal delegation
292
+
293
+ Returns:
294
+ Dictionary with waste metrics
295
+ """
296
+ waste_tokens = max(0, actual_cost - optimal_cost)
297
+
298
+ if actual_cost == 0:
299
+ waste_percentage = 0.0
300
+ efficiency_score = 100.0
301
+ else:
302
+ waste_percentage = (waste_tokens / actual_cost) * 100
303
+ efficiency_score = (optimal_cost / actual_cost) * 100
304
+
305
+ return {
306
+ "waste_tokens": waste_tokens,
307
+ "waste_percentage": waste_percentage,
308
+ "efficiency_score": efficiency_score,
309
+ }
310
+
311
+ def aggregate_session_costs(
312
+ self,
313
+ operations: list[tuple[str, dict, dict]],
314
+ violations_count: int = 0,
315
+ ) -> CostMetrics:
316
+ """
317
+ Aggregate cost metrics for a session.
318
+
319
+ Args:
320
+ operations: List of (tool, params, result) tuples
321
+ violations_count: Number of violations in session
322
+
323
+ Returns:
324
+ CostMetrics with aggregated session costs
325
+ """
326
+ total_tokens = 0
327
+ optimal_tokens = 0
328
+ orchestrator_tokens = 0
329
+ subagent_tokens = 0
330
+
331
+ for tool, params, result in operations:
332
+ # Predict cost
333
+ predicted = self.predict_cost(tool, params)
334
+ total_tokens += predicted
335
+
336
+ # Calculate actual if available
337
+ cost_record = self.calculate_actual_cost(tool, result)
338
+ total_tokens = max(total_tokens, cost_record.total_tokens)
339
+
340
+ # Accumulate subagent costs
341
+ subagent_tokens += cost_record.subagent_tokens
342
+ orchestrator_tokens += cost_record.orchestrator_tokens
343
+
344
+ # Accumulate optimal costs
345
+ # For delegated operations, optimal = subagent cost
346
+ # For direct operations that should be delegated, optimal = subagent cost
347
+ if tool in ["Task", "AskUserQuestion"]:
348
+ estimate = self.COST_ESTIMATES.get(tool, 500)
349
+ optimal_tokens += int(estimate) if isinstance(estimate, int) else 500
350
+ else:
351
+ optimal_tokens += self._get_subagent_cost(tool)
352
+
353
+ waste_tokens = max(0, total_tokens - optimal_tokens)
354
+
355
+ metrics = CostMetrics(
356
+ total_tokens=total_tokens,
357
+ optimal_tokens=optimal_tokens,
358
+ orchestrator_tokens=orchestrator_tokens,
359
+ subagent_tokens=subagent_tokens,
360
+ waste_tokens=waste_tokens,
361
+ )
362
+
363
+ return metrics
364
+
365
+ def classify_operation(
366
+ self,
367
+ tool: str,
368
+ params: dict,
369
+ is_exploration_sequence: bool = False,
370
+ tool_history: list[str] | None = None,
371
+ ) -> OperationClassification:
372
+ """
373
+ Classify a tool operation for cost and delegation analysis.
374
+
375
+ Args:
376
+ tool: Tool name
377
+ params: Tool parameters
378
+ is_exploration_sequence: Whether this is part of exploration sequence
379
+ tool_history: Recent tool usage history
380
+
381
+ Returns:
382
+ OperationClassification with cost and delegation info
383
+ """
384
+ # Categorize tool
385
+ category = self._categorize_tool(tool)
386
+
387
+ # Determine if delegation is recommended
388
+ should_delegate = self._should_delegate(tool, is_exploration_sequence)
389
+
390
+ # Get delegation suggestion
391
+ suggestion = self._get_delegation_suggestion(tool, category)
392
+
393
+ # Predict costs
394
+ predicted_cost = self.predict_cost(tool, params)
395
+
396
+ # Calculate optimal cost for this category
397
+ dummy_classification = OperationClassification(
398
+ tool=tool,
399
+ category="", # Will be set below
400
+ should_delegate=should_delegate,
401
+ reason="",
402
+ predicted_cost=predicted_cost,
403
+ optimal_cost=0,
404
+ is_exploration_sequence=is_exploration_sequence,
405
+ suggested_delegation=suggestion,
406
+ )
407
+
408
+ optimal_cost = self.optimal_cost(dummy_classification)
409
+
410
+ # Calculate waste percentage
411
+ waste_tokens = max(0, predicted_cost - optimal_cost)
412
+ waste_pct = (waste_tokens / predicted_cost * 100) if predicted_cost > 0 else 0.0
413
+
414
+ return OperationClassification(
415
+ tool=tool,
416
+ category=category,
417
+ should_delegate=should_delegate,
418
+ reason=self._get_delegation_reason(tool, category, is_exploration_sequence),
419
+ predicted_cost=predicted_cost,
420
+ optimal_cost=optimal_cost,
421
+ is_exploration_sequence=is_exploration_sequence,
422
+ suggested_delegation=suggestion,
423
+ waste_percentage=waste_pct,
424
+ )
425
+
426
+ def _categorize_tool(self, tool: str) -> str:
427
+ """Categorize tool into operational type."""
428
+ if tool in ["Read", "Grep", "Glob"]:
429
+ return "exploration"
430
+ elif tool in ["Edit", "Write", "NotebookEdit", "Delete"]:
431
+ return "implementation"
432
+ elif tool in ["Task", "AskUserQuestion", "TodoWrite"]:
433
+ return "orchestration"
434
+ elif tool == "Bash":
435
+ return "execution"
436
+ else:
437
+ return "unknown"
438
+
439
+ def _should_delegate(self, tool: str, is_sequence: bool) -> bool:
440
+ """Determine if operation should be delegated."""
441
+ # Orchestrator tools are already delegated
442
+ if tool in ["Task", "AskUserQuestion", "TodoWrite"]:
443
+ return False
444
+
445
+ # Exploration sequences should be delegated
446
+ if is_sequence and tool in ["Read", "Grep", "Glob"]:
447
+ return True
448
+
449
+ # Single direct operations are allowed but not recommended
450
+ # CIGS messaging will recommend delegation
451
+ return False
452
+
453
+ def _get_delegation_suggestion(self, tool: str, category: str) -> str:
454
+ """Get suggested delegation for this tool."""
455
+ suggestions = {
456
+ "exploration": "spawn_gemini(prompt='Search and analyze codebase')",
457
+ "implementation": "spawn_codex(prompt='Implement with full testing')",
458
+ "execution": "Task(prompt='Execute and report results')",
459
+ "testing": "Task(prompt='Run tests and report')",
460
+ }
461
+ return suggestions.get(category, "Task(prompt='Delegate this operation')")
462
+
463
+ def _get_delegation_reason(
464
+ self, tool: str, category: str, is_sequence: bool
465
+ ) -> str:
466
+ """Get reason for delegation recommendation."""
467
+ if is_sequence:
468
+ return f"Multiple {category} operations detected (research work should be delegated)"
469
+
470
+ reasons = {
471
+ "exploration": "Exploration operations have unpredictable scope",
472
+ "implementation": "Implementation requires iteration and testing",
473
+ "execution": "Consider delegating execution for better isolation",
474
+ }
475
+ return reasons.get(category, "Delegation preserves your strategic context")