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.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +173 -17
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +127 -0
- htmlgraph/agent_registry.py +45 -30
- htmlgraph/agents.py +160 -107
- htmlgraph/analytics/__init__.py +9 -2
- htmlgraph/analytics/cli.py +190 -51
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +192 -100
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +190 -14
- htmlgraph/analytics_index.py +135 -51
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +1366 -0
- htmlgraph/api/templates/dashboard.html +794 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +1100 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +578 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +208 -0
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/__init__.py +14 -0
- htmlgraph/builders/base.py +118 -29
- htmlgraph/builders/bug.py +150 -0
- htmlgraph/builders/chore.py +119 -0
- htmlgraph/builders/epic.py +150 -0
- htmlgraph/builders/feature.py +31 -6
- htmlgraph/builders/insight.py +195 -0
- htmlgraph/builders/metric.py +217 -0
- htmlgraph/builders/pattern.py +202 -0
- htmlgraph/builders/phase.py +162 -0
- htmlgraph/builders/spike.py +52 -19
- htmlgraph/builders/track.py +148 -72
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +18 -0
- htmlgraph/collections/base.py +415 -98
- htmlgraph/collections/bug.py +53 -0
- htmlgraph/collections/chore.py +53 -0
- htmlgraph/collections/epic.py +53 -0
- htmlgraph/collections/feature.py +12 -26
- htmlgraph/collections/insight.py +100 -0
- htmlgraph/collections/metric.py +92 -0
- htmlgraph/collections/pattern.py +97 -0
- htmlgraph/collections/phase.py +53 -0
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +56 -16
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +511 -0
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +344 -0
- htmlgraph/converter.py +216 -28
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2406 -307
- htmlgraph/dashboard.html.backup +6592 -0
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +19 -2
- htmlgraph/deploy.py +142 -125
- htmlgraph/deployment_models.py +474 -0
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +182 -27
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +100 -52
- htmlgraph/event_migration.py +13 -4
- htmlgraph/exceptions.py +49 -0
- htmlgraph/file_watcher.py +101 -28
- htmlgraph/find_api.py +75 -63
- htmlgraph/git_events.py +145 -63
- htmlgraph/graph.py +1122 -106
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/hooks/.htmlgraph/agents.json +72 -0
- htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
- htmlgraph/hooks/__init__.py +45 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +1314 -0
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/hooks-config.example.json +12 -0
- htmlgraph/hooks/installer.py +343 -0
- htmlgraph/hooks/orchestrator.py +674 -0
- htmlgraph/hooks/orchestrator_reflector.py +223 -0
- htmlgraph/hooks/post-checkout.sh +28 -0
- htmlgraph/hooks/post-commit.sh +24 -0
- htmlgraph/hooks/post-merge.sh +26 -0
- htmlgraph/hooks/post_tool_use_failure.py +273 -0
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +408 -0
- htmlgraph/hooks/pre-commit.sh +94 -0
- htmlgraph/hooks/pre-push.sh +28 -0
- htmlgraph/hooks/pretooluse.py +819 -0
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +255 -0
- htmlgraph/hooks/task_validator.py +177 -0
- htmlgraph/hooks/validator.py +628 -0
- htmlgraph/ids.py +41 -27
- htmlgraph/index.d.ts +286 -0
- htmlgraph/learning.py +767 -0
- htmlgraph/mcp_server.py +69 -23
- htmlgraph/models.py +1586 -87
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/orchestration/task_coordination.py +343 -0
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +669 -0
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +328 -0
- htmlgraph/orchestrator_validator.py +133 -0
- htmlgraph/parallel.py +646 -0
- htmlgraph/parser.py +160 -35
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/planning.py +147 -52
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +109 -72
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/routing.py +8 -19
- htmlgraph/scripts/deploy.py +1 -2
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +685 -180
- htmlgraph/services/__init__.py +10 -0
- htmlgraph/services/claiming.py +199 -0
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +1392 -175
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +201 -0
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/setup.py +34 -17
- htmlgraph/spike_index.py +143 -0
- htmlgraph/sync_docs.py +12 -15
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph/templates/GEMINI.md.template +87 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +146 -15
- htmlgraph/track_manager.py +69 -21
- htmlgraph/transcript.py +890 -0
- htmlgraph/transcript_analytics.py +699 -0
- htmlgraph/types.py +323 -0
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +8 -5
- htmlgraph/work_type_utils.py +3 -2
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
- htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -2688
- htmlgraph/sdk.py +0 -709
- htmlgraph-0.9.3.dist-info/RECORD +0 -61
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Services package for SessionManager functionality decomposition.
|
|
3
|
+
|
|
4
|
+
This package contains service classes that handle specific domains of functionality
|
|
5
|
+
previously embedded in the SessionManager god object.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .claiming import ClaimingService
|
|
9
|
+
|
|
10
|
+
__all__ = ["ClaimingService"]
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ClaimingService - Handles feature claiming and release logic.
|
|
3
|
+
|
|
4
|
+
Extracted from SessionManager to reduce complexity and improve maintainability.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from htmlgraph.graph import HtmlGraph
|
|
11
|
+
from htmlgraph.models import Node
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from htmlgraph.session_manager import SessionManager
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ClaimingService:
|
|
18
|
+
"""
|
|
19
|
+
Service for managing feature claims and releases.
|
|
20
|
+
|
|
21
|
+
This service handles:
|
|
22
|
+
- Claiming features for agents
|
|
23
|
+
- Releasing feature claims
|
|
24
|
+
- Auto-releasing all claims for an agent
|
|
25
|
+
- Releasing all claims for a session
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
features_graph: HtmlGraph,
|
|
31
|
+
bugs_graph: HtmlGraph,
|
|
32
|
+
session_manager: "SessionManager",
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Initialize ClaimingService.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
features_graph: Graph for features collection
|
|
39
|
+
bugs_graph: Graph for bugs collection
|
|
40
|
+
session_manager: Reference to SessionManager for session operations
|
|
41
|
+
"""
|
|
42
|
+
self.features_graph = features_graph
|
|
43
|
+
self.bugs_graph = bugs_graph
|
|
44
|
+
self.session_manager = session_manager
|
|
45
|
+
|
|
46
|
+
def _get_graph(self, collection: str) -> HtmlGraph:
|
|
47
|
+
"""Get graph for a collection."""
|
|
48
|
+
if collection == "bugs":
|
|
49
|
+
return self.bugs_graph
|
|
50
|
+
return self.features_graph
|
|
51
|
+
|
|
52
|
+
def _invalidate_features_cache(self) -> None:
|
|
53
|
+
"""Invalidate the active features cache in SessionManager."""
|
|
54
|
+
self.session_manager._features_cache_dirty = True
|
|
55
|
+
|
|
56
|
+
def claim_feature(
|
|
57
|
+
self,
|
|
58
|
+
feature_id: str,
|
|
59
|
+
collection: str = "features",
|
|
60
|
+
*,
|
|
61
|
+
agent: str,
|
|
62
|
+
) -> Node | None:
|
|
63
|
+
"""
|
|
64
|
+
Claim a feature for an agent.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
feature_id: Feature to claim
|
|
68
|
+
collection: Collection name
|
|
69
|
+
agent: Agent name claiming the feature
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Updated Node or None
|
|
73
|
+
"""
|
|
74
|
+
graph = self._get_graph(collection)
|
|
75
|
+
node = graph.get(feature_id)
|
|
76
|
+
if not node:
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
# Check if already claimed by someone else
|
|
80
|
+
if node.agent_assigned and node.agent_assigned != agent:
|
|
81
|
+
# Check if session that claimed it is still active
|
|
82
|
+
if node.claimed_by_session:
|
|
83
|
+
session = self.session_manager.get_session(node.claimed_by_session)
|
|
84
|
+
if session and session.status == "active":
|
|
85
|
+
raise ValueError(
|
|
86
|
+
f"Feature '{feature_id}' is already claimed by {node.agent_assigned} "
|
|
87
|
+
f"(session {node.claimed_by_session})"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
session = self.session_manager._ensure_session_for_agent(agent)
|
|
91
|
+
|
|
92
|
+
node.agent_assigned = agent
|
|
93
|
+
node.claimed_at = datetime.now()
|
|
94
|
+
node.claimed_by_session = session.id
|
|
95
|
+
node.updated = datetime.now()
|
|
96
|
+
graph.update(node)
|
|
97
|
+
|
|
98
|
+
self.session_manager._maybe_log_work_item_action(
|
|
99
|
+
agent=agent,
|
|
100
|
+
tool="FeatureClaim",
|
|
101
|
+
summary=f"Claimed: {collection}/{feature_id}",
|
|
102
|
+
feature_id=feature_id,
|
|
103
|
+
payload={"collection": collection, "action": "claim"},
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return node
|
|
107
|
+
|
|
108
|
+
def release_feature(
|
|
109
|
+
self,
|
|
110
|
+
feature_id: str,
|
|
111
|
+
collection: str = "features",
|
|
112
|
+
*,
|
|
113
|
+
agent: str,
|
|
114
|
+
) -> Node | None:
|
|
115
|
+
"""
|
|
116
|
+
Release a feature claim.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
feature_id: Feature to release
|
|
120
|
+
collection: Collection name
|
|
121
|
+
agent: Agent name releasing the feature
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Updated Node or None
|
|
125
|
+
"""
|
|
126
|
+
graph = self._get_graph(collection)
|
|
127
|
+
node = graph.get(feature_id)
|
|
128
|
+
if not node:
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
if node.agent_assigned and node.agent_assigned != agent:
|
|
132
|
+
raise ValueError(
|
|
133
|
+
f"Feature '{feature_id}' is claimed by {node.agent_assigned}, not {agent}"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
node.agent_assigned = None
|
|
137
|
+
node.claimed_at = None
|
|
138
|
+
node.claimed_by_session = None
|
|
139
|
+
node.updated = datetime.now()
|
|
140
|
+
graph.update(node)
|
|
141
|
+
|
|
142
|
+
# Invalidate active features cache
|
|
143
|
+
self._invalidate_features_cache()
|
|
144
|
+
|
|
145
|
+
self.session_manager._maybe_log_work_item_action(
|
|
146
|
+
agent=agent,
|
|
147
|
+
tool="FeatureRelease",
|
|
148
|
+
summary=f"Released: {collection}/{feature_id}",
|
|
149
|
+
feature_id=feature_id,
|
|
150
|
+
payload={"collection": collection, "action": "release"},
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return node
|
|
154
|
+
|
|
155
|
+
def auto_release_features(self, agent: str) -> list[str]:
|
|
156
|
+
"""
|
|
157
|
+
Release all features claimed by an agent.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
agent: Agent name
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
List of released feature IDs
|
|
164
|
+
"""
|
|
165
|
+
released = []
|
|
166
|
+
for collection in ["features", "bugs"]:
|
|
167
|
+
graph = self._get_graph(collection)
|
|
168
|
+
for node in graph:
|
|
169
|
+
if node.agent_assigned == agent:
|
|
170
|
+
node.agent_assigned = None
|
|
171
|
+
node.claimed_at = None
|
|
172
|
+
node.claimed_by_session = None
|
|
173
|
+
node.updated = datetime.now()
|
|
174
|
+
graph.update(node)
|
|
175
|
+
released.append(node.id)
|
|
176
|
+
return released
|
|
177
|
+
|
|
178
|
+
def release_session_features(self, session_id: str) -> list[str]:
|
|
179
|
+
"""
|
|
180
|
+
Release all features claimed by a specific session.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
session_id: Session ID
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
List of released feature IDs
|
|
187
|
+
"""
|
|
188
|
+
released = []
|
|
189
|
+
for collection in ["features", "bugs"]:
|
|
190
|
+
graph = self._get_graph(collection)
|
|
191
|
+
for node in graph:
|
|
192
|
+
if node.claimed_by_session == session_id:
|
|
193
|
+
node.agent_assigned = None
|
|
194
|
+
node.claimed_at = None
|
|
195
|
+
node.claimed_by_session = None
|
|
196
|
+
node.updated = datetime.now()
|
|
197
|
+
graph.update(node)
|
|
198
|
+
released.append(node.id)
|
|
199
|
+
return released
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SessionStart Hook Integration - Initialize session registry with repo awareness.
|
|
3
|
+
|
|
4
|
+
Integrates:
|
|
5
|
+
- SessionRegistry: File-based session tracking
|
|
6
|
+
- RepoHash: Git awareness and repository identification
|
|
7
|
+
- AtomicFileWriter: Crash-safe writes
|
|
8
|
+
|
|
9
|
+
Called by SessionStart hook to:
|
|
10
|
+
1. Register new session with repo info
|
|
11
|
+
2. Export session IDs to CLAUDE_ENV_FILE
|
|
12
|
+
3. Detect parent sessions from environment
|
|
13
|
+
4. Initialize heartbeat mechanism
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
import uuid
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def initialize_session_from_hook(env_file: str | None = None) -> str:
|
|
26
|
+
"""
|
|
27
|
+
Initialize session registry from SessionStart hook.
|
|
28
|
+
|
|
29
|
+
Called automatically by SessionStart hook to set up session tracking.
|
|
30
|
+
Registers session with repository information and exports environment variables.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
env_file: Path to CLAUDE_ENV_FILE (from hook environment).
|
|
34
|
+
Allows exporting session IDs to parent process.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
session_id: Generated session ID for logging/tracking
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
OSError: If registry initialization fails
|
|
41
|
+
RuntimeError: If session registration fails
|
|
42
|
+
"""
|
|
43
|
+
from htmlgraph.repo_hash import RepoHash
|
|
44
|
+
from htmlgraph.session_registry import SessionRegistry
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
# Initialize registry (creates .htmlgraph/sessions/registry structure)
|
|
48
|
+
registry = SessionRegistry()
|
|
49
|
+
logger.debug(f"Initialized SessionRegistry at {registry.registry_dir}")
|
|
50
|
+
|
|
51
|
+
# Get repo information
|
|
52
|
+
try:
|
|
53
|
+
repo_hash = RepoHash()
|
|
54
|
+
git_info = repo_hash.get_git_info()
|
|
55
|
+
repo_hash_value = repo_hash.compute_repo_hash()
|
|
56
|
+
|
|
57
|
+
repo_info = {
|
|
58
|
+
"path": str(repo_hash.repo_path),
|
|
59
|
+
"hash": repo_hash_value,
|
|
60
|
+
"branch": git_info.get("branch"),
|
|
61
|
+
"commit": git_info.get("commit"),
|
|
62
|
+
"remote": git_info.get("remote"),
|
|
63
|
+
"dirty": git_info.get("dirty", False),
|
|
64
|
+
"is_monorepo": repo_hash.is_monorepo(),
|
|
65
|
+
"monorepo_project": repo_hash.get_monorepo_project(),
|
|
66
|
+
}
|
|
67
|
+
logger.debug(f"Repo info: {repo_info}")
|
|
68
|
+
except OSError as e:
|
|
69
|
+
logger.warning(f"Failed to get repo info: {e}")
|
|
70
|
+
repo_info = {
|
|
71
|
+
"path": str(Path.cwd()),
|
|
72
|
+
"hash": "unknown",
|
|
73
|
+
"branch": None,
|
|
74
|
+
"commit": None,
|
|
75
|
+
"remote": None,
|
|
76
|
+
"dirty": False,
|
|
77
|
+
"is_monorepo": False,
|
|
78
|
+
"monorepo_project": None,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Get instance information
|
|
82
|
+
instance_info = {
|
|
83
|
+
"pid": os.getpid(),
|
|
84
|
+
"hostname": _get_hostname(),
|
|
85
|
+
"start_time": _get_utc_timestamp(),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Generate session ID
|
|
89
|
+
session_id = f"sess-{uuid.uuid4().hex[:8]}"
|
|
90
|
+
|
|
91
|
+
# Register session atomically
|
|
92
|
+
try:
|
|
93
|
+
registry_file = registry.register_session(
|
|
94
|
+
session_id=session_id,
|
|
95
|
+
repo_info=repo_info,
|
|
96
|
+
instance_info=instance_info,
|
|
97
|
+
)
|
|
98
|
+
logger.info(f"Registered session {session_id} at {registry_file}")
|
|
99
|
+
except OSError as e:
|
|
100
|
+
logger.error(f"Failed to register session: {e}")
|
|
101
|
+
raise RuntimeError(f"Session registration failed: {e}") from e
|
|
102
|
+
|
|
103
|
+
# Export to CLAUDE_ENV_FILE if provided
|
|
104
|
+
if env_file:
|
|
105
|
+
try:
|
|
106
|
+
_export_to_env_file(
|
|
107
|
+
env_file=env_file,
|
|
108
|
+
session_id=session_id,
|
|
109
|
+
instance_id=registry.get_instance_id(),
|
|
110
|
+
repo_hash=repo_hash_value
|
|
111
|
+
if "repo_hash_value" in locals()
|
|
112
|
+
else "unknown",
|
|
113
|
+
)
|
|
114
|
+
logger.debug(f"Exported session environment to {env_file}")
|
|
115
|
+
except OSError as e:
|
|
116
|
+
logger.warning(f"Failed to export environment: {e}")
|
|
117
|
+
# Don't fail - session is registered even if export fails
|
|
118
|
+
|
|
119
|
+
# Check for parent session
|
|
120
|
+
parent_session_id = _get_parent_session_id()
|
|
121
|
+
if parent_session_id:
|
|
122
|
+
logger.info(f"Parent session detected: {parent_session_id}")
|
|
123
|
+
# Store parent relationship in environment for subprocesses
|
|
124
|
+
os.environ["HTMLGRAPH_PARENT_SESSION_ID"] = parent_session_id
|
|
125
|
+
|
|
126
|
+
return session_id
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error(f"Failed to initialize session: {e}", exc_info=True)
|
|
130
|
+
raise
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def finalize_session(session_id: str, status: str = "ended") -> bool:
|
|
134
|
+
"""
|
|
135
|
+
Finalize session (called by SessionEnd hook).
|
|
136
|
+
|
|
137
|
+
Archives the session and updates last activity timestamp.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
session_id: Session ID to finalize
|
|
141
|
+
status: Final status (ended, failed, etc.)
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
True if finalization succeeded, False otherwise
|
|
145
|
+
"""
|
|
146
|
+
from htmlgraph.session_registry import SessionRegistry
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
registry = SessionRegistry()
|
|
150
|
+
instance_id = registry.get_instance_id()
|
|
151
|
+
|
|
152
|
+
# Archive the session
|
|
153
|
+
success = registry.archive_session(instance_id)
|
|
154
|
+
if success:
|
|
155
|
+
logger.info(f"Archived session {session_id} (status: {status})")
|
|
156
|
+
else:
|
|
157
|
+
logger.warning(f"Failed to archive session {session_id}")
|
|
158
|
+
|
|
159
|
+
return success
|
|
160
|
+
except Exception as e:
|
|
161
|
+
logger.error(f"Failed to finalize session {session_id}: {e}")
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def heartbeat(session_id: str | None = None) -> bool:
|
|
166
|
+
"""
|
|
167
|
+
Update session activity timestamp (liveness heartbeat).
|
|
168
|
+
|
|
169
|
+
Called periodically to indicate the session is still active.
|
|
170
|
+
Should be called on each tool use or periodically (e.g., every 5 minutes).
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
session_id: Optional session ID (uses current instance if None)
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
True if heartbeat succeeded, False otherwise
|
|
177
|
+
"""
|
|
178
|
+
from htmlgraph.session_registry import SessionRegistry
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
registry = SessionRegistry()
|
|
182
|
+
instance_id = registry.get_instance_id()
|
|
183
|
+
|
|
184
|
+
success = registry.update_activity(instance_id)
|
|
185
|
+
if success:
|
|
186
|
+
logger.debug(f"Updated activity for instance {instance_id}")
|
|
187
|
+
else:
|
|
188
|
+
logger.warning(f"Failed to update activity for instance {instance_id}")
|
|
189
|
+
|
|
190
|
+
return success
|
|
191
|
+
except Exception as e:
|
|
192
|
+
logger.error(f"Failed to update activity: {e}")
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def get_current_session() -> dict | None:
|
|
197
|
+
"""
|
|
198
|
+
Get current session for this instance.
|
|
199
|
+
|
|
200
|
+
Returns the registration data for the current instance's session.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Session dict with instance_id, session_id, repo, etc., or None if not found
|
|
204
|
+
"""
|
|
205
|
+
from htmlgraph.session_registry import SessionRegistry
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
registry = SessionRegistry()
|
|
209
|
+
instance_id = registry.get_instance_id()
|
|
210
|
+
session = registry.read_session(instance_id)
|
|
211
|
+
return session
|
|
212
|
+
except Exception as e:
|
|
213
|
+
logger.error(f"Failed to get current session: {e}")
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def get_parent_session_id() -> str | None:
|
|
218
|
+
"""
|
|
219
|
+
Get parent session ID if this is a spawned task.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Parent session ID from environment, or None if not a spawned task
|
|
223
|
+
"""
|
|
224
|
+
return _get_parent_session_id()
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# Private helpers
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _get_hostname() -> str:
|
|
231
|
+
"""Get hostname safely."""
|
|
232
|
+
try:
|
|
233
|
+
import socket
|
|
234
|
+
|
|
235
|
+
return socket.gethostname()
|
|
236
|
+
except Exception:
|
|
237
|
+
return "unknown"
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _get_utc_timestamp() -> str:
|
|
241
|
+
"""Get current UTC timestamp in ISO 8601 format."""
|
|
242
|
+
now = datetime.now(timezone.utc)
|
|
243
|
+
return now.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _export_to_env_file(
|
|
247
|
+
env_file: str,
|
|
248
|
+
session_id: str,
|
|
249
|
+
instance_id: str,
|
|
250
|
+
repo_hash: str,
|
|
251
|
+
) -> None:
|
|
252
|
+
"""
|
|
253
|
+
Export session environment variables to CLAUDE_ENV_FILE.
|
|
254
|
+
|
|
255
|
+
Appends to the file to preserve any existing variables.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
env_file: Path to environment file
|
|
259
|
+
session_id: Session ID to export
|
|
260
|
+
instance_id: Instance ID to export
|
|
261
|
+
repo_hash: Repository hash to export
|
|
262
|
+
|
|
263
|
+
Raises:
|
|
264
|
+
OSError: If file write fails
|
|
265
|
+
"""
|
|
266
|
+
env_path = Path(env_file)
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
# Append to environment file
|
|
270
|
+
with open(env_path, "a") as f:
|
|
271
|
+
f.write(f"export HTMLGRAPH_SESSION_ID={session_id}\n")
|
|
272
|
+
f.write(f"export HTMLGRAPH_INSTANCE_ID={instance_id}\n")
|
|
273
|
+
f.write(f"export HTMLGRAPH_REPO_HASH={repo_hash}\n")
|
|
274
|
+
|
|
275
|
+
logger.debug(f"Exported environment variables to {env_file}")
|
|
276
|
+
except OSError as e:
|
|
277
|
+
logger.error(f"Failed to export environment to {env_file}: {e}")
|
|
278
|
+
raise
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _get_parent_session_id() -> str | None:
|
|
282
|
+
"""
|
|
283
|
+
Detect parent session from environment.
|
|
284
|
+
|
|
285
|
+
Checks:
|
|
286
|
+
1. HTMLGRAPH_PARENT_SESSION_ID env var (set by Task spawning)
|
|
287
|
+
2. HTMLGRAPH_PARENT_SESSION env var (alternate name)
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
Parent session ID if found, None otherwise
|
|
291
|
+
"""
|
|
292
|
+
parent_id = os.environ.get("HTMLGRAPH_PARENT_SESSION_ID")
|
|
293
|
+
if parent_id:
|
|
294
|
+
return parent_id
|
|
295
|
+
|
|
296
|
+
parent_id = os.environ.get("HTMLGRAPH_PARENT_SESSION")
|
|
297
|
+
if parent_id:
|
|
298
|
+
return parent_id
|
|
299
|
+
|
|
300
|
+
return None
|