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
htmlgraph/git_events.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Git event logging for HtmlGraph.
|
|
3
5
|
|
|
@@ -10,17 +12,19 @@ Design goals:
|
|
|
10
12
|
- Analytics-friendly: schema compatible with EventRecord/AnalyticsIndex
|
|
11
13
|
"""
|
|
12
14
|
|
|
13
|
-
from __future__ import annotations
|
|
14
15
|
|
|
15
16
|
import os
|
|
16
17
|
import re
|
|
17
18
|
import subprocess
|
|
18
19
|
from datetime import datetime
|
|
19
20
|
from pathlib import Path
|
|
20
|
-
from typing import
|
|
21
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
21
22
|
|
|
22
23
|
from htmlgraph.event_log import EventRecord, JsonlEventLog
|
|
23
24
|
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from htmlgraph.session_manager import SessionManager
|
|
27
|
+
|
|
24
28
|
|
|
25
29
|
def get_git_info() -> dict:
|
|
26
30
|
"""
|
|
@@ -33,85 +37,81 @@ def get_git_info() -> dict:
|
|
|
33
37
|
try:
|
|
34
38
|
# Get commit hash
|
|
35
39
|
commit_hash = subprocess.check_output(
|
|
36
|
-
[
|
|
37
|
-
stderr=subprocess.DEVNULL,
|
|
38
|
-
text=True
|
|
40
|
+
["git", "rev-parse", "HEAD"], stderr=subprocess.DEVNULL, text=True
|
|
39
41
|
).strip()
|
|
40
42
|
|
|
41
43
|
commit_hash_short = subprocess.check_output(
|
|
42
|
-
[
|
|
44
|
+
["git", "rev-parse", "--short", "HEAD"],
|
|
43
45
|
stderr=subprocess.DEVNULL,
|
|
44
|
-
text=True
|
|
46
|
+
text=True,
|
|
45
47
|
).strip()
|
|
46
48
|
|
|
47
49
|
# Get branch name
|
|
48
50
|
branch = subprocess.check_output(
|
|
49
|
-
[
|
|
51
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
50
52
|
stderr=subprocess.DEVNULL,
|
|
51
|
-
text=True
|
|
53
|
+
text=True,
|
|
52
54
|
).strip()
|
|
53
55
|
|
|
54
56
|
# Get author info
|
|
55
57
|
author_name = subprocess.check_output(
|
|
56
|
-
[
|
|
57
|
-
stderr=subprocess.DEVNULL,
|
|
58
|
-
text=True
|
|
58
|
+
["git", "log", "-1", "--format=%an"], stderr=subprocess.DEVNULL, text=True
|
|
59
59
|
).strip()
|
|
60
60
|
|
|
61
61
|
author_email = subprocess.check_output(
|
|
62
|
-
[
|
|
63
|
-
stderr=subprocess.DEVNULL,
|
|
64
|
-
text=True
|
|
62
|
+
["git", "log", "-1", "--format=%ae"], stderr=subprocess.DEVNULL, text=True
|
|
65
63
|
).strip()
|
|
66
64
|
|
|
67
65
|
# Get commit message
|
|
68
66
|
commit_message = subprocess.check_output(
|
|
69
|
-
[
|
|
70
|
-
stderr=subprocess.DEVNULL,
|
|
71
|
-
text=True
|
|
67
|
+
["git", "log", "-1", "--format=%B"], stderr=subprocess.DEVNULL, text=True
|
|
72
68
|
).strip()
|
|
73
69
|
|
|
74
70
|
# Get changed files
|
|
75
|
-
files_changed =
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
71
|
+
files_changed = (
|
|
72
|
+
subprocess.check_output(
|
|
73
|
+
["git", "diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD"],
|
|
74
|
+
stderr=subprocess.DEVNULL,
|
|
75
|
+
text=True,
|
|
76
|
+
)
|
|
77
|
+
.strip()
|
|
78
|
+
.split("\n")
|
|
79
|
+
)
|
|
80
80
|
files_changed = [f for f in files_changed if f] # Remove empty strings
|
|
81
81
|
|
|
82
82
|
# Get stats (insertions/deletions)
|
|
83
83
|
stats = subprocess.check_output(
|
|
84
|
-
[
|
|
84
|
+
["git", "diff-tree", "--no-commit-id", "--numstat", "-r", "HEAD"],
|
|
85
85
|
stderr=subprocess.DEVNULL,
|
|
86
|
-
text=True
|
|
86
|
+
text=True,
|
|
87
87
|
).strip()
|
|
88
88
|
|
|
89
89
|
insertions = 0
|
|
90
90
|
deletions = 0
|
|
91
|
-
for line in stats.split(
|
|
91
|
+
for line in stats.split("\n"):
|
|
92
92
|
if line:
|
|
93
|
-
parts = line.split(
|
|
94
|
-
if len(parts) >= 2 and parts[0] !=
|
|
93
|
+
parts = line.split("\t")
|
|
94
|
+
if len(parts) >= 2 and parts[0] != "-" and parts[1] != "-":
|
|
95
95
|
insertions += int(parts[0])
|
|
96
96
|
deletions += int(parts[1])
|
|
97
97
|
|
|
98
98
|
return {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
99
|
+
"commit_hash": commit_hash,
|
|
100
|
+
"commit_hash_short": commit_hash_short,
|
|
101
|
+
"branch": branch,
|
|
102
|
+
"author_name": author_name,
|
|
103
|
+
"author_email": author_email,
|
|
104
|
+
"commit_message": commit_message,
|
|
105
|
+
"files_changed": files_changed,
|
|
106
|
+
"insertions": insertions,
|
|
107
|
+
"deletions": deletions,
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
except subprocess.CalledProcessError:
|
|
111
111
|
return {}
|
|
112
112
|
|
|
113
113
|
|
|
114
|
-
def _get_session_manager(graph_dir: str | Path) ->
|
|
114
|
+
def _get_session_manager(graph_dir: str | Path) -> SessionManager | None:
|
|
115
115
|
try:
|
|
116
116
|
from htmlgraph.session_manager import SessionManager
|
|
117
117
|
|
|
@@ -148,7 +148,7 @@ def get_primary_feature_id(graph_dir: str | Path = ".htmlgraph") -> str | None:
|
|
|
148
148
|
return None
|
|
149
149
|
|
|
150
150
|
|
|
151
|
-
def get_active_session(graph_dir: str | Path = ".htmlgraph"):
|
|
151
|
+
def get_active_session(graph_dir: str | Path = ".htmlgraph") -> Any:
|
|
152
152
|
"""
|
|
153
153
|
Get the current active HtmlGraph session.
|
|
154
154
|
|
|
@@ -169,9 +169,19 @@ def parse_feature_refs(message: str) -> list[str]:
|
|
|
169
169
|
Parse feature IDs from commit message.
|
|
170
170
|
|
|
171
171
|
Looks for patterns like:
|
|
172
|
-
- Implements:
|
|
172
|
+
- Implements: feat-xyz
|
|
173
173
|
- Fixes: bug-abc
|
|
174
|
-
-
|
|
174
|
+
- [feat-123abc]
|
|
175
|
+
- feat-xyz
|
|
176
|
+
|
|
177
|
+
Supports HtmlGraph ID formats:
|
|
178
|
+
- feat-XXXXXXXX (features)
|
|
179
|
+
- feature-XXXXXXXX (legacy features)
|
|
180
|
+
- bug-XXXXXXXX (bugs)
|
|
181
|
+
- spk-XXXXXXXX (spikes)
|
|
182
|
+
- spike-XXXXXXXX (legacy spikes)
|
|
183
|
+
- chr-XXXXXXXX (chores)
|
|
184
|
+
- trk-XXXXXXXX (tracks)
|
|
175
185
|
|
|
176
186
|
Args:
|
|
177
187
|
message: Commit message
|
|
@@ -181,14 +191,21 @@ def parse_feature_refs(message: str) -> list[str]:
|
|
|
181
191
|
"""
|
|
182
192
|
features = []
|
|
183
193
|
|
|
184
|
-
#
|
|
185
|
-
|
|
194
|
+
# All HtmlGraph ID prefixes (current + legacy)
|
|
195
|
+
id_prefixes = r"(?:feat|feature|bug|spk|spike|chr|chore|trk|track|todo)"
|
|
196
|
+
|
|
197
|
+
# Pattern 1: Explicit tags (Implements: feat-xyz)
|
|
198
|
+
pattern1 = rf"(?:Implements|Fixes|Closes|Refs):\s*({id_prefixes}-[\w-]+)"
|
|
186
199
|
features.extend(re.findall(pattern1, message, re.IGNORECASE))
|
|
187
200
|
|
|
188
|
-
# Pattern:
|
|
189
|
-
pattern2 =
|
|
201
|
+
# Pattern 2: Square brackets [feat-xyz] (common in commit messages)
|
|
202
|
+
pattern2 = rf"\[({id_prefixes}-[\w-]+)\]"
|
|
190
203
|
features.extend(re.findall(pattern2, message, re.IGNORECASE))
|
|
191
204
|
|
|
205
|
+
# Pattern 3: Anywhere in message as word boundary
|
|
206
|
+
pattern3 = rf"\b({id_prefixes}-[\w-]+)\b"
|
|
207
|
+
features.extend(re.findall(pattern3, message, re.IGNORECASE))
|
|
208
|
+
|
|
192
209
|
# Remove duplicates while preserving order
|
|
193
210
|
seen = set()
|
|
194
211
|
unique_features = []
|
|
@@ -200,7 +217,9 @@ def parse_feature_refs(message: str) -> list[str]:
|
|
|
200
217
|
return unique_features
|
|
201
218
|
|
|
202
219
|
|
|
203
|
-
def _parse_checkout_from_reflog(
|
|
220
|
+
def _parse_checkout_from_reflog(
|
|
221
|
+
reflog_action: str | None,
|
|
222
|
+
) -> tuple[str | None, str | None]:
|
|
204
223
|
if not reflog_action:
|
|
205
224
|
return None, None
|
|
206
225
|
# Example: "checkout: moving from main to feature/foo"
|
|
@@ -228,6 +247,7 @@ def _append_event(
|
|
|
228
247
|
) -> None:
|
|
229
248
|
# Auto-infer work type from feature_id (Phase 1: Work Type Classification)
|
|
230
249
|
from htmlgraph.work_type_utils import infer_work_type_from_id
|
|
250
|
+
|
|
231
251
|
work_type = infer_work_type_from_id(feature_id)
|
|
232
252
|
|
|
233
253
|
record = EventRecord(
|
|
@@ -257,7 +277,12 @@ def _append_event(
|
|
|
257
277
|
p = Path(override_path)
|
|
258
278
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
259
279
|
with p.open("a", encoding="utf-8") as f:
|
|
260
|
-
f.write(
|
|
280
|
+
f.write(
|
|
281
|
+
json.dumps(
|
|
282
|
+
record.model_dump(mode="json"), ensure_ascii=False, default=str
|
|
283
|
+
)
|
|
284
|
+
+ "\n"
|
|
285
|
+
)
|
|
261
286
|
return
|
|
262
287
|
|
|
263
288
|
log = JsonlEventLog(graph_dir / "events")
|
|
@@ -275,14 +300,53 @@ def _determine_context(graph_dir: Path, commit_message: str | None = None) -> di
|
|
|
275
300
|
"""
|
|
276
301
|
active_features = get_active_features(graph_dir)
|
|
277
302
|
primary_feature_id = get_primary_feature_id(graph_dir)
|
|
278
|
-
message_features =
|
|
303
|
+
message_features = (
|
|
304
|
+
parse_feature_refs(commit_message or "") if commit_message else []
|
|
305
|
+
)
|
|
279
306
|
|
|
280
307
|
all_features: list[str] = []
|
|
281
|
-
for f in
|
|
308
|
+
for f in active_features + message_features:
|
|
282
309
|
if f and f not in all_features:
|
|
283
310
|
all_features.append(f)
|
|
284
311
|
|
|
285
|
-
session
|
|
312
|
+
# Try to find the right session based on feature IDs in commit message
|
|
313
|
+
# This handles multi-agent scenarios where multiple sessions are active
|
|
314
|
+
session = None
|
|
315
|
+
if message_features:
|
|
316
|
+
# If commit mentions specific features, find the session working on them
|
|
317
|
+
manager = _get_session_manager(graph_dir)
|
|
318
|
+
if manager:
|
|
319
|
+
try:
|
|
320
|
+
# Try to find a session that has any of the message features as active
|
|
321
|
+
for feature_id in message_features:
|
|
322
|
+
# Get the feature to check which agent is working on it
|
|
323
|
+
try:
|
|
324
|
+
# Try features graph first
|
|
325
|
+
feature = manager.features_graph.get(feature_id)
|
|
326
|
+
if not feature:
|
|
327
|
+
# Try bugs graph
|
|
328
|
+
feature = manager.bugs_graph.get(feature_id)
|
|
329
|
+
|
|
330
|
+
if (
|
|
331
|
+
feature
|
|
332
|
+
and hasattr(feature, "agent_assigned")
|
|
333
|
+
and feature.agent_assigned
|
|
334
|
+
):
|
|
335
|
+
# Find active session for this agent
|
|
336
|
+
session = manager.get_active_session(
|
|
337
|
+
agent=feature.agent_assigned
|
|
338
|
+
)
|
|
339
|
+
if session:
|
|
340
|
+
break
|
|
341
|
+
except Exception:
|
|
342
|
+
pass
|
|
343
|
+
except Exception:
|
|
344
|
+
pass
|
|
345
|
+
|
|
346
|
+
# Fallback to any active session if we couldn't match by feature
|
|
347
|
+
if not session:
|
|
348
|
+
session = get_active_session(graph_dir)
|
|
349
|
+
|
|
286
350
|
if session:
|
|
287
351
|
return {
|
|
288
352
|
"session_id": session.id,
|
|
@@ -341,14 +405,22 @@ def log_git_commit(graph_dir: str | Path = ".htmlgraph") -> bool:
|
|
|
341
405
|
except Exception:
|
|
342
406
|
parents = []
|
|
343
407
|
|
|
344
|
-
ctx = _determine_context(
|
|
408
|
+
ctx = _determine_context(
|
|
409
|
+
graph_dir_path, commit_message=git_info.get("commit_message")
|
|
410
|
+
)
|
|
345
411
|
all_features: list[str] = ctx["all_features"]
|
|
346
412
|
|
|
347
413
|
# Create one event per feature (to keep continuity queries simple).
|
|
348
414
|
# If there are no features, write a single un-attributed event.
|
|
349
|
-
feature_ids
|
|
415
|
+
feature_ids: list[str | None] = (
|
|
416
|
+
cast(list[str | None], all_features) if all_features else [None]
|
|
417
|
+
)
|
|
350
418
|
|
|
351
|
-
subject = (
|
|
419
|
+
subject = (
|
|
420
|
+
(git_info.get("commit_message") or "").strip().splitlines()[0]
|
|
421
|
+
if git_info.get("commit_message")
|
|
422
|
+
else ""
|
|
423
|
+
)
|
|
352
424
|
base_event_id = f"git-commit-{git_info['commit_hash']}"
|
|
353
425
|
|
|
354
426
|
for fid in feature_ids:
|
|
@@ -394,8 +466,8 @@ def log_git_commit(graph_dir: str | Path = ".htmlgraph") -> bool:
|
|
|
394
466
|
except Exception as e:
|
|
395
467
|
# Never fail - just log error and continue
|
|
396
468
|
try:
|
|
397
|
-
error_log = Path(
|
|
398
|
-
with open(error_log,
|
|
469
|
+
error_log = Path(".htmlgraph/git-hook-errors.log")
|
|
470
|
+
with open(error_log, "a") as f:
|
|
399
471
|
f.write(f"{datetime.now().isoformat()} - Error logging commit: {e}\n")
|
|
400
472
|
except:
|
|
401
473
|
pass
|
|
@@ -429,7 +501,9 @@ def log_git_checkout(
|
|
|
429
501
|
|
|
430
502
|
# Always record, but callers can filter later based on flag.
|
|
431
503
|
ctx = _determine_context(graph_dir_path)
|
|
432
|
-
fid = ctx["primary_feature_id"] or (
|
|
504
|
+
fid = ctx["primary_feature_id"] or (
|
|
505
|
+
ctx["all_features"][0] if ctx["all_features"] else None
|
|
506
|
+
)
|
|
433
507
|
|
|
434
508
|
event_id = f"git-checkout-{now.strftime('%Y%m%d%H%M%S')}-{(new_head or 'unknown')[:12]}"
|
|
435
509
|
summary = "Checkout"
|
|
@@ -509,9 +583,13 @@ def log_git_merge(
|
|
|
509
583
|
reflog_action = os.environ.get("GIT_REFLOG_ACTION")
|
|
510
584
|
|
|
511
585
|
ctx = _determine_context(graph_dir_path)
|
|
512
|
-
fid = ctx["primary_feature_id"] or (
|
|
586
|
+
fid = ctx["primary_feature_id"] or (
|
|
587
|
+
ctx["all_features"][0] if ctx["all_features"] else None
|
|
588
|
+
)
|
|
513
589
|
|
|
514
|
-
event_id =
|
|
590
|
+
event_id = (
|
|
591
|
+
f"git-merge-{now.strftime('%Y%m%d%H%M%S')}-{(new_head or 'unknown')[:12]}"
|
|
592
|
+
)
|
|
515
593
|
payload = {
|
|
516
594
|
"type": "GitMerge",
|
|
517
595
|
"squash": bool(squash_int),
|
|
@@ -574,7 +652,9 @@ def log_git_push(
|
|
|
574
652
|
)
|
|
575
653
|
|
|
576
654
|
ctx = _determine_context(graph_dir_path)
|
|
577
|
-
fid = ctx["primary_feature_id"] or (
|
|
655
|
+
fid = ctx["primary_feature_id"] or (
|
|
656
|
+
ctx["all_features"][0] if ctx["all_features"] else None
|
|
657
|
+
)
|
|
578
658
|
|
|
579
659
|
event_id = f"git-push-{now.strftime('%Y%m%d%H%M%S')}-{os.getpid()}"
|
|
580
660
|
payload = {
|
|
@@ -606,17 +686,19 @@ def log_git_push(
|
|
|
606
686
|
return False
|
|
607
687
|
|
|
608
688
|
|
|
609
|
-
def main():
|
|
689
|
+
def main() -> None:
|
|
610
690
|
"""CLI entry point for git hook."""
|
|
611
691
|
import sys
|
|
612
692
|
|
|
613
693
|
if len(sys.argv) < 2:
|
|
614
|
-
print(
|
|
694
|
+
print(
|
|
695
|
+
"Usage: python -m htmlgraph.git_events <commit|checkout|merge|push> [args...]"
|
|
696
|
+
)
|
|
615
697
|
sys.exit(1)
|
|
616
698
|
|
|
617
699
|
event_type = sys.argv[1]
|
|
618
700
|
|
|
619
|
-
if event_type ==
|
|
701
|
+
if event_type == "commit":
|
|
620
702
|
sys.exit(0 if log_git_commit() else 1)
|
|
621
703
|
|
|
622
704
|
if event_type == "checkout":
|
|
@@ -639,5 +721,5 @@ def main():
|
|
|
639
721
|
sys.exit(1)
|
|
640
722
|
|
|
641
723
|
|
|
642
|
-
if __name__ ==
|
|
724
|
+
if __name__ == "__main__":
|
|
643
725
|
main()
|