htmlgraph 0.26.24__py3-none-any.whl → 0.26.25__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 (33) hide show
  1. htmlgraph/__init__.py +1 -1
  2. htmlgraph/api/main.py +56 -23
  3. htmlgraph/api/templates/dashboard-redesign.html +3 -3
  4. htmlgraph/api/templates/dashboard.html +3 -3
  5. htmlgraph/api/templates/partials/work-items.html +613 -0
  6. htmlgraph/builders/track.py +26 -0
  7. htmlgraph/cli/base.py +31 -7
  8. htmlgraph/cli/work/__init__.py +74 -0
  9. htmlgraph/cli/work/browse.py +114 -0
  10. htmlgraph/cli/work/snapshot.py +558 -0
  11. htmlgraph/collections/base.py +34 -0
  12. htmlgraph/collections/todo.py +12 -0
  13. htmlgraph/converter.py +11 -0
  14. htmlgraph/hooks/orchestrator.py +88 -14
  15. htmlgraph/hooks/session_handler.py +3 -1
  16. htmlgraph/models.py +18 -1
  17. htmlgraph/orchestration/__init__.py +4 -0
  18. htmlgraph/orchestration/plugin_manager.py +1 -2
  19. htmlgraph/orchestration/spawner_event_tracker.py +383 -0
  20. htmlgraph/refs.py +343 -0
  21. htmlgraph/sdk.py +71 -1
  22. htmlgraph/session_manager.py +1 -7
  23. htmlgraph/sessions/handoff.py +6 -0
  24. htmlgraph/track_builder.py +12 -0
  25. {htmlgraph-0.26.24.dist-info → htmlgraph-0.26.25.dist-info}/METADATA +1 -1
  26. {htmlgraph-0.26.24.dist-info → htmlgraph-0.26.25.dist-info}/RECORD +33 -28
  27. {htmlgraph-0.26.24.data → htmlgraph-0.26.25.data}/data/htmlgraph/dashboard.html +0 -0
  28. {htmlgraph-0.26.24.data → htmlgraph-0.26.25.data}/data/htmlgraph/styles.css +0 -0
  29. {htmlgraph-0.26.24.data → htmlgraph-0.26.25.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  30. {htmlgraph-0.26.24.data → htmlgraph-0.26.25.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  31. {htmlgraph-0.26.24.data → htmlgraph-0.26.25.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  32. {htmlgraph-0.26.24.dist-info → htmlgraph-0.26.25.dist-info}/WHEEL +0 -0
  33. {htmlgraph-0.26.24.dist-info → htmlgraph-0.26.25.dist-info}/entry_points.txt +0 -0
htmlgraph/__init__.py CHANGED
@@ -96,7 +96,7 @@ from htmlgraph.types import (
96
96
  )
97
97
  from htmlgraph.work_type_utils import infer_work_type, infer_work_type_from_id
98
98
 
99
- __version__ = "0.26.24"
99
+ __version__ = "0.26.25"
100
100
  __all__ = [
101
101
  # Exceptions
102
102
  "HtmlGraphError",
htmlgraph/api/main.py CHANGED
@@ -267,7 +267,19 @@ def get_app(db_path: str) -> FastAPI:
267
267
  COUNT(*) as event_count,
268
268
  SUM(e.cost_tokens) as total_tokens,
269
269
  COUNT(DISTINCT e.session_id) as session_count,
270
- MAX(e.timestamp) as last_active
270
+ MAX(e.timestamp) as last_active,
271
+ MAX(e.model) as model,
272
+ CASE
273
+ WHEN MAX(e.timestamp) > datetime('now', '-5 minutes') THEN 'active'
274
+ ELSE 'idle'
275
+ END as status,
276
+ AVG(e.execution_duration_seconds) as avg_duration,
277
+ SUM(CASE WHEN e.event_type = 'error' THEN 1 ELSE 0 END) as error_count,
278
+ ROUND(
279
+ 100.0 * COUNT(CASE WHEN e.status = 'completed' THEN 1 END) /
280
+ CAST(COUNT(*) AS FLOAT),
281
+ 1
282
+ ) as success_rate
271
283
  FROM agent_events e
272
284
  GROUP BY e.agent_id
273
285
  ORDER BY event_count DESC
@@ -297,11 +309,19 @@ def get_app(db_path: str) -> FastAPI:
297
309
 
298
310
  agents.append(
299
311
  {
312
+ "id": row[0],
300
313
  "agent_id": row[0],
314
+ "name": row[0],
301
315
  "event_count": event_count,
302
316
  "total_tokens": row[2] or 0,
303
317
  "session_count": row[3],
318
+ "last_activity": row[4],
304
319
  "last_active": row[4],
320
+ "model": row[5] or "unknown",
321
+ "status": row[6] or "idle",
322
+ "avg_duration": row[7],
323
+ "error_count": row[8] or 0,
324
+ "success_rate": row[9] or 0.0,
305
325
  "workload_pct": round(workload_pct, 1),
306
326
  }
307
327
  )
@@ -1663,22 +1683,29 @@ def get_app(db_path: str) -> FastAPI:
1663
1683
  finally:
1664
1684
  await db.close()
1665
1685
 
1666
- # ========== FEATURES ENDPOINTS ==========
1686
+ # ========== WORK ITEMS ENDPOINTS ==========
1667
1687
 
1668
1688
  @app.get("/views/features", response_class=HTMLResponse)
1669
- async def features_view(request: Request, status: str = "all") -> HTMLResponse:
1670
- """Get features by status as HTMX partial."""
1689
+ async def features_view_redirect(
1690
+ request: Request, status: str = "all"
1691
+ ) -> HTMLResponse:
1692
+ """Redirect to work-items view (legacy endpoint for backward compatibility)."""
1693
+ return await work_items_view(request, status)
1694
+
1695
+ @app.get("/views/work-items", response_class=HTMLResponse)
1696
+ async def work_items_view(request: Request, status: str = "all") -> HTMLResponse:
1697
+ """Get work items (features, bugs, spikes) by status as HTMX partial."""
1671
1698
  db = await get_db()
1672
1699
  cache = app.state.query_cache
1673
1700
  query_start_time = time.time()
1674
1701
 
1675
1702
  try:
1676
1703
  # Create cache key from query parameters
1677
- cache_key = f"features_view:{status}"
1704
+ cache_key = f"work_items_view:{status}"
1678
1705
 
1679
1706
  # Check cache first
1680
1707
  cached_response = cache.get(cache_key)
1681
- features_by_status: dict = {
1708
+ work_items_by_status: dict = {
1682
1709
  "todo": [],
1683
1710
  "in_progress": [],
1684
1711
  "blocked": [],
@@ -1689,9 +1716,9 @@ def get_app(db_path: str) -> FastAPI:
1689
1716
  query_time_ms = (time.time() - query_start_time) * 1000
1690
1717
  cache.record_metric(cache_key, query_time_ms, cache_hit=True)
1691
1718
  logger.debug(
1692
- f"Cache HIT for features_view (key={cache_key}, time={query_time_ms:.2f}ms)"
1719
+ f"Cache HIT for work_items_view (key={cache_key}, time={query_time_ms:.2f}ms)"
1693
1720
  )
1694
- features_by_status = cached_response
1721
+ work_items_by_status = cached_response
1695
1722
  else:
1696
1723
  # OPTIMIZATION: Use composite index idx_features_status_priority
1697
1724
  # for efficient filtering and ordering
@@ -1733,37 +1760,37 @@ def get_app(db_path: str) -> FastAPI:
1733
1760
  exec_time_ms = (time.time() - exec_start) * 1000
1734
1761
 
1735
1762
  for row in rows:
1736
- feature_id = row[0]
1737
- feature_status = row[3]
1738
- features_by_status.setdefault(feature_status, []).append(
1763
+ item_id = row[0]
1764
+ item_status = row[3]
1765
+ work_items_by_status.setdefault(item_status, []).append(
1739
1766
  {
1740
- "id": feature_id,
1767
+ "id": item_id,
1741
1768
  "type": row[1],
1742
1769
  "title": row[2],
1743
- "status": feature_status,
1770
+ "status": item_status,
1744
1771
  "priority": row[4],
1745
1772
  "assigned_to": row[5],
1746
1773
  "created_at": row[6],
1747
1774
  "updated_at": row[7],
1748
1775
  "description": row[8],
1749
- "contributors": feature_agents.get(feature_id, []),
1776
+ "contributors": feature_agents.get(item_id, []),
1750
1777
  }
1751
1778
  )
1752
1779
 
1753
1780
  # Cache the results
1754
- cache.set(cache_key, features_by_status)
1781
+ cache.set(cache_key, work_items_by_status)
1755
1782
  query_time_ms = (time.time() - query_start_time) * 1000
1756
1783
  cache.record_metric(cache_key, exec_time_ms, cache_hit=False)
1757
1784
  logger.debug(
1758
- f"Cache MISS for features_view (key={cache_key}, "
1785
+ f"Cache MISS for work_items_view (key={cache_key}, "
1759
1786
  f"db_time={exec_time_ms:.2f}ms, total_time={query_time_ms:.2f}ms)"
1760
1787
  )
1761
1788
 
1762
1789
  return templates.TemplateResponse(
1763
- "partials/features.html",
1790
+ "partials/work-items.html",
1764
1791
  {
1765
1792
  "request": request,
1766
- "features_by_status": features_by_status,
1793
+ "work_items_by_status": work_items_by_status,
1767
1794
  },
1768
1795
  )
1769
1796
  finally:
@@ -1830,19 +1857,19 @@ def get_app(db_path: str) -> FastAPI:
1830
1857
  else:
1831
1858
  # OPTIMIZATION: Combine session data with event counts in single query
1832
1859
  # This eliminates N+1 query problem (was 20+ queries, now 2)
1833
- # Note: Database uses agent_assigned but started_at/ended_at (partial migration)
1860
+ # Note: Database uses created_at and completed_at (not started_at/ended_at)
1834
1861
  query = """
1835
1862
  SELECT
1836
1863
  s.session_id,
1837
1864
  s.agent_assigned,
1838
1865
  s.status,
1839
- s.started_at,
1840
- s.ended_at,
1866
+ s.created_at,
1867
+ s.completed_at,
1841
1868
  COUNT(DISTINCT e.event_id) as event_count
1842
1869
  FROM sessions s
1843
1870
  LEFT JOIN agent_events e ON s.session_id = e.session_id
1844
1871
  GROUP BY s.session_id
1845
- ORDER BY s.started_at DESC
1872
+ ORDER BY s.created_at DESC
1846
1873
  LIMIT 20
1847
1874
  """
1848
1875
 
@@ -1860,7 +1887,13 @@ def get_app(db_path: str) -> FastAPI:
1860
1887
  ended_at = datetime.fromisoformat(row[4])
1861
1888
  duration_seconds = (ended_at - started_at).total_seconds()
1862
1889
  else:
1863
- duration_seconds = (datetime.now() - started_at).total_seconds()
1890
+ # Use UTC to handle timezone-aware datetime comparison
1891
+ now = (
1892
+ datetime.now(started_at.tzinfo)
1893
+ if started_at.tzinfo
1894
+ else datetime.now()
1895
+ )
1896
+ duration_seconds = (now - started_at).total_seconds()
1864
1897
 
1865
1898
  sessions.append(
1866
1899
  {
@@ -53,12 +53,12 @@
53
53
  <span class="tab-icon">◊</span>
54
54
  ORCHESTRATION
55
55
  </button>
56
- <button class="tab-button" data-tab="features"
57
- hx-get="/views/features"
56
+ <button class="tab-button" data-tab="work-items"
57
+ hx-get="/views/work-items"
58
58
  hx-target="#content-area"
59
59
  hx-trigger="click">
60
60
  <span class="tab-icon">█</span>
61
- FEATURES
61
+ WORK ITEMS
62
62
  </button>
63
63
  <button class="tab-button" data-tab="agents"
64
64
  hx-get="/views/agents"
@@ -52,12 +52,12 @@
52
52
  Orchestration
53
53
  </button>
54
54
  <button class="tab-button"
55
- hx-get="/views/features"
55
+ hx-get="/views/work-items"
56
56
  hx-target="#content-area"
57
57
  hx-trigger="click"
58
- data-tab="features">
58
+ data-tab="work-items">
59
59
  <span class="tab-icon">🎯</span>
60
- Features
60
+ Work Items
61
61
  </button>
62
62
  <button class="tab-button"
63
63
  hx-get="/views/agents"