htmlgraph 0.20.9__tar.gz → 0.22.0__tar.gz

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 (266) hide show
  1. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/PKG-INFO +1 -1
  2. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/gemini-extension.json +1 -1
  3. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/pyproject.toml +1 -1
  4. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/__init__.py +1 -1
  5. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/analytics/__init__.py +3 -1
  6. htmlgraph-0.22.0/src/python/htmlgraph/analytics/cross_session.py +612 -0
  7. htmlgraph-0.22.0/src/python/htmlgraph/archive/__init__.py +24 -0
  8. htmlgraph-0.22.0/src/python/htmlgraph/archive/bloom.py +234 -0
  9. htmlgraph-0.22.0/src/python/htmlgraph/archive/fts.py +297 -0
  10. htmlgraph-0.22.0/src/python/htmlgraph/archive/manager.py +583 -0
  11. htmlgraph-0.22.0/src/python/htmlgraph/archive/search.py +244 -0
  12. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/cli.py +510 -0
  13. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/converter.py +39 -0
  14. htmlgraph-0.22.0/src/python/htmlgraph/docs/__init__.py +77 -0
  15. htmlgraph-0.22.0/src/python/htmlgraph/docs/docs_version.py +55 -0
  16. htmlgraph-0.22.0/src/python/htmlgraph/docs/metadata.py +93 -0
  17. htmlgraph-0.22.0/src/python/htmlgraph/docs/migrations.py +232 -0
  18. htmlgraph-0.22.0/src/python/htmlgraph/docs/template_engine.py +143 -0
  19. htmlgraph-0.22.0/src/python/htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
  20. htmlgraph-0.22.0/src/python/htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
  21. htmlgraph-0.22.0/src/python/htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
  22. htmlgraph-0.22.0/src/python/htmlgraph/docs/templates/base_agents.md.j2 +78 -0
  23. htmlgraph-0.22.0/src/python/htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
  24. htmlgraph-0.22.0/src/python/htmlgraph/docs/version_check.py +161 -0
  25. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/learning.py +121 -97
  26. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/models.py +53 -1
  27. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/sdk.py +4 -1
  28. htmlgraph-0.22.0/tests/integration/multi_agent/README.md +240 -0
  29. htmlgraph-0.22.0/tests/integration/multi_agent/__init__.py +1 -0
  30. htmlgraph-0.22.0/tests/integration/multi_agent/test_agent_quirks.py +561 -0
  31. htmlgraph-0.22.0/tests/integration/multi_agent/test_git_continuity_spine.py +755 -0
  32. htmlgraph-0.22.0/tests/python/test_archive_manager.py +581 -0
  33. htmlgraph-0.22.0/tests/python/test_archive_search.py +388 -0
  34. htmlgraph-0.22.0/tests/python/test_cross_session_analytics.py +479 -0
  35. htmlgraph-0.22.0/tests/python/test_plugin_update_preservation.py +621 -0
  36. htmlgraph-0.22.0/tests/python/test_template_system.py +322 -0
  37. htmlgraph-0.22.0/tests/python/test_version_migration.py +382 -0
  38. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/.gitignore +0 -0
  39. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/README.md +0 -0
  40. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/agent-coordination/README.md +0 -0
  41. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/agent-coordination/demo.py +0 -0
  42. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/create_auth_track_demo.py +0 -0
  43. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/create_htmlgraph_dev_track.py +0 -0
  44. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/documentation-site/README.md +0 -0
  45. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/documentation-site/agents.json +0 -0
  46. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/documentation-site/demo.py +0 -0
  47. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/documentation-site/features/doc-api-graph.html +0 -0
  48. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/documentation-site/features/doc-api-overview.html +0 -0
  49. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/documentation-site/features/doc-api-sdk.html +0 -0
  50. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/documentation-site/features/doc-example-advanced.html +0 -0
  51. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/documentation-site/features/doc-example-basic.html +0 -0
  52. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/documentation-site/features/doc-index.html +0 -0
  53. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/documentation-site/features/doc-installation.html +0 -0
  54. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/documentation-site/features/doc-quickstart.html +0 -0
  55. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/handoff-demo.py +0 -0
  56. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/knowledge-base/README.md +0 -0
  57. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/knowledge-base/agents.json +0 -0
  58. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/knowledge-base/demo.py +0 -0
  59. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/knowledge-base/features/feat-276c615f.html +0 -0
  60. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/knowledge-base/features/feat-7a6f797d.html +0 -0
  61. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/knowledge-base/features/note-ai-agents.html +0 -0
  62. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/knowledge-base/features/note-graph-db.html +0 -0
  63. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/knowledge-base/features/note-htmlgraph.html +0 -0
  64. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/knowledge-base/features/note-web-standards.html +0 -0
  65. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/parallel_task_coordination.py +0 -0
  66. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/routing-demo.py +0 -0
  67. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/sdk_demo.py +0 -0
  68. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/todo-list/README.md +0 -0
  69. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/todo-list/demo.py +0 -0
  70. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/todo-list/index.html +0 -0
  71. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/todo-list/styles.css +0 -0
  72. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/todo-list/task-001.html +0 -0
  73. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/todo-list/task-002.html +0 -0
  74. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/todo-list/task-003.html +0 -0
  75. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/todo-list/task-004.html +0 -0
  76. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/todo-list/task-005.html +0 -0
  77. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/examples/todo-list/task-006.html +0 -0
  78. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/GEMINI.md +0 -0
  79. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/README.md +0 -0
  80. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/command_deploy.md +0 -0
  81. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/command_end.md +0 -0
  82. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/command_feature-add.md +0 -0
  83. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/command_feature-complete.md +0 -0
  84. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/command_feature-primary.md +0 -0
  85. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/command_feature-start.md +0 -0
  86. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/command_git-commit.md +0 -0
  87. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/command_help.md +0 -0
  88. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/command_init.md +0 -0
  89. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/command_plan.md +0 -0
  90. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/command_recommend.md +0 -0
  91. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/command_serve.md +0 -0
  92. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/command_spike.md +0 -0
  93. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/command_start.md +0 -0
  94. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/command_status.md +0 -0
  95. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/command_track.md +0 -0
  96. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/hooks/hooks.json +0 -0
  97. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/hooks/scripts/post-tool.sh +0 -0
  98. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/hooks/scripts/session-end.sh +0 -0
  99. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/packages/gemini-extension/hooks/scripts/session-start.sh +0 -0
  100. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/js/htmlgraph.js +0 -0
  101. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/agent_detection.py +0 -0
  102. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/agent_registry.py +0 -0
  103. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/agents.py +0 -0
  104. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/analytics/cli.py +0 -0
  105. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/analytics/dependency.py +0 -0
  106. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/analytics/work_type.py +0 -0
  107. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/analytics_index.py +0 -0
  108. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/attribute_index.py +0 -0
  109. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/builders/__init__.py +0 -0
  110. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/builders/base.py +0 -0
  111. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/builders/bug.py +0 -0
  112. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/builders/chore.py +0 -0
  113. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/builders/epic.py +0 -0
  114. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/builders/feature.py +0 -0
  115. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/builders/insight.py +0 -0
  116. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/builders/metric.py +0 -0
  117. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/builders/pattern.py +0 -0
  118. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/builders/phase.py +0 -0
  119. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/builders/spike.py +0 -0
  120. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/builders/track.py +0 -0
  121. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/collections/__init__.py +0 -0
  122. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/collections/base.py +0 -0
  123. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/collections/bug.py +0 -0
  124. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/collections/chore.py +0 -0
  125. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/collections/epic.py +0 -0
  126. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/collections/feature.py +0 -0
  127. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/collections/insight.py +0 -0
  128. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/collections/metric.py +0 -0
  129. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/collections/pattern.py +0 -0
  130. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/collections/phase.py +0 -0
  131. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/collections/spike.py +0 -0
  132. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/collections/todo.py +0 -0
  133. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/context_analytics.py +0 -0
  134. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/dashboard.html +0 -0
  135. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/dependency_models.py +0 -0
  136. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/deploy.py +0 -0
  137. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/deployment_models.py +0 -0
  138. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/edge_index.py +0 -0
  139. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/event_log.py +0 -0
  140. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/event_migration.py +0 -0
  141. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/exceptions.py +0 -0
  142. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/extensions/gemini/GEMINI.md +0 -0
  143. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/extensions/gemini/README.md +0 -0
  144. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/extensions/gemini/gemini-extension.json +0 -0
  145. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/extensions/gemini/hooks/hooks.json +0 -0
  146. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/extensions/gemini/hooks/scripts/post-tool.sh +0 -0
  147. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/extensions/gemini/hooks/scripts/session-end.sh +0 -0
  148. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/extensions/gemini/hooks/scripts/session-start.sh +0 -0
  149. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/file_watcher.py +0 -0
  150. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/find_api.py +0 -0
  151. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/git_events.py +0 -0
  152. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/graph.py +0 -0
  153. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/hooks/__init__.py +0 -0
  154. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/hooks/event_tracker.py +0 -0
  155. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/hooks/hooks-config.example.json +0 -0
  156. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/hooks/installer.py +0 -0
  157. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/hooks/orchestrator.py +0 -0
  158. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/hooks/orchestrator_reflector.py +0 -0
  159. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/hooks/post-checkout.sh +0 -0
  160. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/hooks/post-commit.sh +0 -0
  161. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/hooks/post-merge.sh +0 -0
  162. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/hooks/post_tool_use_failure.py +0 -0
  163. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/hooks/posttooluse.py +0 -0
  164. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/hooks/pre-commit.sh +0 -0
  165. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/hooks/pre-push.sh +0 -0
  166. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/hooks/pretooluse.py +0 -0
  167. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/hooks/task_enforcer.py +0 -0
  168. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/hooks/task_validator.py +0 -0
  169. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/hooks/validator.py +0 -0
  170. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/ids.py +0 -0
  171. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/index.d.ts +0 -0
  172. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/mcp_server.py +0 -0
  173. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/orchestration.py +0 -0
  174. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/orchestrator.py +0 -0
  175. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/orchestrator_mode.py +0 -0
  176. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/orchestrator_validator.py +0 -0
  177. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/parallel.py +0 -0
  178. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/parser.py +0 -0
  179. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/planning.py +0 -0
  180. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/query_builder.py +0 -0
  181. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/routing.py +0 -0
  182. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/scripts/__init__.py +0 -0
  183. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/scripts/deploy.py +0 -0
  184. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/server.py +0 -0
  185. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/services/__init__.py +0 -0
  186. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/services/claiming.py +0 -0
  187. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/session_manager.py +0 -0
  188. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/session_warning.py +0 -0
  189. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/setup.py +0 -0
  190. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/spike_index.py +0 -0
  191. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/styles.css +0 -0
  192. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/sync_docs.py +0 -0
  193. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/templates/AGENTS.md.template +0 -0
  194. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/templates/CLAUDE.md.template +0 -0
  195. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/templates/GEMINI.md.template +0 -0
  196. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/track_builder.py +0 -0
  197. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/track_manager.py +0 -0
  198. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/transcript.py +0 -0
  199. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/transcript_analytics.py +0 -0
  200. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/types.py +0 -0
  201. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/watch.py +0 -0
  202. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/src/python/htmlgraph/work_type_utils.py +0 -0
  203. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/benchmarks/README.md +0 -0
  204. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/benchmarks/__init__.py +0 -0
  205. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/benchmarks/bench_graph.py +0 -0
  206. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/benchmarks/conftest.py +0 -0
  207. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/integration/test_deployment_workflow.py +0 -0
  208. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/integration/test_handoff_routing.py +0 -0
  209. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/integration/test_multi_agent_coordination.py +0 -0
  210. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/js/README.md +0 -0
  211. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/js/test_htmlgraph.html +0 -0
  212. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/__init__.py +0 -0
  213. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_agent_detection.py +0 -0
  214. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_analytics.py +0 -0
  215. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_auto_spike.py +0 -0
  216. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_claiming.py +0 -0
  217. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_cli_commands.py +0 -0
  218. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_collection_filter.py +0 -0
  219. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_cross_agent_attribution.py +0 -0
  220. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_dashboard_ui.py +0 -0
  221. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_delete.py +0 -0
  222. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_deploy.py +0 -0
  223. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_drift_parent_activity.py +0 -0
  224. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_drift_queue_cleanup.py +0 -0
  225. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_edge_index.py +0 -0
  226. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_event_index.py +0 -0
  227. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_event_query.py +0 -0
  228. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_find_api.py +0 -0
  229. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_git_events.py +0 -0
  230. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_graph_traversal.py +0 -0
  231. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_handoff.py +0 -0
  232. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_ids.py +0 -0
  233. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_install_hooks.py +0 -0
  234. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_lifecycle.py +0 -0
  235. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_mcp_server.py +0 -0
  236. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_mcp_stdio_transport.py +0 -0
  237. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_models.py +0 -0
  238. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_orchestration.py +0 -0
  239. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_orchestrator_circuit_breaker.py +0 -0
  240. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_orchestrator_cli.py +0 -0
  241. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_orchestrator_enforce_hook.py +0 -0
  242. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_orchestrator_mode.py +0 -0
  243. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_post_tool_use_failure.py +0 -0
  244. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_pre_work_validation_integration.py +0 -0
  245. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_query_builder.py +0 -0
  246. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_query_cache.py +0 -0
  247. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_query_compilation.py +0 -0
  248. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_routing.py +0 -0
  249. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_sdk_discoverability.py +0 -0
  250. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_sdk_get_active_work_item.py +0 -0
  251. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_session_cleanup.py +0 -0
  252. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_session_continuity.py +0 -0
  253. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_session_start_info_validation.py +0 -0
  254. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_system_overhead_drift.py +0 -0
  255. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_todo_collection.py +0 -0
  256. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_track_loading.py +0 -0
  257. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_transcript_linking.py +0 -0
  258. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_update_preserves_incoming_edges.py +0 -0
  259. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_validate_work_hook.py +0 -0
  260. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_work_queue.py +0 -0
  261. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/python/test_work_type.py +0 -0
  262. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/test_agent_routing.py +0 -0
  263. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/test_deployment_models.py +0 -0
  264. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/test_orchestrator_validator.py +0 -0
  265. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/test_transaction_snapshot.py +0 -0
  266. {htmlgraph-0.20.9 → htmlgraph-0.22.0}/tests/test_transcript.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: htmlgraph
