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/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,
@@ -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
+ ]