htmlgraph 0.20.1__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 +51 -1
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +26 -10
- htmlgraph/agent_registry.py +2 -1
- htmlgraph/analytics/__init__.py +8 -1
- htmlgraph/analytics/cli.py +86 -20
- 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 +10 -6
- 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 +67 -27
- htmlgraph/analytics_index.py +53 -20
- 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 +2 -1
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/base.py +57 -2
- htmlgraph/builders/bug.py +19 -3
- htmlgraph/builders/chore.py +19 -3
- htmlgraph/builders/epic.py +19 -3
- htmlgraph/builders/feature.py +27 -3
- htmlgraph/builders/insight.py +2 -1
- htmlgraph/builders/metric.py +2 -1
- htmlgraph/builders/pattern.py +2 -1
- htmlgraph/builders/phase.py +19 -3
- htmlgraph/builders/spike.py +29 -3
- htmlgraph/builders/track.py +42 -1
- 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 +2 -0
- htmlgraph/collections/base.py +197 -14
- htmlgraph/collections/bug.py +2 -1
- htmlgraph/collections/chore.py +2 -1
- htmlgraph/collections/epic.py +2 -1
- htmlgraph/collections/feature.py +2 -1
- htmlgraph/collections/insight.py +2 -1
- htmlgraph/collections/metric.py +2 -1
- htmlgraph/collections/pattern.py +2 -1
- htmlgraph/collections/phase.py +2 -1
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +13 -2
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +14 -1
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/converter.py +116 -7
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2246 -248
- 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 +2 -1
- htmlgraph/deploy.py +26 -27
- 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 +2 -1
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +86 -37
- htmlgraph/event_migration.py +2 -1
- htmlgraph/file_watcher.py +12 -8
- htmlgraph/find_api.py +2 -1
- htmlgraph/git_events.py +67 -9
- 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 +8 -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 +790 -99
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/installer.py +5 -1
- htmlgraph/hooks/orchestrator.py +327 -76
- htmlgraph/hooks/orchestrator_reflector.py +31 -4
- htmlgraph/hooks/post_tool_use_failure.py +32 -7
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +92 -19
- htmlgraph/hooks/pretooluse.py +527 -7
- 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 +99 -4
- htmlgraph/hooks/validator.py +212 -91
- htmlgraph/ids.py +2 -1
- htmlgraph/learning.py +125 -100
- htmlgraph/mcp_server.py +2 -1
- htmlgraph/models.py +217 -18
- 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.py → orchestration/task_coordination.py} +16 -8
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +2 -1
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +115 -4
- htmlgraph/parallel.py +2 -1
- htmlgraph/parser.py +86 -6
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +2 -1
- 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/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 +295 -107
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +285 -3
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +2 -1
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +33 -1
- htmlgraph/track_manager.py +38 -0
- htmlgraph/transcript.py +18 -5
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +2 -1
- htmlgraph/work_type_utils.py +2 -1
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -4839
- htmlgraph/sdk.py +0 -2359
- htmlgraph-0.20.1.dist-info/RECORD +0 -118
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
htmlgraph/models.py
CHANGED
|
@@ -7,7 +7,7 @@ These models provide:
|
|
|
7
7
|
- Lightweight context generation for AI agents
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
from datetime import datetime
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
11
|
from enum import Enum
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
from typing import Any, Literal
|
|
@@ -15,6 +15,11 @@ from typing import Any, Literal
|
|
|
15
15
|
from pydantic import BaseModel, Field
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
def utc_now() -> datetime:
|
|
19
|
+
"""Return current time as UTC-aware datetime."""
|
|
20
|
+
return datetime.now(timezone.utc)
|
|
21
|
+
|
|
22
|
+
|
|
18
23
|
class WorkType(str, Enum):
|
|
19
24
|
"""
|
|
20
25
|
Classification of work/activity type for events and sessions.
|
|
@@ -264,15 +269,15 @@ class Node(BaseModel):
|
|
|
264
269
|
if edge.relationship not in self.edges:
|
|
265
270
|
self.edges[edge.relationship] = []
|
|
266
271
|
self.edges[edge.relationship].append(edge)
|
|
267
|
-
self.updated =
|
|
272
|
+
self.updated = utc_now()
|
|
268
273
|
|
|
269
274
|
def complete_step(self, index: int, agent: str | None = None) -> bool:
|
|
270
275
|
"""Mark a step as completed."""
|
|
271
276
|
if 0 <= index < len(self.steps):
|
|
272
277
|
self.steps[index].completed = True
|
|
273
278
|
self.steps[index].agent = agent
|
|
274
|
-
self.steps[index].timestamp =
|
|
275
|
-
self.updated =
|
|
279
|
+
self.steps[index].timestamp = utc_now()
|
|
280
|
+
self.updated = utc_now()
|
|
276
281
|
return True
|
|
277
282
|
return False
|
|
278
283
|
|
|
@@ -300,7 +305,7 @@ class Node(BaseModel):
|
|
|
300
305
|
self.context_tokens_used += tokens_used
|
|
301
306
|
self.context_peak_tokens = max(self.context_peak_tokens, peak_tokens)
|
|
302
307
|
self.context_cost_usd += cost_usd
|
|
303
|
-
self.updated =
|
|
308
|
+
self.updated = utc_now()
|
|
304
309
|
|
|
305
310
|
def context_stats(self) -> dict:
|
|
306
311
|
"""
|
|
@@ -317,6 +322,24 @@ class Node(BaseModel):
|
|
|
317
322
|
"session_ids": self.context_sessions,
|
|
318
323
|
}
|
|
319
324
|
|
|
325
|
+
def to_dict(self) -> dict:
|
|
326
|
+
"""
|
|
327
|
+
Convert Node to dictionary format.
|
|
328
|
+
|
|
329
|
+
This is a convenience alias for Pydantic's model_dump() method,
|
|
330
|
+
providing a more discoverable API for serialization.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
dict: Dictionary representation of the Node with all fields
|
|
334
|
+
|
|
335
|
+
Example:
|
|
336
|
+
>>> feature = sdk.features.create("My Feature").save()
|
|
337
|
+
>>> data = feature.to_dict()
|
|
338
|
+
>>> print(data['title'])
|
|
339
|
+
'My Feature'
|
|
340
|
+
"""
|
|
341
|
+
return self.model_dump()
|
|
342
|
+
|
|
320
343
|
def to_html(self, stylesheet_path: str = "../styles.css") -> str:
|
|
321
344
|
"""
|
|
322
345
|
Convert node to full HTML document.
|
|
@@ -813,9 +836,7 @@ class ContextSnapshot(BaseModel):
|
|
|
813
836
|
def from_dict(cls, data: dict) -> "ContextSnapshot":
|
|
814
837
|
"""Create from dictionary."""
|
|
815
838
|
return cls(
|
|
816
|
-
timestamp=datetime.fromisoformat(data["ts"])
|
|
817
|
-
if "ts" in data
|
|
818
|
-
else datetime.now(),
|
|
839
|
+
timestamp=datetime.fromisoformat(data["ts"]) if "ts" in data else utc_now(),
|
|
819
840
|
input_tokens=data.get("in", 0),
|
|
820
841
|
output_tokens=data.get("out", 0),
|
|
821
842
|
cache_creation_tokens=data.get("cache_create", 0),
|
|
@@ -829,6 +850,48 @@ class ContextSnapshot(BaseModel):
|
|
|
829
850
|
)
|
|
830
851
|
|
|
831
852
|
|
|
853
|
+
class ErrorEntry(BaseModel):
|
|
854
|
+
"""
|
|
855
|
+
An error record for session error tracking and debugging.
|
|
856
|
+
|
|
857
|
+
Stored inline within Session nodes for error analysis and debugging.
|
|
858
|
+
"""
|
|
859
|
+
|
|
860
|
+
timestamp: datetime = Field(default_factory=datetime.now)
|
|
861
|
+
error_type: str # Exception class name (ValueError, FileNotFoundError, etc.)
|
|
862
|
+
message: str # Error message
|
|
863
|
+
traceback: str | None = None # Full traceback for debugging
|
|
864
|
+
tool: str | None = None # Tool that caused the error (Edit, Bash, etc.)
|
|
865
|
+
context: str | None = None # Additional context information
|
|
866
|
+
session_id: str | None = None # Session ID for cross-referencing
|
|
867
|
+
locals_dump: str | None = None # JSON-serialized local variables at error point
|
|
868
|
+
stack_frames: list[dict[str, Any]] | None = (
|
|
869
|
+
None # Structured stack frame information
|
|
870
|
+
)
|
|
871
|
+
command_args: dict[str, Any] | None = None # Command arguments being executed
|
|
872
|
+
display_level: str = "minimal" # Display level: minimal, verbose, or debug
|
|
873
|
+
|
|
874
|
+
def to_html(self) -> str:
|
|
875
|
+
"""Convert error to HTML details element."""
|
|
876
|
+
attrs = [
|
|
877
|
+
f'data-ts="{self.timestamp.isoformat()}"',
|
|
878
|
+
f'data-error-type="{self.error_type}"',
|
|
879
|
+
]
|
|
880
|
+
if self.tool:
|
|
881
|
+
attrs.append(f'data-tool="{self.tool}"')
|
|
882
|
+
|
|
883
|
+
summary = f"<span class='error-type'>{self.error_type}</span>: {self.message}"
|
|
884
|
+
details = ""
|
|
885
|
+
if self.traceback:
|
|
886
|
+
details = f"<pre class='traceback'>{self.traceback}</pre>"
|
|
887
|
+
|
|
888
|
+
return f"<details class='error-item' {' '.join(attrs)}><summary>{summary}</summary>{details}</details>"
|
|
889
|
+
|
|
890
|
+
def to_context(self) -> str:
|
|
891
|
+
"""Lightweight context for AI agents."""
|
|
892
|
+
return f"[{self.timestamp.strftime('%H:%M:%S')}] ERROR {self.error_type}: {self.message}"
|
|
893
|
+
|
|
894
|
+
|
|
832
895
|
class ActivityEntry(BaseModel):
|
|
833
896
|
"""
|
|
834
897
|
A lightweight activity log entry for high-frequency events.
|
|
@@ -907,10 +970,18 @@ class Session(BaseModel):
|
|
|
907
970
|
worked_on: list[str] = Field(default_factory=list) # Feature IDs
|
|
908
971
|
continued_from: str | None = None # Previous session ID
|
|
909
972
|
|
|
910
|
-
#
|
|
973
|
+
# Parent session context (for nested Task() calls)
|
|
974
|
+
parent_session: str | None = None # Parent session ID
|
|
975
|
+
parent_activity: str | None = None # Parent activity ID
|
|
976
|
+
nesting_depth: int = 0 # Depth of nesting (0 = top-level)
|
|
977
|
+
|
|
978
|
+
# Handoff context (Phase 2 Feature 3: Cross-Session Continuity)
|
|
911
979
|
handoff_notes: str | None = None
|
|
912
980
|
recommended_next: str | None = None
|
|
913
981
|
blockers: list[str] = Field(default_factory=list)
|
|
982
|
+
recommended_context: list[str] = Field(
|
|
983
|
+
default_factory=list
|
|
984
|
+
) # File paths to keep context for
|
|
914
985
|
|
|
915
986
|
# High-frequency activity log
|
|
916
987
|
activity_log: list[ActivityEntry] = Field(default_factory=list)
|
|
@@ -937,20 +1008,67 @@ class Session(BaseModel):
|
|
|
937
1008
|
transcript_synced_at: datetime | None = None # Last sync timestamp
|
|
938
1009
|
transcript_git_branch: str | None = None # Git branch from transcript
|
|
939
1010
|
|
|
1011
|
+
# Pattern detection (inline storage to avoid file bloat)
|
|
1012
|
+
detected_patterns: list[dict[str, Any]] = Field(default_factory=list)
|
|
1013
|
+
"""
|
|
1014
|
+
Patterns detected during this session.
|
|
1015
|
+
|
|
1016
|
+
Format:
|
|
1017
|
+
{
|
|
1018
|
+
"sequence": ["Bash", "Read", "Edit"],
|
|
1019
|
+
"pattern_type": "neutral", # or "optimal", "anti_pattern"
|
|
1020
|
+
"detection_count": 3,
|
|
1021
|
+
"first_detected": "2026-01-02T10:00:00",
|
|
1022
|
+
"last_detected": "2026-01-02T10:30:00"
|
|
1023
|
+
}
|
|
1024
|
+
"""
|
|
1025
|
+
|
|
1026
|
+
# Error handling (Phase 1B)
|
|
1027
|
+
error_log: list[ErrorEntry] = Field(default_factory=list)
|
|
1028
|
+
"""Error records for this session with full tracebacks for debugging."""
|
|
1029
|
+
|
|
940
1030
|
def add_activity(self, entry: ActivityEntry) -> None:
|
|
941
1031
|
"""Add an activity entry to the log."""
|
|
942
1032
|
self.activity_log.append(entry)
|
|
943
1033
|
self.event_count += 1
|
|
944
|
-
self.last_activity =
|
|
1034
|
+
self.last_activity = utc_now()
|
|
945
1035
|
|
|
946
1036
|
# Track features worked on
|
|
947
1037
|
if entry.feature_id and entry.feature_id not in self.worked_on:
|
|
948
1038
|
self.worked_on.append(entry.feature_id)
|
|
949
1039
|
|
|
1040
|
+
def add_error(
|
|
1041
|
+
self,
|
|
1042
|
+
error_type: str,
|
|
1043
|
+
message: str,
|
|
1044
|
+
traceback: str | None = None,
|
|
1045
|
+
tool: str | None = None,
|
|
1046
|
+
context: str | None = None,
|
|
1047
|
+
) -> None:
|
|
1048
|
+
"""
|
|
1049
|
+
Add an error entry to the error log.
|
|
1050
|
+
|
|
1051
|
+
Args:
|
|
1052
|
+
error_type: Exception class name (ValueError, FileNotFoundError, etc.)
|
|
1053
|
+
message: Error message
|
|
1054
|
+
traceback: Full traceback for debugging
|
|
1055
|
+
tool: Tool that caused the error (Edit, Bash, etc.)
|
|
1056
|
+
context: Additional context information
|
|
1057
|
+
"""
|
|
1058
|
+
error = ErrorEntry(
|
|
1059
|
+
error_type=error_type,
|
|
1060
|
+
message=message,
|
|
1061
|
+
traceback=traceback,
|
|
1062
|
+
tool=tool,
|
|
1063
|
+
context=context,
|
|
1064
|
+
session_id=self.id,
|
|
1065
|
+
)
|
|
1066
|
+
self.error_log.append(error)
|
|
1067
|
+
|
|
950
1068
|
def end(self) -> None:
|
|
951
1069
|
"""Mark session as ended."""
|
|
952
1070
|
self.status = "ended"
|
|
953
|
-
self.ended_at =
|
|
1071
|
+
self.ended_at = utc_now()
|
|
954
1072
|
|
|
955
1073
|
def record_context(
|
|
956
1074
|
self, snapshot: ContextSnapshot, sample_interval: int = 10
|
|
@@ -1267,7 +1385,12 @@ class Session(BaseModel):
|
|
|
1267
1385
|
|
|
1268
1386
|
# Build handoff HTML
|
|
1269
1387
|
handoff_html = ""
|
|
1270
|
-
if
|
|
1388
|
+
if (
|
|
1389
|
+
self.handoff_notes
|
|
1390
|
+
or self.recommended_next
|
|
1391
|
+
or self.blockers
|
|
1392
|
+
or self.recommended_context
|
|
1393
|
+
):
|
|
1271
1394
|
handoff_section = """
|
|
1272
1395
|
<section data-handoff>
|
|
1273
1396
|
<h3>Handoff Context</h3>"""
|
|
@@ -1290,6 +1413,18 @@ class Session(BaseModel):
|
|
|
1290
1413
|
</ul>
|
|
1291
1414
|
</div>"""
|
|
1292
1415
|
|
|
1416
|
+
if self.recommended_context:
|
|
1417
|
+
context_items = "\n ".join(
|
|
1418
|
+
f"<li>{file_path}</li>" for file_path in self.recommended_context
|
|
1419
|
+
)
|
|
1420
|
+
handoff_section += f"""
|
|
1421
|
+
<div data-recommended-context>
|
|
1422
|
+
<strong>Recommended Context:</strong>
|
|
1423
|
+
<ul>
|
|
1424
|
+
{context_items}
|
|
1425
|
+
</ul>
|
|
1426
|
+
</div>"""
|
|
1427
|
+
|
|
1293
1428
|
handoff_section += "\n </section>"
|
|
1294
1429
|
handoff_html = handoff_section
|
|
1295
1430
|
|
|
@@ -1324,6 +1459,14 @@ class Session(BaseModel):
|
|
|
1324
1459
|
if self.primary_work_type
|
|
1325
1460
|
else ""
|
|
1326
1461
|
)
|
|
1462
|
+
# Parent session attributes
|
|
1463
|
+
parent_session_attrs = ""
|
|
1464
|
+
if self.parent_session:
|
|
1465
|
+
parent_session_attrs += f' data-parent-session="{self.parent_session}"'
|
|
1466
|
+
if self.parent_activity:
|
|
1467
|
+
parent_session_attrs += f' data-parent-activity="{self.parent_activity}"'
|
|
1468
|
+
if self.nesting_depth > 0:
|
|
1469
|
+
parent_session_attrs += f' data-nesting-depth="{self.nesting_depth}"'
|
|
1327
1470
|
|
|
1328
1471
|
# Serialize work_breakdown as JSON if present
|
|
1329
1472
|
import json
|
|
@@ -1378,6 +1521,62 @@ class Session(BaseModel):
|
|
|
1378
1521
|
</dl>
|
|
1379
1522
|
</section>"""
|
|
1380
1523
|
|
|
1524
|
+
# Build detected patterns section
|
|
1525
|
+
patterns_html = ""
|
|
1526
|
+
if self.detected_patterns:
|
|
1527
|
+
patterns_html = f"""
|
|
1528
|
+
<section data-detected-patterns>
|
|
1529
|
+
<h3>Detected Patterns ({len(self.detected_patterns)})</h3>
|
|
1530
|
+
<table class="patterns-table">
|
|
1531
|
+
<thead>
|
|
1532
|
+
<tr>
|
|
1533
|
+
<th>Sequence</th>
|
|
1534
|
+
<th>Type</th>
|
|
1535
|
+
<th>Count</th>
|
|
1536
|
+
<th>First/Last Detected</th>
|
|
1537
|
+
</tr>
|
|
1538
|
+
</thead>
|
|
1539
|
+
<tbody>"""
|
|
1540
|
+
|
|
1541
|
+
for pattern in self.detected_patterns:
|
|
1542
|
+
seq_str = " → ".join(pattern.get("sequence", []))
|
|
1543
|
+
pattern_type = pattern.get("pattern_type", "neutral")
|
|
1544
|
+
count = pattern.get("detection_count", 0)
|
|
1545
|
+
first = pattern.get("first_detected", "")
|
|
1546
|
+
last = pattern.get("last_detected", "")
|
|
1547
|
+
|
|
1548
|
+
patterns_html += f"""
|
|
1549
|
+
<tr data-pattern-type="{pattern_type}">
|
|
1550
|
+
<td class="sequence">{seq_str}</td>
|
|
1551
|
+
<td><span class="badge pattern-{pattern_type}">{pattern_type}</span></td>
|
|
1552
|
+
<td>{count}</td>
|
|
1553
|
+
<td>{first} / {last}</td>
|
|
1554
|
+
</tr>"""
|
|
1555
|
+
|
|
1556
|
+
patterns_html += """
|
|
1557
|
+
</tbody>
|
|
1558
|
+
</table>
|
|
1559
|
+
</section>"""
|
|
1560
|
+
|
|
1561
|
+
# Build error log section
|
|
1562
|
+
error_html = ""
|
|
1563
|
+
if self.error_log:
|
|
1564
|
+
error_items = "\n ".join(
|
|
1565
|
+
error.to_html() for error in self.error_log
|
|
1566
|
+
)
|
|
1567
|
+
error_html = f"""
|
|
1568
|
+
<section data-error-log>
|
|
1569
|
+
<h3>Errors ({len(self.error_log)})</h3>
|
|
1570
|
+
<div class="error-log">
|
|
1571
|
+
{error_items}
|
|
1572
|
+
</div>
|
|
1573
|
+
<style>
|
|
1574
|
+
.error-item {{ margin: 10px 0; padding: 10px; border-left: 3px solid #ff6b6b; }}
|
|
1575
|
+
.error-type {{ font-weight: bold; color: #ff6b6b; }}
|
|
1576
|
+
.traceback {{ background: #f5f5f5; padding: 10px; overflow-x: auto; font-size: 0.9em; margin-top: 5px; }}
|
|
1577
|
+
</style>
|
|
1578
|
+
</section>"""
|
|
1579
|
+
|
|
1381
1580
|
title = self.title or f"Session {self.id}"
|
|
1382
1581
|
|
|
1383
1582
|
return f'''<!DOCTYPE html>
|
|
@@ -1396,7 +1595,7 @@ class Session(BaseModel):
|
|
|
1396
1595
|
data-agent="{self.agent}"
|
|
1397
1596
|
data-started-at="{self.started_at.isoformat()}"
|
|
1398
1597
|
data-last-activity="{self.last_activity.isoformat()}"
|
|
1399
|
-
data-event-count="{self.event_count}"{subagent_attr}{commit_attr}{ended_attr}{primary_work_type_attr}{work_breakdown_attr}{context_attrs}{transcript_attrs}>
|
|
1598
|
+
data-event-count="{self.event_count}"{subagent_attr}{commit_attr}{ended_attr}{primary_work_type_attr}{work_breakdown_attr}{context_attrs}{transcript_attrs}{parent_session_attrs}>
|
|
1400
1599
|
|
|
1401
1600
|
<header>
|
|
1402
1601
|
<h1>{title}</h1>
|
|
@@ -1406,7 +1605,7 @@ class Session(BaseModel):
|
|
|
1406
1605
|
<span class="badge">{self.event_count} events</span>
|
|
1407
1606
|
</div>
|
|
1408
1607
|
</header>
|
|
1409
|
-
{edges_html}{handoff_html}{context_html}{activity_html}
|
|
1608
|
+
{edges_html}{handoff_html}{context_html}{error_html}{patterns_html}{activity_html}
|
|
1410
1609
|
</article>
|
|
1411
1610
|
</body>
|
|
1412
1611
|
</html>
|
|
@@ -2058,16 +2257,16 @@ class Todo(BaseModel):
|
|
|
2058
2257
|
def start(self) -> "Todo":
|
|
2059
2258
|
"""Mark todo as in progress."""
|
|
2060
2259
|
self.status = "in_progress"
|
|
2061
|
-
self.started_at =
|
|
2062
|
-
self.updated =
|
|
2260
|
+
self.started_at = utc_now()
|
|
2261
|
+
self.updated = utc_now()
|
|
2063
2262
|
return self
|
|
2064
2263
|
|
|
2065
2264
|
def complete(self, agent: str | None = None) -> "Todo":
|
|
2066
2265
|
"""Mark todo as completed."""
|
|
2067
2266
|
self.status = "completed"
|
|
2068
|
-
self.completed_at =
|
|
2267
|
+
self.completed_at = utc_now()
|
|
2069
2268
|
self.completed_by = agent
|
|
2070
|
-
self.updated =
|
|
2269
|
+
self.updated = utc_now()
|
|
2071
2270
|
|
|
2072
2271
|
# Calculate duration if started
|
|
2073
2272
|
if self.started_at:
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# HtmlGraph Operations Layer
|
|
2
|
+
|
|
3
|
+
This module defines a shared, backend operations layer for HtmlGraph. The CLI and SDK
|
|
4
|
+
should call these operations rather than duplicating logic. The operations layer is
|
|
5
|
+
pure Python, stateless, and returns structured data instead of printing.
|
|
6
|
+
|
|
7
|
+
## Design Principles
|
|
8
|
+
|
|
9
|
+
- Stateless: all inputs passed explicitly; no global CLI state.
|
|
10
|
+
- Typed: full type hints, dataclasses for results.
|
|
11
|
+
- Structured results: return data, warnings, and metadata; no printing.
|
|
12
|
+
- Exceptions for errors: no sys.exit.
|
|
13
|
+
- Path-first: accept Path objects for filesystem inputs.
|
|
14
|
+
- Reusable: callable from CLI, SDK, and tests.
|
|
15
|
+
|
|
16
|
+
## Module Structure
|
|
17
|
+
|
|
18
|
+
- `operations/server.py` Server startup and lifecycle helpers
|
|
19
|
+
- `operations/hooks.py` Git hook installation and configuration
|
|
20
|
+
- `operations/events.py` Event export, index rebuild, event queries
|
|
21
|
+
- `operations/analytics.py` Analytics summaries and report generation
|
|
22
|
+
|
|
23
|
+
## Example Signature
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ServerHandle:
|
|
32
|
+
url: str
|
|
33
|
+
port: int
|
|
34
|
+
host: str
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ServerStartResult:
|
|
38
|
+
handle: ServerHandle
|
|
39
|
+
warnings: list[str]
|
|
40
|
+
config_used: dict[str, Any]
|
|
41
|
+
|
|
42
|
+
class ServerStartError(RuntimeError):
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def start_server(
|
|
47
|
+
*,
|
|
48
|
+
port: int,
|
|
49
|
+
graph_dir: Path,
|
|
50
|
+
host: str = "localhost",
|
|
51
|
+
watch: bool = True,
|
|
52
|
+
auto_port: bool = False,
|
|
53
|
+
) -> ServerStartResult:
|
|
54
|
+
"""Start HtmlGraph server with validated config."""
|
|
55
|
+
raise NotImplementedError
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Conventions
|
|
59
|
+
|
|
60
|
+
- Functions should avoid any CLI-specific formatting.
|
|
61
|
+
- Results should be serializable for JSON output.
|
|
62
|
+
- Keep modules focused on a single domain (server, hooks, events, analytics).
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Shared operations layer for HtmlGraph CLI and SDK."""
|
|
2
|
+
|
|
3
|
+
from .analytics import (
|
|
4
|
+
AnalyticsProjectResult,
|
|
5
|
+
AnalyticsSessionResult,
|
|
6
|
+
analyze_project,
|
|
7
|
+
analyze_session,
|
|
8
|
+
)
|
|
9
|
+
from .events import (
|
|
10
|
+
EventExportResult,
|
|
11
|
+
EventQueryResult,
|
|
12
|
+
EventRebuildResult,
|
|
13
|
+
EventStats,
|
|
14
|
+
export_sessions,
|
|
15
|
+
get_event_stats,
|
|
16
|
+
query_events,
|
|
17
|
+
rebuild_index,
|
|
18
|
+
)
|
|
19
|
+
from .hooks import (
|
|
20
|
+
HookInstallResult,
|
|
21
|
+
HookListResult,
|
|
22
|
+
HookValidationResult,
|
|
23
|
+
install_hooks,
|
|
24
|
+
list_hooks,
|
|
25
|
+
validate_hook_config,
|
|
26
|
+
)
|
|
27
|
+
from .initialization import (
|
|
28
|
+
create_analytics_index,
|
|
29
|
+
create_config_files,
|
|
30
|
+
create_database,
|
|
31
|
+
create_directory_structure,
|
|
32
|
+
initialize_htmlgraph,
|
|
33
|
+
install_git_hooks,
|
|
34
|
+
update_gitignore,
|
|
35
|
+
validate_directory,
|
|
36
|
+
)
|
|
37
|
+
from .server import (
|
|
38
|
+
ServerHandle,
|
|
39
|
+
ServerStartResult,
|
|
40
|
+
ServerStatus,
|
|
41
|
+
get_server_status,
|
|
42
|
+
start_server,
|
|
43
|
+
stop_server,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"AnalyticsProjectResult",
|
|
48
|
+
"AnalyticsSessionResult",
|
|
49
|
+
"analyze_project",
|
|
50
|
+
"analyze_session",
|
|
51
|
+
"EventExportResult",
|
|
52
|
+
"EventQueryResult",
|
|
53
|
+
"EventRebuildResult",
|
|
54
|
+
"EventStats",
|
|
55
|
+
"export_sessions",
|
|
56
|
+
"get_event_stats",
|
|
57
|
+
"query_events",
|
|
58
|
+
"rebuild_index",
|
|
59
|
+
"HookInstallResult",
|
|
60
|
+
"HookListResult",
|
|
61
|
+
"HookValidationResult",
|
|
62
|
+
"install_hooks",
|
|
63
|
+
"list_hooks",
|
|
64
|
+
"validate_hook_config",
|
|
65
|
+
"ServerHandle",
|
|
66
|
+
"ServerStartResult",
|
|
67
|
+
"ServerStatus",
|
|
68
|
+
"start_server",
|
|
69
|
+
"stop_server",
|
|
70
|
+
"get_server_status",
|
|
71
|
+
"initialize_htmlgraph",
|
|
72
|
+
"validate_directory",
|
|
73
|
+
"create_directory_structure",
|
|
74
|
+
"create_database",
|
|
75
|
+
"create_analytics_index",
|
|
76
|
+
"create_config_files",
|
|
77
|
+
"update_gitignore",
|
|
78
|
+
"install_git_hooks",
|
|
79
|
+
]
|