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,511 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ Todo collection for managing persistent todo items.
5
+
6
+ Unlike other collections, TodoCollection provides:
7
+ - Ephemeral-style API matching TodoWrite format
8
+ - Session-scoped todos that persist across context boundaries
9
+ - Automatic linking to current session and feature
10
+ - Bulk sync from TodoWrite format
11
+ """
12
+
13
+
14
+ from datetime import datetime
15
+ from typing import TYPE_CHECKING, Any
16
+
17
+ from htmlgraph.ids import generate_id
18
+
19
+ if TYPE_CHECKING:
20
+ from htmlgraph.models import Todo
21
+ from htmlgraph.sdk import SDK
22
+
23
+
24
+ class TodoCollection:
25
+ """
26
+ Collection interface for persistent todos.
27
+
28
+ Provides an API similar to TodoWrite but with persistence.
29
+ Todos are stored as HTML files in `.htmlgraph/todos/`.
30
+
31
+ Example:
32
+ >>> sdk = SDK(agent="claude")
33
+ >>>
34
+ >>> # Create todos (mirrors TodoWrite API)
35
+ >>> sdk.todos.add("Run tests", "Running tests")
36
+ >>> sdk.todos.add("Fix errors", "Fixing errors")
37
+ >>>
38
+ >>> # Start a todo
39
+ >>> sdk.todos.start("todo-abc123")
40
+ >>>
41
+ >>> # Complete a todo
42
+ >>> sdk.todos.complete("todo-abc123")
43
+ >>>
44
+ >>> # List current todos
45
+ >>> pending = sdk.todos.pending()
46
+ >>> in_progress = sdk.todos.in_progress()
47
+ >>>
48
+ >>> # Sync from TodoWrite format
49
+ >>> sdk.todos.sync_from_todowrite([
50
+ ... {"content": "Task 1", "status": "pending", "activeForm": "Doing task 1"},
51
+ ... {"content": "Task 2", "status": "completed", "activeForm": "Doing task 2"},
52
+ ... ])
53
+ """
54
+
55
+ def __init__(self, sdk: SDK):
56
+ """
57
+ Initialize todo collection.
58
+
59
+ Args:
60
+ sdk: Parent SDK instance
61
+ """
62
+ self._sdk = sdk
63
+ self._todos_dir = sdk._directory / "todos"
64
+ self._todos_dir.mkdir(parents=True, exist_ok=True)
65
+
66
+ # Cache for loaded todos (lazy-loaded)
67
+ self._todos: dict[str, Todo] | None = None
68
+ self._ref_manager: Any = None # Set by SDK during initialization
69
+
70
+ def set_ref_manager(self, ref_manager: Any) -> None:
71
+ """
72
+ Set the ref manager for this collection.
73
+
74
+ Called by SDK during initialization to enable short ref support.
75
+
76
+ Args:
77
+ ref_manager: RefManager instance from SDK
78
+ """
79
+ self._ref_manager = ref_manager
80
+
81
+ def _ensure_loaded(self) -> dict[str, Todo]:
82
+ """Load todos from disk if not cached."""
83
+ if self._todos is None:
84
+ self._todos = {}
85
+ self._load_todos()
86
+ return self._todos
87
+
88
+ def _load_todos(self) -> None:
89
+ """Load all todos from HTML files."""
90
+ from htmlgraph.models import Todo
91
+ from htmlgraph.parser import HtmlParser
92
+
93
+ self._todos = {}
94
+
95
+ for html_file in self._todos_dir.glob("*.html"):
96
+ try:
97
+ parser = HtmlParser(filepath=html_file)
98
+ article = parser.get_article()
99
+ if not article:
100
+ continue
101
+
102
+ # Get type to verify this is a todo
103
+ node_type = parser.get_data_attribute(article, "type")
104
+ if node_type != "todo":
105
+ continue
106
+
107
+ # Get all data attributes
108
+ all_attrs = parser.get_all_data_attributes(article)
109
+
110
+ # Parse timestamps
111
+ created = datetime.now()
112
+ if all_attrs.get("created"):
113
+ try:
114
+ created = datetime.fromisoformat(all_attrs["created"])
115
+ except ValueError:
116
+ pass
117
+
118
+ updated = datetime.now()
119
+ if all_attrs.get("updated"):
120
+ try:
121
+ updated = datetime.fromisoformat(all_attrs["updated"])
122
+ except ValueError:
123
+ pass
124
+
125
+ started_at = None
126
+ if all_attrs.get("started-at"):
127
+ try:
128
+ started_at = datetime.fromisoformat(all_attrs["started-at"])
129
+ except ValueError:
130
+ pass
131
+
132
+ completed_at = None
133
+ if all_attrs.get("completed-at"):
134
+ try:
135
+ completed_at = datetime.fromisoformat(all_attrs["completed-at"])
136
+ except ValueError:
137
+ pass
138
+
139
+ # Get content from data attributes
140
+ content = all_attrs.get("todo-content", "")
141
+ active_form = all_attrs.get("todo-active-form", content)
142
+
143
+ # Get status with default
144
+ status = all_attrs.get("status", "pending")
145
+ if status not in ("pending", "in_progress", "completed"):
146
+ status = "pending"
147
+
148
+ todo = Todo(
149
+ id=article.attrs.get("id", html_file.stem),
150
+ content=content,
151
+ active_form=active_form,
152
+ status=status, # type: ignore
153
+ created=created,
154
+ updated=updated,
155
+ started_at=started_at,
156
+ completed_at=completed_at,
157
+ session_id=all_attrs.get("session-id"),
158
+ feature_id=all_attrs.get("feature-id"),
159
+ agent=all_attrs.get("agent"),
160
+ completed_by=all_attrs.get("completed-by"),
161
+ priority=int(all_attrs.get("priority", 0)),
162
+ duration_seconds=float(all_attrs["duration"])
163
+ if all_attrs.get("duration")
164
+ else None,
165
+ )
166
+ self._todos[todo.id] = todo
167
+ except Exception:
168
+ # Skip malformed files
169
+ pass
170
+
171
+ def _save_todo(self, todo: Todo) -> None:
172
+ """Save a todo to disk."""
173
+ html_path = self._todos_dir / f"{todo.id}.html"
174
+ html_path.write_text(todo.to_html())
175
+
176
+ def _delete_todo_file(self, todo_id: str) -> bool:
177
+ """Delete a todo file from disk."""
178
+ html_path = self._todos_dir / f"{todo_id}.html"
179
+ if html_path.exists():
180
+ html_path.unlink()
181
+ return True
182
+ return False
183
+
184
+ def add(
185
+ self,
186
+ content: str,
187
+ active_form: str | None = None,
188
+ feature_id: str | None = None,
189
+ priority: int | None = None,
190
+ ) -> Todo:
191
+ """
192
+ Add a new todo.
193
+
194
+ Args:
195
+ content: The imperative form (e.g., "Run tests")
196
+ active_form: The present continuous form (e.g., "Running tests")
197
+ Defaults to content if not provided
198
+ feature_id: Optional feature to link this todo to
199
+ priority: Order in the list (auto-assigned if not provided)
200
+
201
+ Returns:
202
+ The created Todo
203
+
204
+ Example:
205
+ >>> todo = sdk.todos.add("Fix authentication bug", "Fixing authentication bug")
206
+ """
207
+ from htmlgraph.models import Todo
208
+
209
+ todos = self._ensure_loaded()
210
+
211
+ # Generate ID
212
+ todo_id = generate_id(node_type="todo", title=content)
213
+
214
+ # Get current session/feature context
215
+ session_id = None
216
+ if (
217
+ hasattr(self._sdk, "session_manager")
218
+ and self._sdk.session_manager._active_session
219
+ ):
220
+ session_id = self._sdk.session_manager._active_session.id
221
+
222
+ # Use provided feature_id or try to get from active work
223
+ if not feature_id and hasattr(self._sdk, "session_manager"):
224
+ # Try to get primary feature from session
225
+ active_session = self._sdk.session_manager._active_session
226
+ if active_session and active_session.worked_on:
227
+ feature_id = active_session.worked_on[0]
228
+
229
+ # Auto-assign priority if not provided
230
+ if priority is None:
231
+ priority = len([t for t in todos.values() if t.status != "completed"])
232
+
233
+ todo = Todo(
234
+ id=todo_id,
235
+ content=content,
236
+ active_form=active_form or content,
237
+ status="pending",
238
+ session_id=session_id,
239
+ feature_id=feature_id,
240
+ agent=self._sdk.agent,
241
+ priority=priority,
242
+ )
243
+
244
+ # Save to cache and disk
245
+ todos[todo_id] = todo
246
+ self._save_todo(todo)
247
+
248
+ return todo
249
+
250
+ def get(self, todo_id: str) -> Todo | None:
251
+ """
252
+ Get a todo by ID.
253
+
254
+ Args:
255
+ todo_id: Todo ID to retrieve
256
+
257
+ Returns:
258
+ Todo if found, None otherwise
259
+ """
260
+ return self._ensure_loaded().get(todo_id)
261
+
262
+ def all(self) -> list[Todo]:
263
+ """
264
+ Get all todos.
265
+
266
+ Returns:
267
+ List of all todos, ordered by priority
268
+ """
269
+ todos = list(self._ensure_loaded().values())
270
+ return sorted(todos, key=lambda t: t.priority)
271
+
272
+ def pending(self) -> list[Todo]:
273
+ """
274
+ Get all pending todos.
275
+
276
+ Returns:
277
+ List of pending todos, ordered by priority
278
+ """
279
+ todos = [t for t in self._ensure_loaded().values() if t.status == "pending"]
280
+ return sorted(todos, key=lambda t: t.priority)
281
+
282
+ def in_progress(self) -> list[Todo]:
283
+ """
284
+ Get all in-progress todos.
285
+
286
+ Returns:
287
+ List of in-progress todos
288
+ """
289
+ return [t for t in self._ensure_loaded().values() if t.status == "in_progress"]
290
+
291
+ def completed(self) -> list[Todo]:
292
+ """
293
+ Get all completed todos.
294
+
295
+ Returns:
296
+ List of completed todos
297
+ """
298
+ return [t for t in self._ensure_loaded().values() if t.status == "completed"]
299
+
300
+ def start(self, todo_id: str) -> Todo | None:
301
+ """
302
+ Start working on a todo.
303
+
304
+ Args:
305
+ todo_id: Todo ID to start
306
+
307
+ Returns:
308
+ Updated Todo, or None if not found
309
+ """
310
+ todos = self._ensure_loaded()
311
+ todo = todos.get(todo_id)
312
+ if not todo:
313
+ return None
314
+
315
+ todo.start()
316
+ self._save_todo(todo)
317
+ return todo
318
+
319
+ def complete(self, todo_id: str) -> Todo | None:
320
+ """
321
+ Complete a todo.
322
+
323
+ Args:
324
+ todo_id: Todo ID to complete
325
+
326
+ Returns:
327
+ Updated Todo, or None if not found
328
+ """
329
+ todos = self._ensure_loaded()
330
+ todo = todos.get(todo_id)
331
+ if not todo:
332
+ return None
333
+
334
+ todo.complete(agent=self._sdk.agent)
335
+ self._save_todo(todo)
336
+ return todo
337
+
338
+ def delete(self, todo_id: str) -> bool:
339
+ """
340
+ Delete a todo.
341
+
342
+ Args:
343
+ todo_id: Todo ID to delete
344
+
345
+ Returns:
346
+ True if deleted, False if not found
347
+ """
348
+ todos = self._ensure_loaded()
349
+ if todo_id in todos:
350
+ del todos[todo_id]
351
+ return self._delete_todo_file(todo_id)
352
+ return False
353
+
354
+ def clear_completed(self) -> int:
355
+ """
356
+ Remove all completed todos.
357
+
358
+ Returns:
359
+ Number of todos removed
360
+ """
361
+ todos = self._ensure_loaded()
362
+ completed_ids = [t.id for t in todos.values() if t.status == "completed"]
363
+
364
+ for todo_id in completed_ids:
365
+ del todos[todo_id]
366
+ self._delete_todo_file(todo_id)
367
+
368
+ return len(completed_ids)
369
+
370
+ def sync_from_todowrite(
371
+ self,
372
+ todowrite_list: list[dict[str, str]],
373
+ feature_id: str | None = None,
374
+ clear_existing: bool = False,
375
+ ) -> list[Todo]:
376
+ """
377
+ Sync todos from TodoWrite format.
378
+
379
+ This enables capturing ephemeral TodoWrite data into persistent storage.
380
+
381
+ Args:
382
+ todowrite_list: List of dicts with 'content', 'status', 'activeForm' keys
383
+ feature_id: Optional feature to link all todos to
384
+ clear_existing: If True, removes existing session todos first
385
+
386
+ Returns:
387
+ List of created/updated todos
388
+
389
+ Example:
390
+ >>> sdk.todos.sync_from_todowrite([
391
+ ... {"content": "Run tests", "status": "pending", "activeForm": "Running tests"},
392
+ ... {"content": "Fix errors", "status": "completed", "activeForm": "Fixing errors"},
393
+ ... ])
394
+ """
395
+ from htmlgraph.models import Todo
396
+
397
+ todos = self._ensure_loaded()
398
+
399
+ # Get current session
400
+ session_id = None
401
+ if (
402
+ hasattr(self._sdk, "session_manager")
403
+ and self._sdk.session_manager._active_session
404
+ ):
405
+ session_id = self._sdk.session_manager._active_session.id
406
+
407
+ # Clear existing session todos if requested
408
+ if clear_existing and session_id:
409
+ session_todo_ids = [
410
+ t.id for t in todos.values() if t.session_id == session_id
411
+ ]
412
+ for todo_id in session_todo_ids:
413
+ del todos[todo_id]
414
+ self._delete_todo_file(todo_id)
415
+
416
+ result = []
417
+ for i, item in enumerate(todowrite_list):
418
+ todo_id = generate_id(node_type="todo", title=item.get("content", ""))
419
+
420
+ todo = Todo.from_todowrite(
421
+ todo_dict=item,
422
+ todo_id=todo_id,
423
+ session_id=session_id,
424
+ feature_id=feature_id,
425
+ agent=self._sdk.agent,
426
+ priority=i,
427
+ )
428
+
429
+ todos[todo_id] = todo
430
+ self._save_todo(todo)
431
+ result.append(todo)
432
+
433
+ return result
434
+
435
+ def to_todowrite_format(self) -> list[dict[str, str]]:
436
+ """
437
+ Export todos to TodoWrite format.
438
+
439
+ Returns:
440
+ List of dicts compatible with TodoWrite tool
441
+
442
+ Example:
443
+ >>> todowrite_list = sdk.todos.to_todowrite_format()
444
+ >>> # Can be used with TodoWrite tool
445
+ """
446
+ todos = self.all() # Already sorted by priority
447
+ return [t.to_todowrite_format() for t in todos if t.status != "completed"]
448
+
449
+ def for_feature(self, feature_id: str) -> list[Todo]:
450
+ """
451
+ Get all todos for a specific feature.
452
+
453
+ Args:
454
+ feature_id: Feature ID to filter by
455
+
456
+ Returns:
457
+ List of todos linked to this feature
458
+ """
459
+ todos = [
460
+ t for t in self._ensure_loaded().values() if t.feature_id == feature_id
461
+ ]
462
+ return sorted(todos, key=lambda t: t.priority)
463
+
464
+ def for_session(self, session_id: str) -> list[Todo]:
465
+ """
466
+ Get all todos for a specific session.
467
+
468
+ Args:
469
+ session_id: Session ID to filter by
470
+
471
+ Returns:
472
+ List of todos from this session
473
+ """
474
+ todos = [
475
+ t for t in self._ensure_loaded().values() if t.session_id == session_id
476
+ ]
477
+ return sorted(todos, key=lambda t: t.priority)
478
+
479
+ def summary(self) -> dict[str, Any]:
480
+ """
481
+ Get summary statistics for todos.
482
+
483
+ Returns:
484
+ Dict with counts by status and other stats
485
+ """
486
+ todos = list(self._ensure_loaded().values())
487
+
488
+ pending_count = sum(1 for t in todos if t.status == "pending")
489
+ in_progress_count = sum(1 for t in todos if t.status == "in_progress")
490
+ completed_count = sum(1 for t in todos if t.status == "completed")
491
+
492
+ # Calculate average completion time
493
+ completed_durations = [
494
+ t.duration_seconds
495
+ for t in todos
496
+ if t.status == "completed" and t.duration_seconds is not None
497
+ ]
498
+ avg_duration = (
499
+ sum(completed_durations) / len(completed_durations)
500
+ if completed_durations
501
+ else None
502
+ )
503
+
504
+ return {
505
+ "total": len(todos),
506
+ "pending": pending_count,
507
+ "in_progress": in_progress_count,
508
+ "completed": completed_count,
509
+ "completion_rate": completed_count / len(todos) if todos else 0.0,
510
+ "avg_duration_seconds": avg_duration,
511
+ }