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
@@ -0,0 +1,140 @@
1
+ from __future__ import annotations
2
+
3
+ """Plugin management for HtmlGraph Claude Code integration.
4
+
5
+ Centralizes plugin installation, directory management, and validation.
6
+ """
7
+
8
+ import logging
9
+ import subprocess
10
+ import sys
11
+ from pathlib import Path
12
+ from typing import TYPE_CHECKING
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ if TYPE_CHECKING:
17
+ pass
18
+
19
+
20
+ class PluginManager:
21
+ """Manage HtmlGraph Claude plugin installation and directories."""
22
+
23
+ @staticmethod
24
+ def get_plugin_dir() -> Path:
25
+ """Get the plugin directory path.
26
+
27
+ Returns:
28
+ Path to packages/claude-plugin (the plugin root, not .claude-plugin)
29
+ """
30
+ # Path(__file__) = .../src/python/htmlgraph/orchestration/plugin_manager.py
31
+ # Need to go up 5 levels to reach project root
32
+ return (
33
+ Path(__file__).parent.parent.parent.parent.parent
34
+ / "packages"
35
+ / "claude-plugin"
36
+ )
37
+
38
+ @staticmethod
39
+ def install_or_update(verbose: bool = True) -> None:
40
+ """Install or update HtmlGraph plugin.
41
+
42
+ Args:
43
+ verbose: Whether to show progress messages
44
+ """
45
+ if verbose:
46
+ logger.info("\n📦 Installing/upgrading HtmlGraph plugin...\n")
47
+
48
+ # Step 1: Update marketplace
49
+ try:
50
+ if verbose:
51
+ logger.info(" Updating marketplace...")
52
+ result = subprocess.run(
53
+ ["claude", "plugin", "marketplace", "update", "htmlgraph"],
54
+ capture_output=True,
55
+ text=True,
56
+ check=False,
57
+ )
58
+ if result.returncode == 0:
59
+ if verbose:
60
+ logger.info(" ✓ Marketplace updated")
61
+ else:
62
+ # Non-blocking errors
63
+ if (
64
+ "not found" in result.stderr.lower()
65
+ or "no marketplace" in result.stderr.lower()
66
+ ):
67
+ if verbose:
68
+ logger.info(" ℹ Marketplace not configured (OK, continuing)")
69
+ elif verbose:
70
+ logger.info(f" ⚠ Marketplace update: {result.stderr.strip()}")
71
+ except FileNotFoundError:
72
+ if verbose:
73
+ logger.info(" ⚠ 'claude' command not found")
74
+ except Exception as e:
75
+ if verbose:
76
+ logger.info(f" ⚠ Error updating marketplace: {e}")
77
+
78
+ # Step 2: Try update, fallback to install
79
+ try:
80
+ if verbose:
81
+ logger.info(" Updating plugin to latest version...")
82
+ result = subprocess.run(
83
+ ["claude", "plugin", "update", "htmlgraph"],
84
+ capture_output=True,
85
+ text=True,
86
+ check=False,
87
+ )
88
+ if result.returncode == 0:
89
+ if verbose:
90
+ logger.info(" ✓ Plugin updated successfully")
91
+ else:
92
+ # Fallback to install
93
+ if (
94
+ "not installed" in result.stderr.lower()
95
+ or "not found" in result.stderr.lower()
96
+ ):
97
+ if verbose:
98
+ logger.info(" ℹ Plugin not yet installed, installing...")
99
+ install_result = subprocess.run(
100
+ ["claude", "plugin", "install", "htmlgraph"],
101
+ capture_output=True,
102
+ text=True,
103
+ check=False,
104
+ )
105
+ if install_result.returncode == 0:
106
+ if verbose:
107
+ logger.info(" ✓ Plugin installed successfully")
108
+ elif verbose:
109
+ logger.info(
110
+ f" ⚠ Plugin install: {install_result.stderr.strip()}"
111
+ )
112
+ elif verbose:
113
+ logger.info(f" ⚠ Plugin update: {result.stderr.strip()}")
114
+ except FileNotFoundError:
115
+ if verbose:
116
+ logger.info(" ⚠ 'claude' command not found")
117
+ except Exception as e:
118
+ if verbose:
119
+ logger.info(f" ⚠ Error updating plugin: {e}")
120
+
121
+ if verbose:
122
+ logger.info("\n✓ Plugin installation complete\n")
123
+
124
+ @staticmethod
125
+ def validate_plugin_dir(plugin_dir: Path) -> None:
126
+ """Validate that plugin directory exists, exit if not.
127
+
128
+ Args:
129
+ plugin_dir: Path to plugin directory
130
+
131
+ Raises:
132
+ SystemExit: If plugin directory doesn't exist
133
+ """
134
+ if not plugin_dir.exists():
135
+ logger.warning(f"Error: Plugin directory not found: {plugin_dir}")
136
+ print(
137
+ "Expected location: packages/claude-plugin/.claude-plugin",
138
+ file=sys.stderr,
139
+ )
140
+ sys.exit(1)
@@ -0,0 +1,137 @@
1
+ """
2
+ Orchestrator system prompt loading and management.
3
+
4
+ Centralizes logic for loading and combining orchestrator system prompts,
5
+ keeping CLI code clean and focused on invocation.
6
+ """
7
+
8
+ import textwrap
9
+ from pathlib import Path
10
+
11
+
12
+ def get_orchestrator_prompt(include_dev_mode: bool = False) -> str:
13
+ """
14
+ Load and combine orchestrator system prompts.
15
+
16
+ Args:
17
+ include_dev_mode: If True, append development mode guidance
18
+
19
+ Returns:
20
+ Combined system prompt text ready for --append-system-prompt
21
+ """
22
+ package_dir = Path(__file__).parent.parent
23
+
24
+ # Load base orchestrator prompt
25
+ prompt_file = package_dir / "orchestrator-system-prompt-optimized.txt"
26
+ if prompt_file.exists():
27
+ base_prompt = prompt_file.read_text(encoding="utf-8")
28
+ else:
29
+ # Fallback: minimal orchestrator guidance
30
+ base_prompt = textwrap.dedent(
31
+ """
32
+ You are an AI orchestrator for HtmlGraph project development.
33
+
34
+ CRITICAL DIRECTIVES:
35
+ 1. DELEGATE to spawner skills - do not implement directly
36
+ 2. CREATE work items before delegating (features, bugs, spikes)
37
+ 3. USE SDK for tracking - all work must be tracked in .htmlgraph/
38
+ 4. RESPECT dependencies - check blockers before starting
39
+
40
+ Key Rules:
41
+ - Exploration/Research → Skill(skill=".claude-plugin:gemini")
42
+ - Code implementation → Skill(skill=".claude-plugin:codex")
43
+ - Git/GitHub ops → Skill(skill=".claude-plugin:copilot")
44
+ - Strategic planning → Task() with Claude subagent
45
+
46
+ Always use:
47
+ from htmlgraph import SDK
48
+ sdk = SDK(agent='orchestrator')
49
+
50
+ See CLAUDE.md for complete orchestrator directives.
51
+ """
52
+ )
53
+
54
+ # Load orchestration rules
55
+ rules_file = package_dir / "orchestration.md"
56
+ orchestration_rules = ""
57
+ if rules_file.exists():
58
+ orchestration_rules = rules_file.read_text(encoding="utf-8")
59
+
60
+ # Combine prompts
61
+ combined_prompt = base_prompt
62
+ if orchestration_rules:
63
+ combined_prompt = f"{base_prompt}\n\n---\n\n{orchestration_rules}"
64
+
65
+ # Add dev mode guidance if requested
66
+ if include_dev_mode:
67
+ dev_addendum = textwrap.dedent(
68
+ """
69
+
70
+ ═══════════════════════════════════════════════════════════
71
+ 🔧 DEVELOPMENT MODE - HtmlGraph Project
72
+ ═══════════════════════════════════════════════════════════
73
+
74
+ CRITICAL: Hooks load htmlgraph from PyPI, NOT local source!
75
+
76
+ Development Workflow:
77
+ 1. Make changes to src/python/htmlgraph/
78
+ 2. Run tests: uv run pytest
79
+ 3. Deploy to PyPI: ./scripts/deploy-all.sh X.Y.Z --no-confirm
80
+ 4. Restart Claude Code (hooks auto-load new version from PyPI)
81
+ 5. Verify changes work correctly
82
+
83
+ Why PyPI in Dev Mode?
84
+ - Hooks use: #!/usr/bin/env -S uv run --with htmlgraph
85
+ - Always pulls latest version from PyPI
86
+ - Tests in production-like environment
87
+ - No surprises when distributed to users
88
+ - Single source of truth (PyPI package)
89
+
90
+ Incremental Versioning:
91
+ - Use patch versions: 0.26.2 → 0.26.3 → 0.26.4
92
+ - No need to edit hook shebangs (always get latest)
93
+ - Deploy frequently for rapid iteration
94
+
95
+ Session ID Tracking (v0.26.3+):
96
+ - PostToolUse hooks query for most recent UserQuery session
97
+ - All events should share same session_id
98
+ - Verify: See "Development Mode" section in CLAUDE.md
99
+
100
+ Key References:
101
+ - Development workflow: CLAUDE.md "Development Mode" section
102
+ - Orchestrator patterns: /orchestrator-directives skill
103
+ - Code quality: /code-quality skill
104
+ - Deployment: /deployment-automation skill
105
+
106
+ Remember: You're dogfooding HtmlGraph!
107
+ - Use SDK to track your own work
108
+ - Delegate to spawner agents (Gemini, Codex, Copilot)
109
+ - Follow orchestration patterns
110
+ - Test in production-like environment
111
+
112
+ ═══════════════════════════════════════════════════════════
113
+ """
114
+ )
115
+ combined_prompt += dev_addendum
116
+
117
+ return combined_prompt
118
+
119
+
120
+ def get_prompt_summary() -> dict[str, str]:
121
+ """
122
+ Get summary of available prompt components.
123
+
124
+ Returns:
125
+ Dictionary with component names and their status
126
+ """
127
+ package_dir = Path(__file__).parent.parent
128
+
129
+ prompt_file = package_dir / "orchestrator-system-prompt-optimized.txt"
130
+ rules_file = package_dir / "orchestration.md"
131
+
132
+ return {
133
+ "base_prompt": "✓ Found" if prompt_file.exists() else "✗ Missing",
134
+ "orchestration_rules": "✓ Found" if rules_file.exists() else "✗ Missing",
135
+ "base_prompt_path": str(prompt_file),
136
+ "rules_path": str(rules_file),
137
+ }
@@ -0,0 +1,383 @@
1
+ """Spawner event tracking helper for internal activity tracking in spawned sessions.
2
+
3
+ This module provides utilities for tracking internal activities within spawner agents
4
+ and linking them to parent delegation events for observability in HtmlGraph.
5
+
6
+ Usage:
7
+ from htmlgraph.orchestration.spawner_event_tracker import SpawnerEventTracker
8
+
9
+ tracker = SpawnerEventTracker(
10
+ delegation_event_id="event-abc123",
11
+ parent_agent="orchestrator",
12
+ spawner_type="gemini"
13
+ )
14
+
15
+ # Track initialization phase
16
+ init_event = tracker.record_phase("Initializing Spawner", spawned_agent="gemini-2.0-flash")
17
+
18
+ # Track execution phase
19
+ exec_event = tracker.record_phase("Executing Gemini", tool_name="gemini-cli")
20
+ tracker.complete_phase(exec_event["event_id"], output_summary="Generated output...")
21
+
22
+ # Track completion
23
+ tracker.record_completion(success=True, response="Result here")
24
+ """
25
+
26
+ import os
27
+ import time
28
+ import uuid
29
+ from typing import Any
30
+
31
+
32
+ class SpawnerEventTracker:
33
+ """Track internal activities in spawner agents with parent-child linking."""
34
+
35
+ def __init__(
36
+ self,
37
+ delegation_event_id: str | None = None,
38
+ parent_agent: str = "orchestrator",
39
+ spawner_type: str = "generic",
40
+ session_id: str | None = None,
41
+ ):
42
+ """
43
+ Initialize spawner event tracker.
44
+
45
+ Args:
46
+ delegation_event_id: Parent delegation event ID to link to
47
+ parent_agent: Agent that initiated the spawning
48
+ spawner_type: Type of spawner (gemini, codex, copilot)
49
+ session_id: Session ID for events
50
+ """
51
+ self.delegation_event_id = delegation_event_id
52
+ self.parent_agent = parent_agent
53
+ self.spawner_type = spawner_type
54
+ self.session_id = session_id or f"session-{uuid.uuid4().hex[:8]}"
55
+ self.db = None
56
+ self.phase_events: dict[str, dict[str, Any]] = {}
57
+ self.start_time = time.time()
58
+
59
+ # Try to initialize database for event tracking
60
+ try:
61
+ from htmlgraph.config import get_database_path
62
+ from htmlgraph.db.schema import HtmlGraphDB
63
+
64
+ # Get correct database path from environment or project root
65
+ db_path = get_database_path()
66
+
67
+ if db_path.exists():
68
+ self.db = HtmlGraphDB(str(db_path))
69
+ except Exception:
70
+ # Tracking is optional, continue without it
71
+ pass
72
+
73
+ def record_phase(
74
+ self,
75
+ phase_name: str,
76
+ spawned_agent: str | None = None,
77
+ tool_name: str | None = None,
78
+ input_summary: str | None = None,
79
+ status: str = "running",
80
+ parent_phase_event_id: str | None = None,
81
+ ) -> dict[str, Any]:
82
+ """
83
+ Record the start of a phase in spawner execution.
84
+
85
+ Args:
86
+ phase_name: Human-readable phase name (e.g., "Initializing Spawner")
87
+ spawned_agent: Agent being spawned (optional)
88
+ tool_name: Tool being executed (optional)
89
+ input_summary: Summary of input (optional)
90
+ status: Current status (running, completed, failed)
91
+ parent_phase_event_id: Parent phase event ID for proper nesting (optional)
92
+
93
+ Returns:
94
+ Event dictionary with event_id and metadata
95
+ """
96
+ if not self.db:
97
+ return {}
98
+
99
+ event_id = f"event-{uuid.uuid4().hex[:8]}"
100
+ event_type = "tool_call"
101
+
102
+ try:
103
+ context = {
104
+ "phase_name": phase_name,
105
+ "spawner_type": self.spawner_type,
106
+ "parent_delegation_event": self.delegation_event_id,
107
+ }
108
+ if spawned_agent:
109
+ context["spawned_agent"] = spawned_agent
110
+ if tool_name:
111
+ context["tool"] = tool_name
112
+
113
+ # Use parent_phase_event_id if provided, otherwise use delegation_event_id
114
+ actual_parent_event_id = parent_phase_event_id or self.delegation_event_id
115
+
116
+ self.db.insert_event(
117
+ event_id=event_id,
118
+ agent_id=spawned_agent or self.parent_agent,
119
+ event_type=event_type,
120
+ session_id=self.session_id,
121
+ tool_name=tool_name
122
+ or f"HeadlessSpawner.{phase_name.replace(' ', '_').lower()}",
123
+ input_summary=input_summary or phase_name,
124
+ context=context,
125
+ parent_event_id=actual_parent_event_id,
126
+ subagent_type=self.spawner_type,
127
+ )
128
+
129
+ event = {
130
+ "event_id": event_id,
131
+ "phase_name": phase_name,
132
+ "spawned_agent": spawned_agent,
133
+ "tool_name": tool_name,
134
+ "status": status,
135
+ "start_time": time.time(),
136
+ }
137
+ self.phase_events[event_id] = event
138
+ return event
139
+
140
+ except Exception:
141
+ # Non-fatal - tracking is best-effort
142
+ return {}
143
+
144
+ def complete_phase(
145
+ self,
146
+ event_id: str,
147
+ output_summary: str | None = None,
148
+ status: str = "completed",
149
+ execution_duration: float | None = None,
150
+ ) -> bool:
151
+ """
152
+ Mark a phase as completed with results.
153
+
154
+ Args:
155
+ event_id: Event ID from record_phase
156
+ output_summary: Summary of output/result
157
+ status: Final status (completed, failed)
158
+ execution_duration: Execution time in seconds (auto-calculated if not provided)
159
+
160
+ Returns:
161
+ True if update successful, False otherwise
162
+ """
163
+ if not self.db or not event_id:
164
+ return False
165
+
166
+ try:
167
+ if execution_duration is None and event_id in self.phase_events:
168
+ execution_duration = (
169
+ time.time() - self.phase_events[event_id]["start_time"]
170
+ )
171
+
172
+ if self.db.connection is None:
173
+ return False
174
+
175
+ cursor = self.db.connection.cursor()
176
+ cursor.execute(
177
+ """
178
+ UPDATE agent_events
179
+ SET output_summary = ?, status = ?, execution_duration_seconds = ?,
180
+ updated_at = CURRENT_TIMESTAMP
181
+ WHERE event_id = ?
182
+ """,
183
+ (output_summary, status, execution_duration or 0.0, event_id),
184
+ )
185
+ self.db.connection.commit()
186
+
187
+ if event_id in self.phase_events:
188
+ self.phase_events[event_id]["status"] = status
189
+ self.phase_events[event_id]["output_summary"] = output_summary
190
+
191
+ return True
192
+ except Exception:
193
+ # Non-fatal
194
+ return False
195
+
196
+ def record_completion(
197
+ self,
198
+ success: bool,
199
+ response: str | None = None,
200
+ error: str | None = None,
201
+ tokens_used: int = 0,
202
+ cost: float = 0.0,
203
+ ) -> dict[str, Any]:
204
+ """
205
+ Record final completion with overall results.
206
+
207
+ Args:
208
+ success: Whether execution succeeded
209
+ response: Successful response/output
210
+ error: Error message if failed
211
+ tokens_used: Tokens consumed
212
+ cost: Execution cost
213
+
214
+ Returns:
215
+ Completion event dictionary
216
+ """
217
+ total_duration = time.time() - self.start_time
218
+
219
+ completion_event: dict[str, Any] = {
220
+ "success": success,
221
+ "duration": total_duration,
222
+ "tokens": tokens_used,
223
+ "cost": cost,
224
+ "phase_count": len(self.phase_events),
225
+ }
226
+
227
+ if success:
228
+ completion_event["response"] = response
229
+ else:
230
+ completion_event["error"] = error
231
+
232
+ return completion_event
233
+
234
+ def get_phase_events(self) -> dict[str, dict[str, Any]]:
235
+ """Get all recorded phase events."""
236
+ return self.phase_events
237
+
238
+ def record_tool_call(
239
+ self,
240
+ tool_name: str,
241
+ tool_input: dict | None,
242
+ phase_event_id: str,
243
+ spawned_agent: str | None = None,
244
+ ) -> dict[str, Any]:
245
+ """
246
+ Record a tool call within a spawned execution phase.
247
+
248
+ Args:
249
+ tool_name: Name of the tool (bash, read_file, write_file, etc.)
250
+ tool_input: Input parameters to the tool
251
+ phase_event_id: Parent phase event ID to link to
252
+ spawned_agent: Agent making the tool call (optional)
253
+
254
+ Returns:
255
+ Event dictionary with event_id and metadata
256
+ """
257
+ if not self.db:
258
+ return {}
259
+
260
+ event_id = f"event-{uuid.uuid4().hex[:8]}"
261
+
262
+ try:
263
+ context = {
264
+ "tool_name": tool_name,
265
+ "spawner_type": self.spawner_type,
266
+ "parent_phase_event": phase_event_id,
267
+ }
268
+ if spawned_agent:
269
+ context["spawned_agent"] = spawned_agent
270
+
271
+ self.db.insert_event(
272
+ event_id=event_id,
273
+ agent_id=spawned_agent or self.parent_agent,
274
+ event_type="tool_call",
275
+ session_id=self.session_id,
276
+ tool_name=tool_name,
277
+ input_summary=(
278
+ str(tool_input)[:200] if tool_input else f"Call to {tool_name}"
279
+ ),
280
+ context=context,
281
+ parent_event_id=phase_event_id,
282
+ subagent_type=self.spawner_type,
283
+ )
284
+
285
+ tool_event = {
286
+ "event_id": event_id,
287
+ "tool_name": tool_name,
288
+ "tool_input": tool_input,
289
+ "phase_event_id": phase_event_id,
290
+ "spawned_agent": spawned_agent,
291
+ "status": "running",
292
+ "start_time": time.time(),
293
+ }
294
+ return tool_event
295
+
296
+ except Exception:
297
+ # Non-fatal - tracking is best-effort
298
+ return {}
299
+
300
+ def complete_tool_call(
301
+ self,
302
+ event_id: str,
303
+ output_summary: str | None = None,
304
+ success: bool = True,
305
+ ) -> bool:
306
+ """
307
+ Mark a tool call as complete with results.
308
+
309
+ Args:
310
+ event_id: Event ID from record_tool_call
311
+ output_summary: Summary of tool output/result
312
+ success: Whether the tool call succeeded
313
+
314
+ Returns:
315
+ True if update successful, False otherwise
316
+ """
317
+ if not self.db or not event_id:
318
+ return False
319
+
320
+ try:
321
+ if self.db.connection is None:
322
+ return False
323
+
324
+ cursor = self.db.connection.cursor()
325
+ cursor.execute(
326
+ """
327
+ UPDATE agent_events
328
+ SET output_summary = ?, status = ?, updated_at = CURRENT_TIMESTAMP
329
+ WHERE event_id = ?
330
+ """,
331
+ (
332
+ output_summary,
333
+ "completed" if success else "failed",
334
+ event_id,
335
+ ),
336
+ )
337
+ self.db.connection.commit()
338
+ return True
339
+ except Exception:
340
+ # Non-fatal
341
+ return False
342
+
343
+ def get_event_hierarchy(self) -> dict[str, Any]:
344
+ """
345
+ Get the event hierarchy for this spawner execution.
346
+
347
+ Returns:
348
+ Dictionary with delegation event as root and phases as children
349
+ """
350
+ return {
351
+ "delegation_event_id": self.delegation_event_id,
352
+ "spawner_type": self.spawner_type,
353
+ "session_id": self.session_id,
354
+ "total_duration": time.time() - self.start_time,
355
+ "phase_events": self.phase_events,
356
+ }
357
+
358
+
359
+ def create_tracker_from_env(
360
+ spawner_type: str = "generic",
361
+ ) -> SpawnerEventTracker:
362
+ """
363
+ Create a SpawnerEventTracker from environment variables.
364
+
365
+ Reads HTMLGRAPH_PARENT_EVENT, HTMLGRAPH_PARENT_AGENT, HTMLGRAPH_PARENT_SESSION
366
+ from environment to link to parent context.
367
+
368
+ Args:
369
+ spawner_type: Type of spawner (gemini, codex, copilot)
370
+
371
+ Returns:
372
+ Initialized SpawnerEventTracker
373
+ """
374
+ delegation_event_id = os.getenv("HTMLGRAPH_PARENT_EVENT")
375
+ parent_agent = os.getenv("HTMLGRAPH_PARENT_AGENT", "orchestrator")
376
+ session_id = os.getenv("HTMLGRAPH_PARENT_SESSION")
377
+
378
+ return SpawnerEventTracker(
379
+ delegation_event_id=delegation_event_id,
380
+ parent_agent=parent_agent,
381
+ spawner_type=spawner_type,
382
+ session_id=session_id,
383
+ )
@@ -0,0 +1,16 @@
1
+ """AI spawner implementations for multi-AI orchestration."""
2
+
3
+ from .base import AIResult, BaseSpawner
4
+ from .claude import ClaudeSpawner
5
+ from .codex import CodexSpawner
6
+ from .copilot import CopilotSpawner
7
+ from .gemini import GeminiSpawner
8
+
9
+ __all__ = [
10
+ "AIResult",
11
+ "BaseSpawner",
12
+ "ClaudeSpawner",
13
+ "CodexSpawner",
14
+ "CopilotSpawner",
15
+ "GeminiSpawner",
16
+ ]