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,443 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ Computational Reflection Module.
5
+
6
+ Pre-computes actionable context from session history for injection into
7
+ orchestrator prompts. Addresses the LLM limitation where models can only
8
+ effectively track 5-10 variables in working memory.
9
+
10
+ Design Principles:
11
+ 1. COMPUTE, don't prompt - Do the synthesis work here, not in prompts
12
+ 2. LIMIT to 5 items - Respect LLM working memory constraints
13
+ 3. PRIORITIZE by recency and relevance - Most actionable items first
14
+ 4. CONNECT the dots - Surface relationships the model would miss
15
+
16
+ Usage:
17
+ from htmlgraph.reflection import ComputationalReflection
18
+
19
+ reflection = ComputationalReflection(sdk)
20
+ context = reflection.get_actionable_context()
21
+ # Returns: {
22
+ # "summary": "3 blockers, 1 recent failure, avoid Read-Read-Read pattern",
23
+ # "items": [...], # Max 5 items
24
+ # "injected_at": "2025-01-04T12:00:00"
25
+ # }
26
+ """
27
+
28
+
29
+ from dataclasses import dataclass
30
+ from datetime import datetime, timedelta
31
+ from typing import TYPE_CHECKING, Any
32
+
33
+ if TYPE_CHECKING:
34
+ from htmlgraph.sdk import SDK
35
+
36
+
37
+ @dataclass
38
+ class ReflectionItem:
39
+ """A single actionable reflection item."""
40
+
41
+ category: str # "blocker", "failure", "anti_pattern", "spike", "recommendation"
42
+ priority: int # 1-5, higher = more important
43
+ title: str # Brief title
44
+ detail: str # One-line actionable detail
45
+ source_id: str | None = None # ID of source item (feature, spike, etc.)
46
+
47
+ def to_dict(self) -> dict[str, Any]:
48
+ """Convert to dictionary for JSON serialization."""
49
+ return {
50
+ "category": self.category,
51
+ "priority": self.priority,
52
+ "title": self.title,
53
+ "detail": self.detail,
54
+ "source_id": self.source_id,
55
+ }
56
+
57
+
58
+ class ComputationalReflection:
59
+ """
60
+ Computes actionable context from HtmlGraph history.
61
+
62
+ This class addresses the core problem: LLMs can retrieve data but
63
+ struggle to synthesize insights from complex graph structures.
64
+ We do the synthesis here and inject computed results.
65
+
66
+ Example:
67
+ >>> sdk = SDK(agent="claude")
68
+ >>> reflection = ComputationalReflection(sdk)
69
+ >>> context = reflection.get_actionable_context()
70
+ >>> print(context["summary"])
71
+ "2 blockers | Avoid: Edit-Edit-Edit | Related: spk-abc123"
72
+ """
73
+
74
+ MAX_ITEMS = 5 # LLM working memory limit
75
+ LOOKBACK_HOURS = 48 # How far back to look for patterns
76
+
77
+ def __init__(self, sdk: SDK):
78
+ self.sdk = sdk
79
+ self._cache: dict[str, Any] = {}
80
+ self._cache_time: datetime | None = None
81
+ self._cache_ttl = timedelta(minutes=5)
82
+
83
+ def get_actionable_context(
84
+ self,
85
+ current_feature_id: str | None = None,
86
+ current_track: str | None = None,
87
+ ) -> dict[str, Any]:
88
+ """
89
+ Get pre-computed actionable context for injection.
90
+
91
+ This is the main entry point. Returns a structured dict
92
+ suitable for injection into SessionStart or PreToolUse hooks.
93
+
94
+ Args:
95
+ current_feature_id: ID of feature being worked on (if any)
96
+ current_track: Track name for filtering relevant history
97
+
98
+ Returns:
99
+ Dict with summary string and list of max 5 items
100
+ """
101
+ # Check cache
102
+ if self._cache_time and datetime.now() - self._cache_time < self._cache_ttl:
103
+ return self._cache
104
+
105
+ items: list[ReflectionItem] = []
106
+
107
+ # 1. Get blocking items (highest priority)
108
+ items.extend(self._get_blockers(current_feature_id))
109
+
110
+ # 2. Get recent failures
111
+ items.extend(self._get_recent_failures(current_track))
112
+
113
+ # 3. Get anti-patterns to avoid
114
+ items.extend(self._get_anti_patterns())
115
+
116
+ # 4. Get related spikes (investigations)
117
+ items.extend(self._get_related_spikes(current_feature_id, current_track))
118
+
119
+ # 5. Get strategic recommendations
120
+ items.extend(self._get_recommendations())
121
+
122
+ # Sort by priority (highest first) and limit to MAX_ITEMS
123
+ items.sort(key=lambda x: x.priority, reverse=True)
124
+ items = items[: self.MAX_ITEMS]
125
+
126
+ # Build summary string
127
+ summary = self._build_summary(items)
128
+
129
+ result = {
130
+ "summary": summary,
131
+ "items": [item.to_dict() for item in items],
132
+ "injected_at": datetime.now().isoformat(),
133
+ "item_count": len(items),
134
+ }
135
+
136
+ # Cache result
137
+ self._cache = result
138
+ self._cache_time = datetime.now()
139
+
140
+ return result
141
+
142
+ def _get_blockers(self, feature_id: str | None) -> list[ReflectionItem]:
143
+ """Get items blocking current work."""
144
+ items = []
145
+
146
+ try:
147
+ # Use SDK's find_bottlenecks
148
+ bottlenecks = self.sdk.find_bottlenecks(top_n=3)
149
+
150
+ for bn in bottlenecks[:2]: # Max 2 blockers
151
+ items.append(
152
+ ReflectionItem(
153
+ category="blocker",
154
+ priority=5, # Highest priority
155
+ title=f"Blocker: {bn.get('title', 'Unknown')[:40]}",
156
+ detail=f"Blocks {bn.get('blocks_count', 0)} items. Resolve first.",
157
+ source_id=bn.get("id"),
158
+ )
159
+ )
160
+ except Exception:
161
+ pass
162
+
163
+ # Also check for features marked as blocking
164
+ try:
165
+ blocked = self.sdk.features.where(status="blocked")
166
+ for feat in blocked[:1]: # Max 1 blocked feature
167
+ items.append(
168
+ ReflectionItem(
169
+ category="blocker",
170
+ priority=4,
171
+ title=f"Blocked: {feat.title[:40]}",
172
+ detail="Feature is blocked. Check dependencies.",
173
+ source_id=feat.id,
174
+ )
175
+ )
176
+ except Exception:
177
+ pass
178
+
179
+ return items
180
+
181
+ def _get_recent_failures(self, track: str | None) -> list[ReflectionItem]:
182
+ """Get recent failures from session history."""
183
+ items = []
184
+
185
+ try:
186
+ # Get recent sessions
187
+ sessions = self.sdk.sessions.all()
188
+ cutoff = datetime.now() - timedelta(hours=self.LOOKBACK_HOURS)
189
+
190
+ recent_sessions = [
191
+ s
192
+ for s in sessions
193
+ if hasattr(s, "started_at") and s.started_at and s.started_at > cutoff
194
+ ]
195
+
196
+ # Look for error patterns in session activity
197
+ for session in recent_sessions[-3:]: # Last 3 sessions
198
+ if hasattr(session, "activity_log") and session.activity_log:
199
+ for activity in session.activity_log:
200
+ success = (
201
+ activity.success
202
+ if not isinstance(activity, dict)
203
+ else activity.get("success", True)
204
+ )
205
+ if not success:
206
+ tool = (
207
+ activity.tool
208
+ if not isinstance(activity, dict)
209
+ else activity.get("tool", "")
210
+ )
211
+ summary = (
212
+ activity.summary
213
+ if not isinstance(activity, dict)
214
+ else activity.get("summary", "")
215
+ )
216
+
217
+ items.append(
218
+ ReflectionItem(
219
+ category="failure",
220
+ priority=4,
221
+ title=f"Recent failure: {tool}",
222
+ detail=summary[:60]
223
+ if summary
224
+ else "Check session log",
225
+ source_id=session.id,
226
+ )
227
+ )
228
+ break # One failure per session max
229
+ except Exception:
230
+ pass
231
+
232
+ return items[:2] # Max 2 failures
233
+
234
+ def _get_anti_patterns(self) -> list[ReflectionItem]:
235
+ """Get anti-patterns to avoid from recent sessions."""
236
+ items = []
237
+
238
+ try:
239
+ # Import learning module for pattern analysis
240
+ from htmlgraph.learning import LearningPersistence
241
+
242
+ learning = LearningPersistence(self.sdk)
243
+
244
+ # Get active session for analysis
245
+ active_sessions = [
246
+ s for s in self.sdk.sessions.all() if s.status == "active"
247
+ ]
248
+
249
+ if active_sessions:
250
+ # Analyze most recent active session
251
+ session = active_sessions[-1]
252
+ analysis = learning.analyze_for_orchestrator(session.id)
253
+
254
+ # Extract anti-patterns
255
+ for pattern in analysis.get("anti_patterns", [])[:1]:
256
+ seq = pattern.get("sequence", [])
257
+ desc = pattern.get("description", "")
258
+ items.append(
259
+ ReflectionItem(
260
+ category="anti_pattern",
261
+ priority=3,
262
+ title=f"Avoid: {' → '.join(seq)}",
263
+ detail=desc[:60]
264
+ if desc
265
+ else "Detected inefficient pattern",
266
+ source_id=session.id,
267
+ )
268
+ )
269
+ except Exception:
270
+ pass
271
+
272
+ return items[:1] # Max 1 anti-pattern
273
+
274
+ def _get_related_spikes(
275
+ self, feature_id: str | None, track: str | None
276
+ ) -> list[ReflectionItem]:
277
+ """Get related investigation spikes."""
278
+ items = []
279
+
280
+ try:
281
+ spikes = self.sdk.spikes.all()
282
+
283
+ # Find spikes with findings that might be relevant
284
+ relevant_spikes = []
285
+
286
+ for spike in spikes:
287
+ # Check if spike has findings
288
+ if not hasattr(spike, "findings") or not spike.findings:
289
+ continue
290
+
291
+ # Check if spike is related to current feature
292
+ if feature_id and hasattr(spike, "edges") and spike.edges:
293
+ for edge_type, edges in spike.edges.items():
294
+ for edge in edges:
295
+ if edge.target_id == feature_id:
296
+ relevant_spikes.append((spike, 5)) # High relevance
297
+ break
298
+
299
+ # Check if spike mentions the track
300
+ if track and track.lower() in (spike.title or "").lower():
301
+ relevant_spikes.append((spike, 3)) # Medium relevance
302
+
303
+ # Check for recent completed spikes with findings
304
+ if spike.status == "done" and spike.findings:
305
+ if hasattr(spike, "updated") and spike.updated:
306
+ cutoff = datetime.now() - timedelta(hours=24)
307
+ if spike.updated > cutoff:
308
+ relevant_spikes.append((spike, 2)) # Lower relevance
309
+
310
+ # Sort by relevance and take top
311
+ relevant_spikes.sort(key=lambda x: x[1], reverse=True)
312
+
313
+ for spike, relevance in relevant_spikes[:1]:
314
+ findings_preview = spike.findings[:60] if spike.findings else ""
315
+ items.append(
316
+ ReflectionItem(
317
+ category="spike",
318
+ priority=2,
319
+ title=f"Related: {spike.title[:35]}",
320
+ detail=findings_preview or "See spike for details",
321
+ source_id=spike.id,
322
+ )
323
+ )
324
+ except Exception:
325
+ pass
326
+
327
+ return items[:1] # Max 1 spike
328
+
329
+ def _get_recommendations(self) -> list[ReflectionItem]:
330
+ """Get strategic recommendations."""
331
+ items = []
332
+
333
+ try:
334
+ recs = self.sdk.recommend_next_work(agent_count=1)
335
+
336
+ if recs and len(recs) > 0:
337
+ rec = recs[0]
338
+ reasons = rec.get("reasons", [])
339
+ reason_str = reasons[0] if reasons else "Recommended next"
340
+
341
+ items.append(
342
+ ReflectionItem(
343
+ category="recommendation",
344
+ priority=2,
345
+ title=f"Next: {rec.get('title', 'Unknown')[:35]}",
346
+ detail=reason_str[:60],
347
+ source_id=rec.get("id"),
348
+ )
349
+ )
350
+ except Exception:
351
+ pass
352
+
353
+ return items[:1] # Max 1 recommendation
354
+
355
+ def _build_summary(self, items: list[ReflectionItem]) -> str:
356
+ """Build a one-line summary from items."""
357
+ if not items:
358
+ return "No actionable context found."
359
+
360
+ parts = []
361
+
362
+ # Count by category
363
+ blockers = [i for i in items if i.category == "blocker"]
364
+ failures = [i for i in items if i.category == "failure"]
365
+ anti_patterns = [i for i in items if i.category == "anti_pattern"]
366
+ spikes = [i for i in items if i.category == "spike"]
367
+
368
+ if blockers:
369
+ parts.append(f"{len(blockers)} blocker{'s' if len(blockers) > 1 else ''}")
370
+
371
+ if failures:
372
+ parts.append(
373
+ f"{len(failures)} recent failure{'s' if len(failures) > 1 else ''}"
374
+ )
375
+
376
+ if anti_patterns:
377
+ pattern = anti_patterns[0]
378
+ parts.append(f"Avoid: {pattern.title.replace('Avoid: ', '')}")
379
+
380
+ if spikes:
381
+ spike = spikes[0]
382
+ parts.append(f"See: {spike.source_id}")
383
+
384
+ return " | ".join(parts) if parts else "Session context loaded."
385
+
386
+ def format_for_injection(self, context: dict[str, Any] | None = None) -> str:
387
+ """
388
+ Format context for injection into hooks.
389
+
390
+ Returns a markdown-formatted string suitable for additionalContext.
391
+ """
392
+ if context is None:
393
+ context = self.get_actionable_context()
394
+
395
+ if not context.get("items"):
396
+ return ""
397
+
398
+ lines = ["## Computed Reflections", ""]
399
+ lines.append(f"**Summary:** {context.get('summary', 'N/A')}")
400
+ lines.append("")
401
+
402
+ for item in context.get("items", []):
403
+ icon = {
404
+ "blocker": "🚫",
405
+ "failure": "❌",
406
+ "anti_pattern": "⚠️",
407
+ "spike": "🔍",
408
+ "recommendation": "💡",
409
+ }.get(item.get("category", ""), "•")
410
+
411
+ lines.append(f"{icon} **{item.get('title', 'Unknown')}**")
412
+ lines.append(f" {item.get('detail', '')}")
413
+ if item.get("source_id"):
414
+ lines.append(f" _Source: {item.get('source_id')}_")
415
+ lines.append("")
416
+
417
+ lines.append("---")
418
+ lines.append("")
419
+
420
+ return "\n".join(lines)
421
+
422
+
423
+ def get_reflection_context(
424
+ sdk: SDK,
425
+ feature_id: str | None = None,
426
+ track: str | None = None,
427
+ ) -> str:
428
+ """
429
+ Convenience function to get formatted reflection context.
430
+
431
+ This is the main entry point for hooks.
432
+
433
+ Args:
434
+ sdk: HtmlGraph SDK instance
435
+ feature_id: Current feature ID (optional)
436
+ track: Current track name (optional)
437
+
438
+ Returns:
439
+ Formatted string for injection into hook context
440
+ """
441
+ reflection = ComputationalReflection(sdk)
442
+ context = reflection.get_actionable_context(feature_id, track)
443
+ return reflection.format_for_injection(context)