htmlgraph 0.26.25__py3-none-any.whl → 0.27.1__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 (175) hide show
  1. htmlgraph/__init__.py +23 -1
  2. htmlgraph/__init__.pyi +123 -0
  3. htmlgraph/agent_registry.py +2 -1
  4. htmlgraph/analytics/cli.py +3 -3
  5. htmlgraph/analytics/cost_analyzer.py +5 -1
  6. htmlgraph/analytics/cost_monitor.py +664 -0
  7. htmlgraph/analytics/cross_session.py +13 -9
  8. htmlgraph/analytics/dependency.py +10 -6
  9. htmlgraph/analytics/strategic/__init__.py +80 -0
  10. htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
  11. htmlgraph/analytics/strategic/pattern_detector.py +876 -0
  12. htmlgraph/analytics/strategic/preference_manager.py +709 -0
  13. htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
  14. htmlgraph/analytics/work_type.py +15 -11
  15. htmlgraph/analytics_index.py +2 -1
  16. htmlgraph/api/cost_alerts_websocket.py +416 -0
  17. htmlgraph/api/main.py +167 -62
  18. htmlgraph/api/websocket.py +538 -0
  19. htmlgraph/attribute_index.py +2 -1
  20. htmlgraph/builders/base.py +2 -1
  21. htmlgraph/builders/bug.py +2 -1
  22. htmlgraph/builders/chore.py +2 -1
  23. htmlgraph/builders/epic.py +2 -1
  24. htmlgraph/builders/feature.py +2 -1
  25. htmlgraph/builders/insight.py +2 -1
  26. htmlgraph/builders/metric.py +2 -1
  27. htmlgraph/builders/pattern.py +2 -1
  28. htmlgraph/builders/phase.py +2 -1
  29. htmlgraph/builders/spike.py +2 -1
  30. htmlgraph/builders/track.py +2 -1
  31. htmlgraph/cli/analytics.py +2 -1
  32. htmlgraph/cli/base.py +2 -1
  33. htmlgraph/cli/core.py +2 -1
  34. htmlgraph/cli/main.py +2 -1
  35. htmlgraph/cli/models.py +2 -1
  36. htmlgraph/cli/templates/cost_dashboard.py +2 -1
  37. htmlgraph/cli/work/__init__.py +2 -1
  38. htmlgraph/cli/work/browse.py +2 -1
  39. htmlgraph/cli/work/features.py +2 -1
  40. htmlgraph/cli/work/orchestration.py +2 -1
  41. htmlgraph/cli/work/report.py +2 -1
  42. htmlgraph/cli/work/sessions.py +2 -1
  43. htmlgraph/cli/work/snapshot.py +2 -1
  44. htmlgraph/cli/work/tracks.py +2 -1
  45. htmlgraph/collections/base.py +10 -5
  46. htmlgraph/collections/bug.py +2 -1
  47. htmlgraph/collections/chore.py +2 -1
  48. htmlgraph/collections/epic.py +2 -1
  49. htmlgraph/collections/feature.py +2 -1
  50. htmlgraph/collections/insight.py +2 -1
  51. htmlgraph/collections/metric.py +2 -1
  52. htmlgraph/collections/pattern.py +2 -1
  53. htmlgraph/collections/phase.py +2 -1
  54. htmlgraph/collections/session.py +12 -7
  55. htmlgraph/collections/spike.py +6 -1
  56. htmlgraph/collections/task_delegation.py +7 -2
  57. htmlgraph/collections/todo.py +2 -1
  58. htmlgraph/collections/traces.py +15 -10
  59. htmlgraph/config/cost_models.json +56 -0
  60. htmlgraph/context_analytics.py +2 -1
  61. htmlgraph/db/schema.py +67 -6
  62. htmlgraph/dependency_models.py +2 -1
  63. htmlgraph/edge_index.py +2 -1
  64. htmlgraph/event_log.py +83 -64
  65. htmlgraph/event_migration.py +2 -1
  66. htmlgraph/file_watcher.py +12 -8
  67. htmlgraph/find_api.py +2 -1
  68. htmlgraph/git_events.py +6 -2
  69. htmlgraph/hooks/cigs_pretool_enforcer.py +5 -1
  70. htmlgraph/hooks/drift_handler.py +3 -3
  71. htmlgraph/hooks/event_tracker.py +40 -61
  72. htmlgraph/hooks/installer.py +5 -1
  73. htmlgraph/hooks/orchestrator.py +4 -0
  74. htmlgraph/hooks/orchestrator_reflector.py +4 -0
  75. htmlgraph/hooks/post_tool_use_failure.py +7 -3
  76. htmlgraph/hooks/posttooluse.py +4 -0
  77. htmlgraph/hooks/prompt_analyzer.py +5 -5
  78. htmlgraph/hooks/session_handler.py +2 -1
  79. htmlgraph/hooks/session_summary.py +6 -2
  80. htmlgraph/hooks/validator.py +8 -4
  81. htmlgraph/ids.py +2 -1
  82. htmlgraph/learning.py +2 -1
  83. htmlgraph/mcp_server.py +2 -1
  84. htmlgraph/operations/analytics.py +2 -1
  85. htmlgraph/operations/bootstrap.py +2 -1
  86. htmlgraph/operations/events.py +2 -1
  87. htmlgraph/operations/fastapi_server.py +2 -1
  88. htmlgraph/operations/hooks.py +2 -1
  89. htmlgraph/operations/initialization.py +2 -1
  90. htmlgraph/operations/server.py +2 -1
  91. htmlgraph/orchestration/claude_launcher.py +23 -20
  92. htmlgraph/orchestration/command_builder.py +2 -1
  93. htmlgraph/orchestration/headless_spawner.py +6 -2
  94. htmlgraph/orchestration/model_selection.py +7 -3
  95. htmlgraph/orchestration/plugin_manager.py +24 -19
  96. htmlgraph/orchestration/spawners/claude.py +5 -2
  97. htmlgraph/orchestration/spawners/codex.py +12 -19
  98. htmlgraph/orchestration/spawners/copilot.py +13 -18
  99. htmlgraph/orchestration/spawners/gemini.py +12 -19
  100. htmlgraph/orchestration/subprocess_runner.py +6 -3
  101. htmlgraph/orchestration/task_coordination.py +16 -8
  102. htmlgraph/orchestrator.py +2 -1
  103. htmlgraph/parallel.py +2 -1
  104. htmlgraph/query_builder.py +2 -1
  105. htmlgraph/reflection.py +2 -1
  106. htmlgraph/refs.py +2 -1
  107. htmlgraph/repo_hash.py +2 -1
  108. htmlgraph/repositories/__init__.py +292 -0
  109. htmlgraph/repositories/analytics_repository.py +455 -0
  110. htmlgraph/repositories/analytics_repository_standard.py +628 -0
  111. htmlgraph/repositories/feature_repository.py +581 -0
  112. htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
  113. htmlgraph/repositories/feature_repository_memory.py +607 -0
  114. htmlgraph/repositories/feature_repository_sqlite.py +858 -0
  115. htmlgraph/repositories/filter_service.py +620 -0
  116. htmlgraph/repositories/filter_service_standard.py +445 -0
  117. htmlgraph/repositories/shared_cache.py +621 -0
  118. htmlgraph/repositories/shared_cache_memory.py +395 -0
  119. htmlgraph/repositories/track_repository.py +552 -0
  120. htmlgraph/repositories/track_repository_htmlfile.py +619 -0
  121. htmlgraph/repositories/track_repository_memory.py +508 -0
  122. htmlgraph/repositories/track_repository_sqlite.py +711 -0
  123. htmlgraph/sdk/__init__.py +398 -0
  124. htmlgraph/sdk/__init__.pyi +14 -0
  125. htmlgraph/sdk/analytics/__init__.py +19 -0
  126. htmlgraph/sdk/analytics/engine.py +155 -0
  127. htmlgraph/sdk/analytics/helpers.py +178 -0
  128. htmlgraph/sdk/analytics/registry.py +109 -0
  129. htmlgraph/sdk/base.py +484 -0
  130. htmlgraph/sdk/constants.py +216 -0
  131. htmlgraph/sdk/core.pyi +308 -0
  132. htmlgraph/sdk/discovery.py +120 -0
  133. htmlgraph/sdk/help/__init__.py +12 -0
  134. htmlgraph/sdk/help/mixin.py +699 -0
  135. htmlgraph/sdk/mixins/__init__.py +15 -0
  136. htmlgraph/sdk/mixins/attribution.py +113 -0
  137. htmlgraph/sdk/mixins/mixin.py +410 -0
  138. htmlgraph/sdk/operations/__init__.py +12 -0
  139. htmlgraph/sdk/operations/mixin.py +427 -0
  140. htmlgraph/sdk/orchestration/__init__.py +17 -0
  141. htmlgraph/sdk/orchestration/coordinator.py +203 -0
  142. htmlgraph/sdk/orchestration/spawner.py +204 -0
  143. htmlgraph/sdk/planning/__init__.py +19 -0
  144. htmlgraph/sdk/planning/bottlenecks.py +93 -0
  145. htmlgraph/sdk/planning/mixin.py +211 -0
  146. htmlgraph/sdk/planning/parallel.py +186 -0
  147. htmlgraph/sdk/planning/queue.py +210 -0
  148. htmlgraph/sdk/planning/recommendations.py +87 -0
  149. htmlgraph/sdk/planning/smart_planning.py +319 -0
  150. htmlgraph/sdk/session/__init__.py +19 -0
  151. htmlgraph/sdk/session/continuity.py +57 -0
  152. htmlgraph/sdk/session/handoff.py +110 -0
  153. htmlgraph/sdk/session/info.py +309 -0
  154. htmlgraph/sdk/session/manager.py +103 -0
  155. htmlgraph/sdk/strategic/__init__.py +26 -0
  156. htmlgraph/sdk/strategic/mixin.py +563 -0
  157. htmlgraph/server.py +21 -17
  158. htmlgraph/session_warning.py +2 -1
  159. htmlgraph/sessions/handoff.py +4 -3
  160. htmlgraph/system_prompts.py +2 -1
  161. htmlgraph/track_builder.py +2 -1
  162. htmlgraph/transcript.py +2 -1
  163. htmlgraph/watch.py +2 -1
  164. htmlgraph/work_type_utils.py +2 -1
  165. {htmlgraph-0.26.25.dist-info → htmlgraph-0.27.1.dist-info}/METADATA +1 -1
  166. htmlgraph-0.27.1.dist-info/RECORD +332 -0
  167. htmlgraph/sdk.py +0 -3500
  168. htmlgraph-0.26.25.dist-info/RECORD +0 -274
  169. {htmlgraph-0.26.25.data → htmlgraph-0.27.1.data}/data/htmlgraph/dashboard.html +0 -0
  170. {htmlgraph-0.26.25.data → htmlgraph-0.27.1.data}/data/htmlgraph/styles.css +0 -0
  171. {htmlgraph-0.26.25.data → htmlgraph-0.27.1.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  172. {htmlgraph-0.26.25.data → htmlgraph-0.27.1.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  173. {htmlgraph-0.26.25.data → htmlgraph-0.27.1.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  174. {htmlgraph-0.26.25.dist-info → htmlgraph-0.27.1.dist-info}/WHEEL +0 -0
  175. {htmlgraph-0.26.25.dist-info → htmlgraph-0.27.1.dist-info}/entry_points.txt +0 -0
@@ -1,9 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  """Command builder for Claude Code CLI invocations.
2
4
 
3
5
  Provides fluent interface for constructing Claude CLI commands.
4
6
  """
5
7
 
6
- from __future__ import annotations
7
8
 
8
9
  from typing import TYPE_CHECKING
9
10
 
@@ -1,5 +1,9 @@
1
1
  """Headless AI spawner for multi-AI orchestration.
2
2
 
3
+ import logging
4
+
5
+ logger = logging.getLogger(__name__)
6
+
3
7
  This module provides backward compatibility by delegating to modular spawner implementations.
4
8
  """
5
9
 
@@ -263,8 +267,8 @@ class HeadlessSpawner:
263
267
  >>> spawner = HeadlessSpawner()
264
268
  >>> result = spawner.spawn_claude("What is 2+2?")
265
269
  >>> if result.success:
266
- ... print(result.response) # "4"
267
- ... print(f"Cost: ${result.raw_output['total_cost_usd']}")
270
+ ... logger.info("%s", result.response) # "4"
271
+ ... logger.info(f"Cost: ${result.raw_output['total_cost_usd']}")
268
272
  """
269
273
  return self._claude_spawner.spawn(
270
274
  prompt=prompt,
@@ -1,5 +1,9 @@
1
1
  """Intelligent model selection for task routing.
2
2
 
3
+ import logging
4
+
5
+ logger = logging.getLogger(__name__)
6
+
3
7
  This module provides functionality to select the best AI model for a given task
4
8
  based on task type, complexity, and budget constraints.
5
9
 
@@ -179,7 +183,7 @@ class ModelSelection:
179
183
 
180
184
  Example:
181
185
  >>> model = ModelSelection.select_model("implementation", "high", "balanced")
182
- >>> print(model)
186
+ >>> logger.info("%s", model)
183
187
  'claude-opus'
184
188
  """
185
189
  # Normalize inputs
@@ -218,7 +222,7 @@ class ModelSelection:
218
222
 
219
223
  Example:
220
224
  >>> fallbacks = ModelSelection.get_fallback_chain("gemini")
221
- >>> print(fallbacks)
225
+ >>> logger.info("%s", fallbacks)
222
226
  ['claude-haiku', 'claude-sonnet', 'claude-opus']
223
227
  """
224
228
  return ModelSelection.FALLBACK_CHAINS.get(primary_model, ["claude-sonnet"])
@@ -305,7 +309,7 @@ def select_model(
305
309
 
306
310
  Example:
307
311
  >>> model = select_model("implementation", "high")
308
- >>> print(model)
312
+ >>> logger.info("%s", model)
309
313
  """
310
314
  return ModelSelection.select_model(task_type, complexity, budget)
311
315
 
@@ -1,15 +1,18 @@
1
+ from __future__ import annotations
2
+
1
3
  """Plugin management for HtmlGraph Claude Code integration.
2
4
 
3
5
  Centralizes plugin installation, directory management, and validation.
4
6
  """
5
7
 
6
- from __future__ import annotations
7
-
8
+ import logging
8
9
  import subprocess
9
10
  import sys
10
11
  from pathlib import Path
11
12
  from typing import TYPE_CHECKING
12
13
 
14
+ logger = logging.getLogger(__name__)
15
+
13
16
  if TYPE_CHECKING:
14
17
  pass
15
18
 
@@ -40,12 +43,12 @@ class PluginManager:
40
43
  verbose: Whether to show progress messages
41
44
  """
42
45
  if verbose:
43
- print("\n📦 Installing/upgrading HtmlGraph plugin...\n")
46
+ logger.info("\n📦 Installing/upgrading HtmlGraph plugin...\n")
44
47
 
45
48
  # Step 1: Update marketplace
46
49
  try:
47
50
  if verbose:
48
- print(" Updating marketplace...")
51
+ logger.info(" Updating marketplace...")
49
52
  result = subprocess.run(
50
53
  ["claude", "plugin", "marketplace", "update", "htmlgraph"],
51
54
  capture_output=True,
@@ -54,7 +57,7 @@ class PluginManager:
54
57
  )
55
58
  if result.returncode == 0:
56
59
  if verbose:
57
- print(" ✓ Marketplace updated")
60
+ logger.info(" ✓ Marketplace updated")
58
61
  else:
59
62
  # Non-blocking errors
60
63
  if (
@@ -62,20 +65,20 @@ class PluginManager:
62
65
  or "no marketplace" in result.stderr.lower()
63
66
  ):
64
67
  if verbose:
65
- print(" ℹ Marketplace not configured (OK, continuing)")
68
+ logger.info(" ℹ Marketplace not configured (OK, continuing)")
66
69
  elif verbose:
67
- print(f" ⚠ Marketplace update: {result.stderr.strip()}")
70
+ logger.info(f" ⚠ Marketplace update: {result.stderr.strip()}")
68
71
  except FileNotFoundError:
69
72
  if verbose:
70
- print(" ⚠ 'claude' command not found")
73
+ logger.info(" ⚠ 'claude' command not found")
71
74
  except Exception as e:
72
75
  if verbose:
73
- print(f" ⚠ Error updating marketplace: {e}")
76
+ logger.info(f" ⚠ Error updating marketplace: {e}")
74
77
 
75
78
  # Step 2: Try update, fallback to install
76
79
  try:
77
80
  if verbose:
78
- print(" Updating plugin to latest version...")
81
+ logger.info(" Updating plugin to latest version...")
79
82
  result = subprocess.run(
80
83
  ["claude", "plugin", "update", "htmlgraph"],
81
84
  capture_output=True,
@@ -84,7 +87,7 @@ class PluginManager:
84
87
  )
85
88
  if result.returncode == 0:
86
89
  if verbose:
87
- print(" ✓ Plugin updated successfully")
90
+ logger.info(" ✓ Plugin updated successfully")
88
91
  else:
89
92
  # Fallback to install
90
93
  if (
@@ -92,7 +95,7 @@ class PluginManager:
92
95
  or "not found" in result.stderr.lower()
93
96
  ):
94
97
  if verbose:
95
- print(" ℹ Plugin not yet installed, installing...")
98
+ logger.info(" ℹ Plugin not yet installed, installing...")
96
99
  install_result = subprocess.run(
97
100
  ["claude", "plugin", "install", "htmlgraph"],
98
101
  capture_output=True,
@@ -101,20 +104,22 @@ class PluginManager:
101
104
  )
102
105
  if install_result.returncode == 0:
103
106
  if verbose:
104
- print(" ✓ Plugin installed successfully")
107
+ logger.info(" ✓ Plugin installed successfully")
105
108
  elif verbose:
106
- print(f" ⚠ Plugin install: {install_result.stderr.strip()}")
109
+ logger.info(
110
+ f" ⚠ Plugin install: {install_result.stderr.strip()}"
111
+ )
107
112
  elif verbose:
108
- print(f" ⚠ Plugin update: {result.stderr.strip()}")
113
+ logger.info(f" ⚠ Plugin update: {result.stderr.strip()}")
109
114
  except FileNotFoundError:
110
115
  if verbose:
111
- print(" ⚠ 'claude' command not found")
116
+ logger.info(" ⚠ 'claude' command not found")
112
117
  except Exception as e:
113
118
  if verbose:
114
- print(f" ⚠ Error updating plugin: {e}")
119
+ logger.info(f" ⚠ Error updating plugin: {e}")
115
120
 
116
121
  if verbose:
117
- print("\n✓ Plugin installation complete\n")
122
+ logger.info("\n✓ Plugin installation complete\n")
118
123
 
119
124
  @staticmethod
120
125
  def validate_plugin_dir(plugin_dir: Path) -> None:
@@ -127,7 +132,7 @@ class PluginManager:
127
132
  SystemExit: If plugin directory doesn't exist
128
133
  """
129
134
  if not plugin_dir.exists():
130
- print(f"Error: Plugin directory not found: {plugin_dir}", file=sys.stderr)
135
+ logger.warning(f"Error: Plugin directory not found: {plugin_dir}")
131
136
  print(
132
137
  "Expected location: packages/claude-plugin/.claude-plugin",
133
138
  file=sys.stderr,
@@ -1,11 +1,14 @@
1
1
  """Claude spawner implementation."""
2
2
 
3
3
  import json
4
+ import logging
4
5
  import subprocess
5
6
  from typing import TYPE_CHECKING
6
7
 
7
8
  from .base import AIResult, BaseSpawner
8
9
 
10
+ logger = logging.getLogger(__name__)
11
+
9
12
  if TYPE_CHECKING:
10
13
  pass
11
14
 
@@ -64,8 +67,8 @@ class ClaudeSpawner(BaseSpawner):
64
67
  >>> spawner = ClaudeSpawner()
65
68
  >>> result = spawner.spawn("What is 2+2?")
66
69
  >>> if result.success:
67
- ... print(result.response) # "4"
68
- ... print(f"Cost: ${result.raw_output['total_cost_usd']}")
70
+ ... logger.info("%s", result.response) # "4"
71
+ ... logger.info(f"Cost: ${result.raw_output['total_cost_usd']}")
69
72
  """
70
73
  cmd = ["claude", "-p"]
71
74
 
@@ -1,13 +1,15 @@
1
1
  """Codex spawner implementation."""
2
2
 
3
3
  import json
4
+ import logging
4
5
  import subprocess
5
- import sys
6
6
  import time
7
7
  from typing import TYPE_CHECKING, Any
8
8
 
9
9
  from .base import AIResult, BaseSpawner
10
10
 
11
+ logger = logging.getLogger(__name__)
12
+
11
13
  if TYPE_CHECKING:
12
14
  from htmlgraph.sdk import SDK
13
15
 
@@ -232,15 +234,11 @@ class CodexSpawner(BaseSpawner):
232
234
 
233
235
  # Record subprocess invocation if tracker is available
234
236
  subprocess_event_id = None
235
- print(
236
- f"DEBUG: tracker={tracker is not None}, parent_event_id={parent_event_id}",
237
- file=sys.stderr,
237
+ logger.warning(
238
+ f"DEBUG: tracker={tracker is not None}, parent_event_id={parent_event_id}"
238
239
  )
239
240
  if tracker and parent_event_id:
240
- print(
241
- "DEBUG: Recording subprocess invocation for Codex...",
242
- file=sys.stderr,
243
- )
241
+ logger.debug("Recording subprocess invocation for Codex...")
244
242
  try:
245
243
  subprocess_event = tracker.record_tool_call(
246
244
  tool_name="subprocess.codex",
@@ -250,23 +248,18 @@ class CodexSpawner(BaseSpawner):
250
248
  )
251
249
  if subprocess_event:
252
250
  subprocess_event_id = subprocess_event.get("event_id")
253
- print(
254
- f"DEBUG: Subprocess event created for Codex: {subprocess_event_id}",
255
- file=sys.stderr,
251
+ logger.warning(
252
+ f"DEBUG: Subprocess event created for Codex: {subprocess_event_id}"
256
253
  )
257
254
  else:
258
- print("DEBUG: subprocess_event was None", file=sys.stderr)
255
+ logger.debug("subprocess_event was None")
259
256
  except Exception as e:
260
257
  # Tracking failure should not break execution
261
- print(
262
- f"DEBUG: Exception recording Codex subprocess: {e}",
263
- file=sys.stderr,
264
- )
258
+ logger.warning(f"DEBUG: Exception recording Codex subprocess: {e}")
265
259
  pass
266
260
  else:
267
- print(
268
- f"DEBUG: Skipping Codex subprocess tracking - tracker={tracker is not None}, parent_event_id={parent_event_id}",
269
- file=sys.stderr,
261
+ logger.warning(
262
+ f"DEBUG: Skipping Codex subprocess tracking - tracker={tracker is not None}, parent_event_id={parent_event_id}"
270
263
  )
271
264
 
272
265
  result = subprocess.run(
@@ -1,12 +1,14 @@
1
1
  """Copilot spawner implementation."""
2
2
 
3
+ import logging
3
4
  import subprocess
4
- import sys
5
5
  import time
6
6
  from typing import TYPE_CHECKING, Any
7
7
 
8
8
  from .base import AIResult, BaseSpawner
9
9
 
10
+ logger = logging.getLogger(__name__)
11
+
10
12
  if TYPE_CHECKING:
11
13
  from htmlgraph.sdk import SDK
12
14
 
@@ -129,15 +131,11 @@ class CopilotSpawner(BaseSpawner):
129
131
 
130
132
  # Record subprocess invocation if tracker is available
131
133
  subprocess_event_id = None
132
- print(
133
- f"DEBUG: tracker={tracker is not None}, parent_event_id={parent_event_id}",
134
- file=sys.stderr,
134
+ logger.warning(
135
+ f"DEBUG: tracker={tracker is not None}, parent_event_id={parent_event_id}"
135
136
  )
136
137
  if tracker and parent_event_id:
137
- print(
138
- "DEBUG: Recording subprocess invocation for Copilot...",
139
- file=sys.stderr,
140
- )
138
+ logger.debug("Recording subprocess invocation for Copilot...")
141
139
  try:
142
140
  subprocess_event = tracker.record_tool_call(
143
141
  tool_name="subprocess.copilot",
@@ -147,23 +145,20 @@ class CopilotSpawner(BaseSpawner):
147
145
  )
148
146
  if subprocess_event:
149
147
  subprocess_event_id = subprocess_event.get("event_id")
150
- print(
151
- f"DEBUG: Subprocess event created for Copilot: {subprocess_event_id}",
152
- file=sys.stderr,
148
+ logger.warning(
149
+ f"DEBUG: Subprocess event created for Copilot: {subprocess_event_id}"
153
150
  )
154
151
  else:
155
- print("DEBUG: subprocess_event was None", file=sys.stderr)
152
+ logger.debug("subprocess_event was None")
156
153
  except Exception as e:
157
154
  # Tracking failure should not break execution
158
- print(
159
- f"DEBUG: Exception recording Copilot subprocess: {e}",
160
- file=sys.stderr,
155
+ logger.warning(
156
+ f"DEBUG: Exception recording Copilot subprocess: {e}"
161
157
  )
162
158
  pass
163
159
  else:
164
- print(
165
- f"DEBUG: Skipping Copilot subprocess tracking - tracker={tracker is not None}, parent_event_id={parent_event_id}",
166
- file=sys.stderr,
160
+ logger.warning(
161
+ f"DEBUG: Skipping Copilot subprocess tracking - tracker={tracker is not None}, parent_event_id={parent_event_id}"
167
162
  )
168
163
 
169
164
  result = subprocess.run(
@@ -1,13 +1,15 @@
1
1
  """Gemini spawner implementation."""
2
2
 
3
3
  import json
4
+ import logging
4
5
  import subprocess
5
- import sys
6
6
  import time
7
7
  from typing import TYPE_CHECKING, Any
8
8
 
9
9
  from .base import AIResult, BaseSpawner
10
10
 
11
+ logger = logging.getLogger(__name__)
12
+
11
13
  if TYPE_CHECKING:
12
14
  from htmlgraph.sdk import SDK
13
15
 
@@ -220,15 +222,11 @@ class GeminiSpawner(BaseSpawner):
220
222
 
221
223
  # Record subprocess invocation if tracker is available
222
224
  subprocess_event_id = None
223
- print(
224
- f"DEBUG: tracker={tracker is not None}, parent_event_id={parent_event_id}",
225
- file=sys.stderr,
225
+ logger.warning(
226
+ f"DEBUG: tracker={tracker is not None}, parent_event_id={parent_event_id}"
226
227
  )
227
228
  if tracker and parent_event_id:
228
- print(
229
- "DEBUG: Recording subprocess invocation for Gemini...",
230
- file=sys.stderr,
231
- )
229
+ logger.debug("Recording subprocess invocation for Gemini...")
232
230
  try:
233
231
  subprocess_event = tracker.record_tool_call(
234
232
  tool_name="subprocess.gemini",
@@ -238,23 +236,18 @@ class GeminiSpawner(BaseSpawner):
238
236
  )
239
237
  if subprocess_event:
240
238
  subprocess_event_id = subprocess_event.get("event_id")
241
- print(
242
- f"DEBUG: Subprocess event created for Gemini: {subprocess_event_id}",
243
- file=sys.stderr,
239
+ logger.warning(
240
+ f"DEBUG: Subprocess event created for Gemini: {subprocess_event_id}"
244
241
  )
245
242
  else:
246
- print("DEBUG: subprocess_event was None", file=sys.stderr)
243
+ logger.debug("subprocess_event was None")
247
244
  except Exception as e:
248
245
  # Tracking failure should not break execution
249
- print(
250
- f"DEBUG: Exception recording Gemini subprocess: {e}",
251
- file=sys.stderr,
252
- )
246
+ logger.warning(f"DEBUG: Exception recording Gemini subprocess: {e}")
253
247
  pass
254
248
  else:
255
- print(
256
- f"DEBUG: Skipping Gemini subprocess tracking - tracker={tracker is not None}, parent_event_id={parent_event_id}",
257
- file=sys.stderr,
249
+ logger.warning(
250
+ f"DEBUG: Skipping Gemini subprocess tracking - tracker={tracker is not None}, parent_event_id={parent_event_id}"
258
251
  )
259
252
 
260
253
  # Execute with timeout and stderr redirection
@@ -1,13 +1,16 @@
1
+ from __future__ import annotations
2
+
1
3
  """Subprocess execution with standardized error handling.
2
4
 
3
5
  Provides consistent error handling for Claude Code CLI invocations.
4
6
  """
5
7
 
6
- from __future__ import annotations
7
-
8
+ import logging
8
9
  import subprocess
9
10
  import sys
10
11
 
12
+ logger = logging.getLogger(__name__)
13
+
11
14
 
12
15
  class SubprocessRunner:
13
16
  """Execute subprocess commands with error handling."""
@@ -25,7 +28,7 @@ class SubprocessRunner:
25
28
  try:
26
29
  subprocess.run(cmd, check=False)
27
30
  except FileNotFoundError:
28
- print("Error: 'claude' command not found.", file=sys.stderr)
31
+ logger.warning("Error: 'claude' command not found.")
29
32
  print(
30
33
  "Please install Claude Code CLI: https://code.claude.com",
31
34
  file=sys.stderr,
@@ -1,3 +1,7 @@
1
+ import logging
2
+
3
+ logger = logging.getLogger(__name__)
4
+
1
5
  """
2
6
  Orchestration helpers for reliable parallel task coordination.
3
7
 
@@ -7,9 +11,13 @@ Provides Task ID pattern for retrieving results from parallel delegations.
7
11
  import time
8
12
  import uuid
9
13
  from datetime import datetime, timedelta
10
- from typing import Any
14
+ from typing import TYPE_CHECKING, Any
11
15
 
12
- from htmlgraph.sdk import SDK
16
+ if TYPE_CHECKING:
17
+ from htmlgraph.sdk import SDK
18
+ else:
19
+ # Avoid circular import during module initialization
20
+ SDK = None
13
21
 
14
22
 
15
23
  def generate_task_id() -> str:
@@ -72,7 +80,7 @@ Provide detailed findings in your response.
72
80
 
73
81
 
74
82
  def get_results_by_task_id(
75
- sdk: SDK,
83
+ sdk: "SDK",
76
84
  task_id: str,
77
85
  timeout: int = 60,
78
86
  poll_interval: int = 2,
@@ -138,7 +146,7 @@ def get_results_by_task_id(
138
146
 
139
147
 
140
148
  def parallel_delegate(
141
- sdk: SDK,
149
+ sdk: "SDK",
142
150
  tasks: list[dict[str, str]],
143
151
  timeout: int = 120,
144
152
  ) -> dict[str, dict[str, Any]]:
@@ -161,7 +169,7 @@ def parallel_delegate(
161
169
  ])
162
170
 
163
171
  for task_id, result in results.items():
164
- print(f"{task_id}: {result['findings']}")
172
+ logger.info(f"{task_id}: {result['findings']}")
165
173
  """
166
174
  # Generate task IDs and enhanced prompts
167
175
  task_mapping = {}
@@ -189,7 +197,7 @@ def parallel_delegate(
189
197
 
190
198
 
191
199
  def save_task_results(
192
- sdk: SDK,
200
+ sdk: "SDK",
193
201
  task_id: str,
194
202
  description: str,
195
203
  results: str,
@@ -263,7 +271,7 @@ def save_task_results(
263
271
 
264
272
 
265
273
  def validate_and_save(
266
- sdk: SDK,
274
+ sdk: "SDK",
267
275
  task_id: str,
268
276
  description: str,
269
277
  results: str,
@@ -303,7 +311,7 @@ def validate_and_save(
303
311
  )
304
312
 
305
313
  if outcome["validated"]:
306
- print(f"✅ Saved to spike: {outcome['spike_id']}")
314
+ logger.info(f"✅ Saved to spike: {outcome['spike_id']}")
307
315
  """
308
316
  validated = True
309
317
  validation_results = None
htmlgraph/orchestrator.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  SubagentOrchestrator for context-preserving delegation.
3
5
 
@@ -83,7 +85,6 @@ Key Patterns
83
85
  4. Parallel execution: Multiple subagents can work simultaneously
84
86
  """
85
87
 
86
- from __future__ import annotations
87
88
 
88
89
  from dataclasses import dataclass, field
89
90
  from datetime import datetime
htmlgraph/parallel.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  Parallel workflow execution coordinator for multi-agent task processing.
3
5
 
@@ -76,7 +78,6 @@ Best Practices:
76
78
  - Limit to 3-5 parallel agents for optimal results
77
79
  """
78
80
 
79
- from __future__ import annotations
80
81
 
81
82
  from dataclasses import dataclass, field
82
83
  from datetime import datetime
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  Fluent Query Builder for HtmlGraph.
3
5
 
@@ -24,7 +26,6 @@ Example:
24
26
  .execute()
25
27
  """
26
28
 
27
- from __future__ import annotations
28
29
 
29
30
  import re
30
31
  from collections.abc import Callable, Iterator
htmlgraph/reflection.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  Computational Reflection Module.
3
5
 
@@ -23,7 +25,6 @@ Usage:
23
25
  # }
24
26
  """
25
27
 
26
- from __future__ import annotations
27
28
 
28
29
  from dataclasses import dataclass
29
30
  from datetime import datetime, timedelta
htmlgraph/refs.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  Short reference manager for graph nodes.
3
5
 
@@ -5,7 +7,6 @@ Manages persistent mapping of short refs (@f1, @t2, @b5) to full node IDs,
5
7
  enabling AI-friendly snapshots and queries.
6
8
  """
7
9
 
8
- from __future__ import annotations
9
10
 
10
11
  import json
11
12
  from datetime import datetime
htmlgraph/repo_hash.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  Repository Hashing and Git Awareness Module.
3
5
 
@@ -22,7 +24,6 @@ Architecture:
22
24
  - monorepo_project: "project-name" (if in monorepo)
23
25
  """
24
26
 
25
- from __future__ import annotations
26
27
 
27
28
  import hashlib
28
29
  import logging