htmlgraph 0.26.22__py3-none-any.whl → 0.26.24__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.
- htmlgraph/__init__.py +6 -1
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/cli/analytics.py +465 -0
- htmlgraph/cli/core.py +97 -0
- htmlgraph/cli/main.py +3 -0
- htmlgraph/cli/models.py +12 -0
- htmlgraph/cli/work/__init__.py +5 -0
- htmlgraph/cli/work/report.py +727 -0
- htmlgraph/db/schema.py +34 -1
- htmlgraph/decorators.py +317 -0
- htmlgraph/models.py +4 -1
- htmlgraph/operations/bootstrap.py +288 -0
- htmlgraph/sdk.py +91 -0
- htmlgraph/session_manager.py +160 -2
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +749 -0
- {htmlgraph-0.26.22.dist-info → htmlgraph-0.26.24.dist-info}/METADATA +1 -1
- {htmlgraph-0.26.22.dist-info → htmlgraph-0.26.24.dist-info}/RECORD +25 -19
- {htmlgraph-0.26.22.data → htmlgraph-0.26.24.data}/data/htmlgraph/dashboard.html +0 -0
- {htmlgraph-0.26.22.data → htmlgraph-0.26.24.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.26.22.data → htmlgraph-0.26.24.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.26.22.data → htmlgraph-0.26.24.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.26.22.data → htmlgraph-0.26.24.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.26.22.dist-info → htmlgraph-0.26.24.dist-info}/WHEEL +0 -0
- {htmlgraph-0.26.22.dist-info → htmlgraph-0.26.24.dist-info}/entry_points.txt +0 -0
htmlgraph/sdk.py
CHANGED
|
@@ -333,6 +333,11 @@ class SDK:
|
|
|
333
333
|
# Context analytics interface (Context usage tracking)
|
|
334
334
|
self.context = ContextAnalytics(self)
|
|
335
335
|
|
|
336
|
+
# Pattern learning interface (Phase 2: Behavior Pattern Learning)
|
|
337
|
+
from htmlgraph.analytics.pattern_learning import PatternLearner
|
|
338
|
+
|
|
339
|
+
self.pattern_learning = PatternLearner(self._directory)
|
|
340
|
+
|
|
336
341
|
# Lazy-loaded orchestrator for subagent management
|
|
337
342
|
self._orchestrator: Any = None
|
|
338
343
|
|
|
@@ -776,6 +781,92 @@ class SDK:
|
|
|
776
781
|
blockers=blockers,
|
|
777
782
|
)
|
|
778
783
|
|
|
784
|
+
def continue_from_last(
|
|
785
|
+
self,
|
|
786
|
+
agent: str | None = None,
|
|
787
|
+
auto_create_session: bool = True,
|
|
788
|
+
) -> tuple[Any, Any]:
|
|
789
|
+
"""
|
|
790
|
+
Continue work from the last completed session.
|
|
791
|
+
|
|
792
|
+
Loads context from previous session including handoff notes,
|
|
793
|
+
recommended files, blockers, and recent commits.
|
|
794
|
+
|
|
795
|
+
Args:
|
|
796
|
+
agent: Filter by agent (None = current SDK agent)
|
|
797
|
+
auto_create_session: Create new session if True
|
|
798
|
+
|
|
799
|
+
Returns:
|
|
800
|
+
Tuple of (new_session, resume_info) or (None, None)
|
|
801
|
+
|
|
802
|
+
Example:
|
|
803
|
+
>>> sdk = SDK(agent="claude")
|
|
804
|
+
>>> session, resume = sdk.continue_from_last()
|
|
805
|
+
>>> if resume:
|
|
806
|
+
... print(resume.summary)
|
|
807
|
+
... print(resume.next_focus)
|
|
808
|
+
... for file in resume.recommended_files:
|
|
809
|
+
... print(f" - {file}")
|
|
810
|
+
"""
|
|
811
|
+
if not agent:
|
|
812
|
+
agent = self._agent_id
|
|
813
|
+
|
|
814
|
+
return self.session_manager.continue_from_last(
|
|
815
|
+
agent=agent,
|
|
816
|
+
auto_create_session=auto_create_session,
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
def end_session_with_handoff(
|
|
820
|
+
self,
|
|
821
|
+
session_id: str | None = None,
|
|
822
|
+
summary: str | None = None,
|
|
823
|
+
next_focus: str | None = None,
|
|
824
|
+
blockers: list[str] | None = None,
|
|
825
|
+
keep_context: list[str] | None = None,
|
|
826
|
+
auto_recommend_context: bool = True,
|
|
827
|
+
) -> Any:
|
|
828
|
+
"""
|
|
829
|
+
End session with handoff information for next session.
|
|
830
|
+
|
|
831
|
+
Args:
|
|
832
|
+
session_id: Session to end (None = active session)
|
|
833
|
+
summary: What was accomplished
|
|
834
|
+
next_focus: What should be done next
|
|
835
|
+
blockers: List of blockers
|
|
836
|
+
keep_context: List of files to keep context for
|
|
837
|
+
auto_recommend_context: Auto-recommend files from git
|
|
838
|
+
|
|
839
|
+
Returns:
|
|
840
|
+
Updated Session or None
|
|
841
|
+
|
|
842
|
+
Example:
|
|
843
|
+
>>> sdk.end_session_with_handoff(
|
|
844
|
+
... summary="Completed OAuth integration",
|
|
845
|
+
... next_focus="Implement JWT token refresh",
|
|
846
|
+
... blockers=["Waiting for security review"],
|
|
847
|
+
... keep_context=["src/auth/oauth.py"]
|
|
848
|
+
... )
|
|
849
|
+
"""
|
|
850
|
+
if not session_id:
|
|
851
|
+
if self._agent_id:
|
|
852
|
+
active = self.session_manager.get_active_session_for_agent(
|
|
853
|
+
self._agent_id
|
|
854
|
+
)
|
|
855
|
+
else:
|
|
856
|
+
active = self.session_manager.get_active_session()
|
|
857
|
+
if not active:
|
|
858
|
+
return None
|
|
859
|
+
session_id = active.id
|
|
860
|
+
|
|
861
|
+
return self.session_manager.end_session_with_handoff(
|
|
862
|
+
session_id=session_id,
|
|
863
|
+
summary=summary,
|
|
864
|
+
next_focus=next_focus,
|
|
865
|
+
blockers=blockers,
|
|
866
|
+
keep_context=keep_context,
|
|
867
|
+
auto_recommend_context=auto_recommend_context,
|
|
868
|
+
)
|
|
869
|
+
|
|
779
870
|
def start_session(
|
|
780
871
|
self,
|
|
781
872
|
session_id: str | None = None,
|
htmlgraph/session_manager.py
CHANGED
|
@@ -186,7 +186,7 @@ class SessionManager:
|
|
|
186
186
|
"""Mark a session as stale (kept for history but not considered active)."""
|
|
187
187
|
if session.status != "active":
|
|
188
188
|
return
|
|
189
|
-
now = datetime.now()
|
|
189
|
+
now = datetime.now(timezone.utc)
|
|
190
190
|
session.status = "stale"
|
|
191
191
|
session.ended_at = now
|
|
192
192
|
session.last_activity = now
|
|
@@ -742,7 +742,7 @@ class SessionManager:
|
|
|
742
742
|
ActivityEntry(
|
|
743
743
|
tool="SessionEnd",
|
|
744
744
|
summary="Session ended",
|
|
745
|
-
timestamp=datetime.now(),
|
|
745
|
+
timestamp=datetime.now(timezone.utc),
|
|
746
746
|
)
|
|
747
747
|
)
|
|
748
748
|
|
|
@@ -792,6 +792,164 @@ class SessionManager:
|
|
|
792
792
|
|
|
793
793
|
return session
|
|
794
794
|
|
|
795
|
+
def continue_from_last(
|
|
796
|
+
self,
|
|
797
|
+
agent: str | None = None,
|
|
798
|
+
auto_create_session: bool = True,
|
|
799
|
+
) -> tuple[Session | None, Any]: # Returns (new_session, resume_info)
|
|
800
|
+
"""
|
|
801
|
+
Continue work from the last completed session.
|
|
802
|
+
|
|
803
|
+
Loads context from the previous session including:
|
|
804
|
+
- Handoff notes and next focus
|
|
805
|
+
- Blockers
|
|
806
|
+
- Recommended context files
|
|
807
|
+
- Recent commits
|
|
808
|
+
- Features worked on
|
|
809
|
+
|
|
810
|
+
Args:
|
|
811
|
+
agent: Filter by agent (None = current agent)
|
|
812
|
+
auto_create_session: Create new session if True
|
|
813
|
+
|
|
814
|
+
Returns:
|
|
815
|
+
Tuple of (new_session, resume_info) or (None, None) if no previous session
|
|
816
|
+
|
|
817
|
+
Example:
|
|
818
|
+
>>> manager = SessionManager(".htmlgraph")
|
|
819
|
+
>>> new_session, resume = manager.continue_from_last(agent="claude")
|
|
820
|
+
>>> if resume:
|
|
821
|
+
... print(resume.summary)
|
|
822
|
+
... print(resume.recommended_files)
|
|
823
|
+
"""
|
|
824
|
+
# Import handoff module
|
|
825
|
+
from typing import Any
|
|
826
|
+
|
|
827
|
+
from htmlgraph.sessions.handoff import SessionResume
|
|
828
|
+
|
|
829
|
+
# Create a minimal SDK-like object with just the directory
|
|
830
|
+
# to avoid circular dependency and database initialization issues
|
|
831
|
+
class MinimalSDK:
|
|
832
|
+
def __init__(self, directory: Path) -> None:
|
|
833
|
+
self._directory = directory
|
|
834
|
+
|
|
835
|
+
sdk: Any = MinimalSDK(self.graph_dir)
|
|
836
|
+
resume = SessionResume(sdk)
|
|
837
|
+
|
|
838
|
+
# Get last session
|
|
839
|
+
last_session = resume.get_last_session(agent=agent)
|
|
840
|
+
if not last_session:
|
|
841
|
+
return None, None
|
|
842
|
+
|
|
843
|
+
# Build resume info
|
|
844
|
+
resume_info = resume.build_resume_info(last_session)
|
|
845
|
+
|
|
846
|
+
# Create new session if requested
|
|
847
|
+
new_session = None
|
|
848
|
+
if auto_create_session:
|
|
849
|
+
from htmlgraph.ids import generate_id
|
|
850
|
+
|
|
851
|
+
session_id = generate_id("sess")
|
|
852
|
+
new_session = self.start_session(
|
|
853
|
+
session_id=session_id,
|
|
854
|
+
agent=agent or last_session.agent,
|
|
855
|
+
title=f"Continuing from {last_session.id}",
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
# Link to previous session
|
|
859
|
+
new_session.continued_from = last_session.id
|
|
860
|
+
self.session_converter.save(new_session)
|
|
861
|
+
|
|
862
|
+
return new_session, resume_info
|
|
863
|
+
|
|
864
|
+
def end_session_with_handoff(
|
|
865
|
+
self,
|
|
866
|
+
session_id: str,
|
|
867
|
+
summary: str | None = None,
|
|
868
|
+
next_focus: str | None = None,
|
|
869
|
+
blockers: list[str] | None = None,
|
|
870
|
+
keep_context: list[str] | None = None,
|
|
871
|
+
auto_recommend_context: bool = True,
|
|
872
|
+
) -> Session | None:
|
|
873
|
+
"""
|
|
874
|
+
End session with handoff information for next session.
|
|
875
|
+
|
|
876
|
+
Args:
|
|
877
|
+
session_id: Session to end
|
|
878
|
+
summary: What was accomplished (handoff notes)
|
|
879
|
+
next_focus: What should be done next
|
|
880
|
+
blockers: List of blockers preventing progress
|
|
881
|
+
keep_context: List of files to keep context for
|
|
882
|
+
auto_recommend_context: Auto-recommend files from git history
|
|
883
|
+
|
|
884
|
+
Returns:
|
|
885
|
+
Updated session or None
|
|
886
|
+
|
|
887
|
+
Example:
|
|
888
|
+
>>> manager.end_session_with_handoff(
|
|
889
|
+
... session_id="sess-123",
|
|
890
|
+
... summary="Completed OAuth integration",
|
|
891
|
+
... next_focus="Implement JWT token refresh",
|
|
892
|
+
... blockers=["Waiting for security review"],
|
|
893
|
+
... keep_context=["src/auth/oauth.py"]
|
|
894
|
+
... )
|
|
895
|
+
"""
|
|
896
|
+
from htmlgraph.sessions.handoff import (
|
|
897
|
+
ContextRecommender,
|
|
898
|
+
HandoffBuilder,
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
# Get session
|
|
902
|
+
session = self.get_session(session_id)
|
|
903
|
+
if not session:
|
|
904
|
+
return None
|
|
905
|
+
|
|
906
|
+
# Build handoff using HandoffBuilder
|
|
907
|
+
builder = HandoffBuilder(session)
|
|
908
|
+
|
|
909
|
+
if summary:
|
|
910
|
+
builder.add_summary(summary)
|
|
911
|
+
|
|
912
|
+
if next_focus:
|
|
913
|
+
builder.add_next_focus(next_focus)
|
|
914
|
+
|
|
915
|
+
if blockers:
|
|
916
|
+
builder.add_blockers(blockers)
|
|
917
|
+
|
|
918
|
+
if keep_context:
|
|
919
|
+
builder.add_context_files(keep_context)
|
|
920
|
+
|
|
921
|
+
# Auto-recommend context files
|
|
922
|
+
if auto_recommend_context:
|
|
923
|
+
recommender = ContextRecommender()
|
|
924
|
+
builder.auto_recommend_context(recommender, max_files=10)
|
|
925
|
+
|
|
926
|
+
handoff_data = builder.build()
|
|
927
|
+
|
|
928
|
+
# Update session with handoff data
|
|
929
|
+
session.handoff_notes = handoff_data["handoff_notes"]
|
|
930
|
+
session.recommended_next = handoff_data["recommended_next"]
|
|
931
|
+
session.blockers = handoff_data["blockers"]
|
|
932
|
+
|
|
933
|
+
# Store recommended_context as JSON-serializable list
|
|
934
|
+
# (Session model expects list[str], converter will handle serialization)
|
|
935
|
+
if hasattr(session, "__dict__"):
|
|
936
|
+
session.__dict__["recommended_context"] = handoff_data[
|
|
937
|
+
"recommended_context"
|
|
938
|
+
]
|
|
939
|
+
|
|
940
|
+
# Persist handoff data to database before ending session
|
|
941
|
+
self.session_converter.save(session)
|
|
942
|
+
|
|
943
|
+
# End the session
|
|
944
|
+
self.end_session(session_id)
|
|
945
|
+
|
|
946
|
+
# Track handoff effectiveness (optional - only if database available)
|
|
947
|
+
# Note: SessionManager doesn't have direct database access,
|
|
948
|
+
# handoff tracking is primarily done through SDK
|
|
949
|
+
# Users should use SDK.end_session_with_handoff() for full tracking
|
|
950
|
+
|
|
951
|
+
return session
|
|
952
|
+
|
|
795
953
|
def release_session_features(self, session_id: str) -> list[str]:
|
|
796
954
|
"""
|
|
797
955
|
Release all features claimed by a specific session.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session management and continuity.
|
|
3
|
+
|
|
4
|
+
Provides session lifecycle, handoff, and resumption features.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from htmlgraph.sessions.handoff import (
|
|
8
|
+
ContextRecommender,
|
|
9
|
+
HandoffBuilder,
|
|
10
|
+
HandoffMetrics,
|
|
11
|
+
HandoffTracker,
|
|
12
|
+
SessionResume,
|
|
13
|
+
SessionResumeInfo,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"HandoffBuilder",
|
|
18
|
+
"SessionResume",
|
|
19
|
+
"SessionResumeInfo",
|
|
20
|
+
"HandoffTracker",
|
|
21
|
+
"HandoffMetrics",
|
|
22
|
+
"ContextRecommender",
|
|
23
|
+
]
|