3
- Version: 0.20.9
3
+ Version: 0.22.0
4
4
  Summary: HTML is All You Need - Graph database on web standards
5
5
  Project-URL: Homepage, https://github.com/Shakes-tzd/htmlgraph
6
6
  Project-URL: Documentation, https://github.com/Shakes-tzd/htmlgraph#readme
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "htmlgraph",
3
- "version": "0.20.9",
3
+ "version": "0.22.0",
4
4
  "description": "HtmlGraph session tracking and documentation for Gemini CLI. Ensures proper activity attribution, feature awareness, and continuous work tracking.",
5
5
  "author": "Shakes",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "htmlgraph"
3
- version = "0.20.9"
3
+ version = "0.22.0"
4
4
  description = "HTML is All You Need - Graph database on web standards"
5
5
  authors = [{name = "Shakes", email = "shakestzd@gmail.com"}]
6
6
  readme = "README.md"
@@ -84,7 +84,7 @@ from htmlgraph.types import (
84
84
  )
85
85
  from htmlgraph.work_type_utils import infer_work_type, infer_work_type_from_id
86
86
 
87
- __version__ = "0.20.9"
87
+ __version__ = "0.22.0"
88
88
  __all__ = [
89
89
  # Exceptions
90
90
  "HtmlGraphError",
@@ -1,13 +1,15 @@
1
1
  """
2
2
  Analytics modules for HtmlGraph.
3
3
 
4
- Provides work type analysis, dependency analytics, and CLI analytics.
4
+ Provides work type analysis, dependency analytics, cross-session analytics, and CLI analytics.
5
5
  """
6
6
 
7
+ from htmlgraph.analytics.cross_session import CrossSessionAnalytics
7
8
  from htmlgraph.analytics.dependency import DependencyAnalytics
8
9
  from htmlgraph.analytics.work_type import Analytics
9
10
 
10
11
  __all__ = [
11
12
  "Analytics",
12
13
  "DependencyAnalytics",
14
+ "CrossSessionAnalytics",
13
15
  ]
@@ -0,0 +1,612 @@
1
+ """
2
+ Cross-session analytics using Git commits as the continuity spine.
3
+
4
+ This module provides analytics that track work across multiple sessions
5
+ using Git commit history as the linking mechanism. Unlike session-based
6
+ analytics that only look within a single session, these analytics span
7
+ the entire commit graph to provide comprehensive insights.
8
+
9
+ Key Features:
10
+ - Query work in commit ranges (e.g., show all work between two commits)
11
+ - Track feature implementation across multiple sessions
12
+ - Analyze work by author across the project history
13
+ - Find sessions that contributed to specific commits
14
+ - Build work timelines using commit timestamps
15
+
16
+ Design:
17
+ - Uses Git commit hashes from EventRecord.payload['commit_hash']
18
+ - Leverages event logs (JSONL) as primary data source
19
+ - Falls back to Git commands when needed
20
+ - Works with both active sessions and historical work
21
+
22
+ Example:
23
+ from htmlgraph import SDK
24
+
25
+ sdk = SDK(agent="claude")
26
+ cross = sdk.cross_session_analytics
27
+
28
+ # Get all work between two commits
29
+ work = cross.work_in_commit_range(
30
+ from_commit="abc123",
31
+ to_commit="def456"
32
+ )
33
+
34
+ # Find sessions that contributed to a feature
35
+ sessions = cross.sessions_for_feature("feature-auth")
36
+
37
+ # Analyze work by author
38
+ authors = cross.work_by_author(since_commit="abc123")
39
+ """
40
+
41
+ from __future__ import annotations
42
+
43
+ import subprocess
44
+ from collections import defaultdict
45
+ from dataclasses import dataclass
46
+ from datetime import datetime
47
+ from pathlib import Path
48
+ from typing import TYPE_CHECKING, Any
49
+
50
+ if TYPE_CHECKING:
51
+ from htmlgraph import SDK
52
+
53
+ from htmlgraph.event_log import JsonlEventLog
54
+
55
+
56
+ @dataclass
57
+ class CommitWorkSummary:
58
+ """Summary of work done in a single commit."""
59
+
60
+ commit_hash: str
61
+ commit_hash_short: str
62
+ branch: str
63
+ author_name: str
64
+ author_email: str
65
+ commit_message: str
66
+ timestamp: datetime
67
+ features: list[str]
68
+ sessions: list[str]
69
+ event_count: int
70
+ files_changed: list[str]
71
+ insertions: int
72
+ deletions: int
73
+
74
+
75
+ @dataclass
76
+ class CommitRangeReport:
77
+ """Report of all work done in a commit range."""
78
+
79
+ from_commit: str
80
+ to_commit: str
81
+ commits: list[CommitWorkSummary]
82
+ total_events: int
83
+ features: list[str]
84
+ sessions: list[str]
85
+ authors: dict[str, int] # author_email -> event_count
86
+ work_types: dict[str, int] # work_type -> event_count
87
+
88
+
89
+ @dataclass
90
+ class FeatureCrossSessionReport:
91
+ """Report of a feature's implementation across multiple sessions."""
92
+
93
+ feature_id: str
94
+ sessions: list[str]
95
+ commits: list[str]
96
+ authors: list[str]
97
+ start_time: datetime | None
98
+ end_time: datetime | None
99
+ duration_hours: float | None
100
+ event_count: int
101
+ work_type_distribution: dict[str, int]
102
+
103
+
104
+ class CrossSessionAnalytics:
105
+ """
106
+ Analytics that track work across sessions using Git commits.
107
+
108
+ This class provides methods to query and analyze work that spans
109
+ multiple sessions, using Git commit history as the continuity spine.
110
+ """
111
+
112
+ def __init__(self, sdk: SDK):
113
+ """
114
+ Initialize CrossSessionAnalytics with SDK instance.
115
+
116
+ Args:
117
+ sdk: Parent SDK instance for accessing events and sessions
118
+ """
119
+ self.sdk = sdk
120
+ self._event_log = JsonlEventLog(sdk._directory / "events")
121
+ self._repo_root = self._find_repo_root(sdk._directory)
122
+
123
+ def work_in_commit_range(
124
+ self,
125
+ from_commit: str | None = None,
126
+ to_commit: str = "HEAD",
127
+ include_uncommitted: bool = False,
128
+ ) -> CommitRangeReport:
129
+ """
130
+ Get all work done in a commit range.
131
+
132
+ This method queries all events associated with commits in the
133
+ specified range and builds a comprehensive report.
134
+
135
+ Args:
136
+ from_commit: Starting commit (None = from beginning)
137
+ to_commit: Ending commit (default: HEAD)
138
+ include_uncommitted: Include events not yet committed
139
+
140
+ Returns:
141
+ CommitRangeReport with all work in the range
142
+
143
+ Example:
144
+ >>> # Get all work in last 10 commits
145
+ >>> report = cross.work_in_commit_range(
146
+ ... from_commit="HEAD~10",
147
+ ... to_commit="HEAD"
148
+ ... )
149
+ >>> print(f"Total events: {report.total_events}")
150
+ >>> print(f"Features: {', '.join(report.features)}")
151
+ """
152
+ # Get commit list from Git
153
+ commits = self._get_commits_in_range(from_commit, to_commit)
154
+
155
+ # Build commit hash set for fast lookup
156
+ commit_hashes = {c["hash"] for c in commits}
157
+
158
+ # Query events for these commits
159
+ commit_summaries: dict[str, CommitWorkSummary] = {}
160
+ features_set = set()
161
+ sessions_set = set()
162
+ authors_count: dict[str, int] = defaultdict(int)
163
+ work_types_count: dict[str, int] = defaultdict(int)
164
+ total_events = 0
165
+
166
+ for _, event in self._event_log.iter_events():
167
+ # Check if event is associated with a commit in our range
168
+ payload = event.get("payload", {})
169
+ commit_hash = payload.get("commit_hash")
170
+
171
+ if not commit_hash or commit_hash not in commit_hashes:
172
+ continue
173
+
174
+ # Extract event details
175
+ feature_id = event.get("feature_id")
176
+ session_id = event.get("session_id")
177
+ work_type = event.get("work_type")
178
+ author_email = payload.get("author_email", "")
179
+
180
+ # Track summary data
181
+ if feature_id:
182
+ features_set.add(feature_id)
183
+ if session_id:
184
+ sessions_set.add(session_id)
185
+ if work_type:
186
+ work_types_count[work_type] += 1
187
+ if author_email:
188
+ authors_count[author_email] += 1
189
+
190
+ total_events += 1
191
+
192
+ # Build commit summary (or update existing)
193
+ if commit_hash not in commit_summaries:
194
+ # Find commit details
195
+ commit_info = next(
196
+ (c for c in commits if c["hash"] == commit_hash), None
197
+ )
198
+ if not commit_info:
199
+ continue
200
+
201
+ commit_summaries[commit_hash] = CommitWorkSummary(
202
+ commit_hash=commit_hash,
203
+ commit_hash_short=commit_hash[:8],
204
+ branch=payload.get("branch", ""),
205
+ author_name=payload.get("author_name", ""),
206
+ author_email=author_email,
207
+ commit_message=payload.get("commit_message", ""),
208
+ timestamp=self._parse_timestamp(event.get("timestamp")),
209
+ features=[],
210
+ sessions=[],
211
+ event_count=0,
212
+ files_changed=payload.get("files_changed", []),
213
+ insertions=payload.get("insertions", 0),
214
+ deletions=payload.get("deletions", 0),
215
+ )
216
+
217
+ # Update commit summary
218
+ summary = commit_summaries[commit_hash]
219
+ if feature_id and feature_id not in summary.features:
220
+ summary.features.append(feature_id)
221
+ if session_id and session_id not in summary.sessions:
222
+ summary.sessions.append(session_id)
223
+ summary.event_count += 1
224
+
225
+ # Handle uncommitted work
226
+ if include_uncommitted:
227
+ # Find events without commit hashes
228
+ for _, event in self._event_log.iter_events():
229
+ payload = event.get("payload", {})
230
+ if payload.get("commit_hash"):
231
+ continue # Already processed
232
+
233
+ # Track uncommitted work
234
+ feature_id = event.get("feature_id")
235
+ session_id = event.get("session_id")
236
+ work_type = event.get("work_type")
237
+
238
+ if feature_id:
239
+ features_set.add(feature_id)
240
+ if session_id:
241
+ sessions_set.add(session_id)
242
+ if work_type:
243
+ work_types_count[work_type] += 1
244
+
245
+ total_events += 1
246
+
247
+ return CommitRangeReport(
248
+ from_commit=from_commit or "beginning",
249
+ to_commit=to_commit,
250
+ commits=sorted(
251
+ commit_summaries.values(), key=lambda c: c.timestamp, reverse=True
252
+ ),
253
+ total_events=total_events,
254
+ features=sorted(features_set),
255
+ sessions=sorted(sessions_set),
256
+ authors=dict(authors_count),
257
+ work_types=dict(work_types_count),
258
+ )
259
+
260
+ def sessions_for_feature(
261
+ self, feature_id: str, include_cross_session: bool = True
262
+ ) -> list[str]:
263
+ """
264
+ Find all sessions that contributed to a feature.
265
+
266
+ Args:
267
+ feature_id: Feature ID to query
268
+ include_cross_session: Include sessions linked via commit graph
269
+
270
+ Returns:
271
+ List of session IDs that worked on this feature
272
+
273
+ Example:
274
+ >>> sessions = cross.sessions_for_feature("feature-auth")
275
+ >>> print(f"Feature worked on in {len(sessions)} sessions")
276
+ """
277
+ sessions = set()
278
+
279
+ # Direct attribution from events
280
+ for _, event in self._event_log.iter_events():
281
+ if event.get("feature_id") == feature_id:
282
+ session_id = event.get("session_id")
283
+ if session_id:
284
+ sessions.add(session_id)
285
+
286
+ # Cross-session via commits (if enabled)
287
+ if include_cross_session:
288
+ # Find commits that mention this feature
289
+ commits_for_feature = set()
290
+ for _, event in self._event_log.iter_events():
291
+ if event.get("feature_id") == feature_id:
292
+ payload = event.get("payload", {})
293
+ commit_hash = payload.get("commit_hash")
294
+ if commit_hash:
295
+ commits_for_feature.add(commit_hash)
296
+
297
+ # Find all sessions that touched these commits
298
+ for _, event in self._event_log.iter_events():
299
+ payload = event.get("payload", {})
300
+ commit_hash = payload.get("commit_hash")
301
+ if commit_hash and commit_hash in commits_for_feature:
302
+ session_id = event.get("session_id")
303
+ if session_id:
304
+ sessions.add(session_id)
305
+
306
+ return sorted(sessions)
307
+
308
+ def feature_cross_session_report(
309
+ self, feature_id: str
310
+ ) -> FeatureCrossSessionReport:
311
+ """
312
+ Generate comprehensive cross-session report for a feature.
313
+
314
+ Args:
315
+ feature_id: Feature ID to analyze
316
+
317
+ Returns:
318
+ FeatureCrossSessionReport with complete implementation history
319
+
320
+ Example:
321
+ >>> report = cross.feature_cross_session_report("feature-auth")
322
+ >>> print(f"Implemented across {len(report.sessions)} sessions")
323
+ >>> print(f"Duration: {report.duration_hours:.1f} hours")
324
+ """
325
+ sessions = set()
326
+ commits = set()
327
+ authors = set()
328
+ work_types: dict[str, int] = defaultdict(int)
329
+ timestamps: list[datetime] = []
330
+ event_count = 0
331
+
332
+ # Scan all events for this feature
333
+ for _, event in self._event_log.iter_events():
334
+ if event.get("feature_id") != feature_id:
335
+ continue
336
+
337
+ event_count += 1
338
+
339
+ # Track metadata
340
+ session_id = event.get("session_id")
341
+ if session_id:
342
+ sessions.add(session_id)
343
+
344
+ payload = event.get("payload", {})
345
+ commit_hash = payload.get("commit_hash")
346
+ if commit_hash:
347
+ commits.add(commit_hash)
348
+
349
+ author_email = payload.get("author_email")
350
+ if author_email:
351
+ authors.add(author_email)
352
+
353
+ work_type = event.get("work_type")
354
+ if work_type:
355
+ work_types[work_type] += 1
356
+
357
+ # Track timing
358
+ timestamp_str = event.get("timestamp")
359
+ if timestamp_str:
360
+ timestamps.append(self._parse_timestamp(timestamp_str))
361
+
362
+ # Calculate duration
363
+ start_time = min(timestamps) if timestamps else None
364
+ end_time = max(timestamps) if timestamps else None
365
+ duration_hours = None
366
+ if start_time and end_time:
367
+ duration_hours = (end_time - start_time).total_seconds() / 3600
368
+
369
+ return FeatureCrossSessionReport(
370
+ feature_id=feature_id,
371
+ sessions=sorted(sessions),
372
+ commits=sorted(commits),
373
+ authors=sorted(authors),
374
+ start_time=start_time,
375
+ end_time=end_time,
376
+ duration_hours=duration_hours,
377
+ event_count=event_count,
378
+ work_type_distribution=dict(work_types),
379
+ )
380
+
381
+ def work_by_author(
382
+ self, since_commit: str | None = None, author_email: str | None = None
383
+ ) -> dict[str, dict[str, Any]]:
384
+ """
385
+ Analyze work by author across the project.
386
+
387
+ Args:
388
+ since_commit: Only analyze work since this commit
389
+ author_email: Filter to specific author (None = all authors)
390
+
391
+ Returns:
392
+ Dictionary mapping author_email to work statistics
393
+
394
+ Example:
395
+ >>> authors = cross.work_by_author(since_commit="v1.0.0")
396
+ >>> for email, stats in authors.items():
397
+ ... print(f"{email}: {stats['event_count']} events")
398
+ """
399
+ authors: dict[str, dict[str, Any]] = defaultdict(
400
+ lambda: {
401
+ "event_count": 0,
402
+ "features": set(),
403
+ "sessions": set(),
404
+ "commits": set(),
405
+ "work_types": defaultdict(int),
406
+ }
407
+ )
408
+
409
+ # Get commit range if specified
410
+ commit_hashes = None
411
+ if since_commit:
412
+ commits = self._get_commits_in_range(since_commit, "HEAD")
413
+ commit_hashes = {c["hash"] for c in commits}
414
+
415
+ # Scan events
416
+ for _, event in self._event_log.iter_events():
417
+ payload = event.get("payload", {})
418
+ event_author = payload.get("author_email")
419
+
420
+ # Skip if filtering by author
421
+ if author_email and event_author != author_email:
422
+ continue
423
+
424
+ # Skip if outside commit range
425
+ if commit_hashes:
426
+ commit_hash = payload.get("commit_hash")
427
+ if not commit_hash or commit_hash not in commit_hashes:
428
+ continue
429
+
430
+ if not event_author:
431
+ continue
432
+
433
+ # Track statistics
434
+ author_stats = authors[event_author]
435
+ author_stats["event_count"] += 1
436
+
437
+ feature_id = event.get("feature_id")
438
+ if feature_id:
439
+ author_stats["features"].add(feature_id)
440
+
441
+ session_id = event.get("session_id")
442
+ if session_id:
443
+ author_stats["sessions"].add(session_id)
444
+
445
+ commit_hash = payload.get("commit_hash")
446
+ if commit_hash:
447
+ author_stats["commits"].add(commit_hash)
448
+
449
+ work_type = event.get("work_type")
450
+ if work_type:
451
+ author_stats["work_types"][work_type] += 1
452
+
453
+ # Convert sets to lists for JSON serialization
454
+ result = {}
455
+ for email, stats in authors.items():
456
+ result[email] = {
457
+ "event_count": stats["event_count"],
458
+ "features": sorted(stats["features"]),
459
+ "sessions": sorted(stats["sessions"]),
460
+ "commits": sorted(stats["commits"]),
461
+ "work_types": dict(stats["work_types"]),
462
+ }
463
+
464
+ return result
465
+
466
+ def commits_for_session(self, session_id: str) -> list[str]:
467
+ """
468
+ Get all commits associated with a session.
469
+
470
+ Args:
471
+ session_id: Session ID to query
472
+
473
+ Returns:
474
+ List of commit hashes (in chronological order)
475
+
476
+ Example:
477
+ >>> commits = cross.commits_for_session("session-abc")
478
+ >>> print(f"Session produced {len(commits)} commits")
479
+ """
480
+ commits = set()
481
+
482
+ for _, event in self._event_log.iter_events():
483
+ if event.get("session_id") != session_id:
484
+ continue
485
+
486
+ payload = event.get("payload", {})
487
+ commit_hash = payload.get("commit_hash")
488
+ if commit_hash:
489
+ commits.add(commit_hash)
490
+
491
+ # Get commit timestamps from Git for chronological ordering
492
+ commit_list = []
493
+ for commit_hash in commits:
494
+ try:
495
+ timestamp = self._get_commit_timestamp(commit_hash)
496
+ commit_list.append((timestamp, commit_hash))
497
+ except Exception:
498
+ commit_list.append((datetime.min, commit_hash))
499
+
500
+ commit_list.sort(key=lambda x: x[0])
501
+ return [commit_hash for _, commit_hash in commit_list]
502
+
503
+ # === Private Helper Methods ===
504
+
505
+ def _find_repo_root(self, start_path: Path) -> Path | None:
506
+ """Find the Git repository root directory."""
507
+ try:
508
+ result = subprocess.run(
509
+ ["git", "rev-parse", "--show-toplevel"],
510
+ cwd=str(start_path),
511
+ capture_output=True,
512
+ text=True,
513
+ check=True,
514
+ )
515
+ return Path(result.stdout.strip())
516
+ except (subprocess.CalledProcessError, FileNotFoundError):
517
+ return None
518
+
519
+ def _get_commits_in_range(
520
+ self, from_commit: str | None, to_commit: str
521
+ ) -> list[dict[str, Any]]:
522
+ """
523
+ Get list of commits in a range using Git.
524
+
525
+ Args:
526
+ from_commit: Starting commit (None = from beginning)
527
+ to_commit: Ending commit
528
+
529
+ Returns:
530
+ List of commit dictionaries with hash, author, date, message
531
+ """
532
+ if not self._repo_root:
533
+ return []
534
+
535
+ try:
536
+ # Build Git log command
537
+ if from_commit:
538
+ rev_range = f"{from_commit}..{to_commit}"
539
+ else:
540
+ rev_range = to_commit
541
+
542
+ # Get commit info in JSON-like format
543
+ result = subprocess.run(
544
+ [
545
+ "git",
546
+ "log",
547
+ rev_range,
548
+ "--pretty=format:%H|%h|%an|%ae|%aI|%s",
549
+ ],
550
+ cwd=str(self._repo_root),
551
+ capture_output=True,
552
+ text=True,
553
+ check=True,
554
+ )
555
+
556
+ commits = []
557
+ for line in result.stdout.strip().split("\n"):
558
+ if not line:
559
+ continue
560
+
561
+ parts = line.split("|")
562
+ if len(parts) < 6:
563
+ continue
564
+
565
+ commits.append(
566
+ {
567
+ "hash": parts[0],
568
+ "hash_short": parts[1],
569
+ "author_name": parts[2],
570
+ "author_email": parts[3],
571
+ "date": parts[4],
572
+ "subject": parts[5],
573
+ }
574
+ )
575
+
576
+ return commits
577
+
578
+ except (subprocess.CalledProcessError, FileNotFoundError):
579
+ return []
580
+
581
+ def _get_commit_timestamp(self, commit_hash: str) -> datetime:
582
+ """Get timestamp for a commit."""
583
+ if not self._repo_root:
584
+ raise ValueError("Not in a Git repository")
585
+
586
+ try:
587
+ result = subprocess.run(
588
+ ["git", "log", "-1", "--format=%aI", commit_hash],
589
+ cwd=str(self._repo_root),
590
+ capture_output=True,
591
+ text=True,
592
+ check=True,
593
+ )
594
+ return datetime.fromisoformat(result.stdout.strip())
595
+ except subprocess.CalledProcessError:
596
+ raise ValueError(f"Commit not found: {commit_hash}")
597
+
598
+ def _parse_timestamp(self, timestamp: str | datetime | None) -> datetime:
599
+ """Parse timestamp from various formats."""
600
+ if timestamp is None:
601
+ return datetime.now()
602
+
603
+ if isinstance(timestamp, datetime):
604
+ return timestamp
605
+
606
+ if isinstance(timestamp, str):
607
+ try:
608
+ return datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
609
+ except (ValueError, AttributeError):
610
+ return datetime.now()
611
+
612
+ return datetime.now()
@@ -0,0 +1,24 @@
1
+ """
2
+ Archive management system for HtmlGraph.
3
+
4
+ Provides three-tier optimized search:
5
+ - Tier 1: Bloom filters (skip 70-90% of archives)
6
+ - Tier 2: SQLite FTS5 with BM25 ranking
7
+ - Tier 3: Snippet extraction and highlighting
8
+
9
+ Target: 67x faster than naive multi-file search.
10
+ """
11
+
12
+ from htmlgraph.archive.bloom import BloomFilter
13
+ from htmlgraph.archive.fts import ArchiveFTS5Index
14
+ from htmlgraph.archive.manager import ArchiveConfig, ArchiveManager
15
+ from htmlgraph.archive.search import ArchiveSearchEngine, SearchResult
16
+
17
+ __all__ = [
18
+ "ArchiveManager",
19
+ "ArchiveConfig",
20
+ "ArchiveSearchEngine",
21
+ "SearchResult",
22
+ "BloomFilter",
23
+ "ArchiveFTS5Index",
24
+ ]