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/builders/feature.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Feature builder for creating feature nodes.
|
|
3
5
|
|
|
@@ -5,8 +7,8 @@ Extends BaseBuilder with feature-specific methods like
|
|
|
5
7
|
capability management.
|
|
6
8
|
"""
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
10
12
|
|
|
11
13
|
if TYPE_CHECKING:
|
|
12
14
|
from htmlgraph.sdk import SDK
|
|
@@ -14,7 +16,7 @@ if TYPE_CHECKING:
|
|
|
14
16
|
from htmlgraph.builders.base import BaseBuilder
|
|
15
17
|
|
|
16
18
|
|
|
17
|
-
class FeatureBuilder(BaseBuilder[
|
|
19
|
+
class FeatureBuilder(BaseBuilder["FeatureBuilder"]):
|
|
18
20
|
"""
|
|
19
21
|
Fluent builder for creating features.
|
|
20
22
|
|
|
@@ -33,7 +35,30 @@ class FeatureBuilder(BaseBuilder['FeatureBuilder']):
|
|
|
33
35
|
|
|
34
36
|
node_type = "feature"
|
|
35
37
|
|
|
36
|
-
def
|
|
38
|
+
def __init__(self, sdk: SDK, title: str, **kwargs: Any):
|
|
39
|
+
"""Initialize feature builder with agent attribution and validation."""
|
|
40
|
+
# Validate title before creating builder
|
|
41
|
+
|
|
42
|
+
stripped_title = title.strip() if title else ""
|
|
43
|
+
if not stripped_title:
|
|
44
|
+
raise ValueError("Feature title cannot be empty or whitespace only")
|
|
45
|
+
if len(stripped_title) < 3:
|
|
46
|
+
raise ValueError("Feature title must be at least 3 characters")
|
|
47
|
+
|
|
48
|
+
super().__init__(sdk, title, **kwargs)
|
|
49
|
+
# Auto-assign agent from SDK for work tracking
|
|
50
|
+
if sdk._agent_id:
|
|
51
|
+
self._data["agent_assigned"] = sdk._agent_id
|
|
52
|
+
elif "agent_assigned" not in self._data:
|
|
53
|
+
# Log warning if agent not assigned (defensive check)
|
|
54
|
+
import logging
|
|
55
|
+
|
|
56
|
+
logging.warning(
|
|
57
|
+
f"Creating feature '{self._data.get('title', 'Unknown')}' without agent attribution. "
|
|
58
|
+
"Pass agent='name' to SDK() initialization."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def set_required_capabilities(self, capabilities: list[str]) -> FeatureBuilder:
|
|
37
62
|
"""
|
|
38
63
|
Set required capabilities for this feature.
|
|
39
64
|
|
|
@@ -52,7 +77,7 @@ class FeatureBuilder(BaseBuilder['FeatureBuilder']):
|
|
|
52
77
|
self._data["required_capabilities"] = capabilities
|
|
53
78
|
return self
|
|
54
79
|
|
|
55
|
-
def add_capability_tag(self, tag: str) ->
|
|
80
|
+
def add_capability_tag(self, tag: str) -> FeatureBuilder:
|
|
56
81
|
"""
|
|
57
82
|
Add a capability tag for flexible matching.
|
|
58
83
|
|
|
@@ -73,7 +98,7 @@ class FeatureBuilder(BaseBuilder['FeatureBuilder']):
|
|
|
73
98
|
self._data["capability_tags"].append(tag)
|
|
74
99
|
return self
|
|
75
100
|
|
|
76
|
-
def add_capability_tags(self, tags: list[str]) ->
|
|
101
|
+
def add_capability_tags(self, tags: list[str]) -> FeatureBuilder:
|
|
77
102
|
"""
|
|
78
103
|
Add multiple capability tags.
|
|
79
104
|
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Insight builder for creating session insight nodes.
|
|
5
|
+
|
|
6
|
+
Extends BaseBuilder with insight-specific methods for
|
|
7
|
+
session health scoring and pattern detection.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from htmlgraph.models import SessionInsight
|
|
15
|
+
from htmlgraph.sdk import SDK
|
|
16
|
+
|
|
17
|
+
from htmlgraph.builders.base import BaseBuilder
|
|
18
|
+
from htmlgraph.ids import generate_id
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class InsightBuilder(BaseBuilder["InsightBuilder"]):
|
|
22
|
+
"""
|
|
23
|
+
Fluent builder for creating session insights.
|
|
24
|
+
|
|
25
|
+
Insights analyze session health, detect patterns, and provide
|
|
26
|
+
recommendations for improvement.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
>>> sdk = SDK(agent="claude")
|
|
30
|
+
>>> insight = sdk.insights.create("Session ABC Health Analysis") \\
|
|
31
|
+
... .for_session("session-abc-123") \\
|
|
32
|
+
... .set_health_scores(efficiency=0.85, retry_rate=0.1) \\
|
|
33
|
+
... .add_issue("High retry rate on Bash commands") \\
|
|
34
|
+
... .add_recommendation("Use Read before Edit") \\
|
|
35
|
+
... .save()
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
node_type = "insight"
|
|
39
|
+
|
|
40
|
+
def __init__(self, sdk: SDK, title: str, **kwargs: Any):
|
|
41
|
+
"""Initialize insight builder with insight-specific defaults."""
|
|
42
|
+
super().__init__(sdk, title, **kwargs)
|
|
43
|
+
# Set insight-specific defaults
|
|
44
|
+
if "insight_type" not in self._data:
|
|
45
|
+
self._data["insight_type"] = "health"
|
|
46
|
+
|
|
47
|
+
def for_session(self, session_id: str) -> InsightBuilder:
|
|
48
|
+
"""
|
|
49
|
+
Set the source session ID.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
session_id: ID of the session being analyzed
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Self for method chaining
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
>>> insight.for_session("session-abc-123")
|
|
59
|
+
"""
|
|
60
|
+
self._data["session_id"] = session_id
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
def set_health_scores(
|
|
64
|
+
self,
|
|
65
|
+
efficiency: float = 0.0,
|
|
66
|
+
retry_rate: float = 0.0,
|
|
67
|
+
context_rebuilds: int = 0,
|
|
68
|
+
tool_diversity: float = 0.0,
|
|
69
|
+
error_recovery: float = 0.0,
|
|
70
|
+
) -> InsightBuilder:
|
|
71
|
+
"""
|
|
72
|
+
Set all health metric scores.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
efficiency: Efficiency score (0.0-1.0)
|
|
76
|
+
retry_rate: Retry rate (0.0-1.0, lower is better)
|
|
77
|
+
context_rebuilds: Number of context rebuilds
|
|
78
|
+
tool_diversity: Tool diversity score (0.0-1.0)
|
|
79
|
+
error_recovery: Error recovery rate (0.0-1.0)
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Self for method chaining
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
>>> insight.set_health_scores(
|
|
86
|
+
... efficiency=0.85,
|
|
87
|
+
... retry_rate=0.12,
|
|
88
|
+
... tool_diversity=0.70
|
|
89
|
+
... )
|
|
90
|
+
"""
|
|
91
|
+
self._data["efficiency_score"] = efficiency
|
|
92
|
+
self._data["retry_rate"] = retry_rate
|
|
93
|
+
self._data["context_rebuild_count"] = context_rebuilds
|
|
94
|
+
self._data["tool_diversity"] = tool_diversity
|
|
95
|
+
self._data["error_recovery_rate"] = error_recovery
|
|
96
|
+
# Calculate overall health score
|
|
97
|
+
self._data["overall_health_score"] = (
|
|
98
|
+
efficiency + (1 - retry_rate) + tool_diversity
|
|
99
|
+
) / 3
|
|
100
|
+
return self
|
|
101
|
+
|
|
102
|
+
def add_issue(self, issue: str) -> InsightBuilder:
|
|
103
|
+
"""
|
|
104
|
+
Add a detected issue.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
issue: Description of the issue
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Self for method chaining
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
>>> insight.add_issue("High retry rate on file operations")
|
|
114
|
+
"""
|
|
115
|
+
if "issues_detected" not in self._data:
|
|
116
|
+
self._data["issues_detected"] = []
|
|
117
|
+
self._data["issues_detected"].append(issue)
|
|
118
|
+
return self
|
|
119
|
+
|
|
120
|
+
def add_recommendation(self, rec: str) -> InsightBuilder:
|
|
121
|
+
"""
|
|
122
|
+
Add a recommendation for improvement.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
rec: Recommendation text
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Self for method chaining
|
|
129
|
+
|
|
130
|
+
Example:
|
|
131
|
+
>>> insight.add_recommendation("Use Read before attempting Edit")
|
|
132
|
+
"""
|
|
133
|
+
if "recommendations" not in self._data:
|
|
134
|
+
self._data["recommendations"] = []
|
|
135
|
+
self._data["recommendations"].append(rec)
|
|
136
|
+
return self
|
|
137
|
+
|
|
138
|
+
def add_pattern_match(
|
|
139
|
+
self, pattern_id: str, is_anti: bool = False
|
|
140
|
+
) -> InsightBuilder:
|
|
141
|
+
"""
|
|
142
|
+
Add a matched pattern ID.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
pattern_id: ID of the matched pattern
|
|
146
|
+
is_anti: Whether this is an anti-pattern match
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Self for method chaining
|
|
150
|
+
|
|
151
|
+
Example:
|
|
152
|
+
>>> insight.add_pattern_match("pattern-efficient-testing", is_anti=False)
|
|
153
|
+
>>> insight.add_pattern_match("pattern-excessive-retries", is_anti=True)
|
|
154
|
+
"""
|
|
155
|
+
key = "anti_patterns_matched" if is_anti else "patterns_matched"
|
|
156
|
+
if key not in self._data:
|
|
157
|
+
self._data[key] = []
|
|
158
|
+
self._data[key].append(pattern_id)
|
|
159
|
+
return self
|
|
160
|
+
|
|
161
|
+
def save(self) -> SessionInsight:
|
|
162
|
+
"""
|
|
163
|
+
Save the insight and return the SessionInsight instance.
|
|
164
|
+
|
|
165
|
+
Overrides BaseBuilder.save() to ensure insights are saved
|
|
166
|
+
to the insights directory.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Created SessionInsight instance
|
|
170
|
+
"""
|
|
171
|
+
# Generate collision-resistant ID if not provided
|
|
172
|
+
if "id" not in self._data:
|
|
173
|
+
self._data["id"] = generate_id(
|
|
174
|
+
node_type="insight",
|
|
175
|
+
title=self._data.get("title", ""),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Import SessionInsight here to avoid circular imports
|
|
179
|
+
from htmlgraph.models import SessionInsight
|
|
180
|
+
|
|
181
|
+
node = SessionInsight(**self._data)
|
|
182
|
+
|
|
183
|
+
# Save to the insights collection
|
|
184
|
+
if hasattr(self._sdk, "insights") and self._sdk.insights is not None:
|
|
185
|
+
graph = self._sdk.insights._ensure_graph()
|
|
186
|
+
graph.add(node)
|
|
187
|
+
else:
|
|
188
|
+
# Fallback: create new graph
|
|
189
|
+
from htmlgraph.graph import HtmlGraph
|
|
190
|
+
|
|
191
|
+
graph_path = self._sdk._directory / "insights"
|
|
192
|
+
graph = HtmlGraph(graph_path, auto_load=False)
|
|
193
|
+
graph.add(node)
|
|
194
|
+
|
|
195
|
+
return node
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Metric builder for creating aggregated metric nodes.
|
|
5
|
+
|
|
6
|
+
Extends BaseBuilder with metric-specific methods for
|
|
7
|
+
time-series aggregation and trend analysis.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from htmlgraph.models import AggregatedMetric
|
|
16
|
+
from htmlgraph.sdk import SDK
|
|
17
|
+
|
|
18
|
+
from htmlgraph.builders.base import BaseBuilder
|
|
19
|
+
from htmlgraph.ids import generate_id
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MetricBuilder(BaseBuilder["MetricBuilder"]):
|
|
23
|
+
"""
|
|
24
|
+
Fluent builder for creating aggregated metrics.
|
|
25
|
+
|
|
26
|
+
Metrics aggregate data over time periods (daily/weekly/monthly)
|
|
27
|
+
and scopes (session/feature/track/agent).
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
>>> sdk = SDK(agent="claude")
|
|
31
|
+
>>> metric = sdk.metrics.create("Weekly Efficiency") \\
|
|
32
|
+
... .set_scope("agent", "claude") \\
|
|
33
|
+
... .set_period("weekly", start=datetime(...), end=datetime(...)) \\
|
|
34
|
+
... .set_metrics({"avg_efficiency": 0.85, "median_retry_rate": 0.12}) \\
|
|
35
|
+
... .set_trend("improving", strength=0.15) \\
|
|
36
|
+
... .save()
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
node_type = "metric"
|
|
40
|
+
|
|
41
|
+
def __init__(self, sdk: SDK, title: str, **kwargs: Any):
|
|
42
|
+
"""Initialize metric builder with metric-specific defaults."""
|
|
43
|
+
super().__init__(sdk, title, **kwargs)
|
|
44
|
+
# Set metric-specific defaults
|
|
45
|
+
if "metric_type" not in self._data:
|
|
46
|
+
self._data["metric_type"] = "efficiency"
|
|
47
|
+
if "scope" not in self._data:
|
|
48
|
+
self._data["scope"] = "session"
|
|
49
|
+
if "period" not in self._data:
|
|
50
|
+
self._data["period"] = "weekly"
|
|
51
|
+
|
|
52
|
+
def set_scope(self, scope: str, scope_id: str | None = None) -> MetricBuilder:
|
|
53
|
+
"""
|
|
54
|
+
Set metric scope: session, feature, track, or agent.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
scope: Scope type (session/feature/track/agent)
|
|
58
|
+
scope_id: Specific ID within scope (optional)
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Self for method chaining
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
>>> metric.set_scope("agent", "claude")
|
|
65
|
+
>>> metric.set_scope("track", "track-planning-workflow")
|
|
66
|
+
"""
|
|
67
|
+
self._data["scope"] = scope
|
|
68
|
+
if scope_id:
|
|
69
|
+
self._data["scope_id"] = scope_id
|
|
70
|
+
return self
|
|
71
|
+
|
|
72
|
+
def set_period(
|
|
73
|
+
self,
|
|
74
|
+
period: str,
|
|
75
|
+
start: datetime | None = None,
|
|
76
|
+
end: datetime | None = None,
|
|
77
|
+
) -> MetricBuilder:
|
|
78
|
+
"""
|
|
79
|
+
Set time period for aggregation.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
period: Period type (daily/weekly/monthly)
|
|
83
|
+
start: Period start datetime (optional)
|
|
84
|
+
end: Period end datetime (optional)
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Self for method chaining
|
|
88
|
+
|
|
89
|
+
Example:
|
|
90
|
+
>>> metric.set_period("weekly", start=datetime(...), end=datetime(...))
|
|
91
|
+
"""
|
|
92
|
+
self._data["period"] = period
|
|
93
|
+
if start:
|
|
94
|
+
self._data["period_start"] = start.isoformat()
|
|
95
|
+
if end:
|
|
96
|
+
self._data["period_end"] = end.isoformat()
|
|
97
|
+
return self
|
|
98
|
+
|
|
99
|
+
def set_metrics(self, metrics: dict[str, float]) -> MetricBuilder:
|
|
100
|
+
"""
|
|
101
|
+
Set metric values dict.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
metrics: Dictionary of metric name -> value
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Self for method chaining
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
>>> metric.set_metrics({
|
|
111
|
+
... "avg_efficiency": 0.85,
|
|
112
|
+
... "median_retry_rate": 0.12,
|
|
113
|
+
... "tool_diversity": 0.68
|
|
114
|
+
... })
|
|
115
|
+
"""
|
|
116
|
+
self._data["metric_values"] = metrics
|
|
117
|
+
return self
|
|
118
|
+
|
|
119
|
+
def set_percentiles(self, percentiles: dict[str, float]) -> MetricBuilder:
|
|
120
|
+
"""
|
|
121
|
+
Set percentile values (p50, p90, p99, etc.).
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
percentiles: Dictionary of percentile name -> value
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Self for method chaining
|
|
128
|
+
|
|
129
|
+
Example:
|
|
130
|
+
>>> metric.set_percentiles({
|
|
131
|
+
... "p50": 0.82,
|
|
132
|
+
... "p90": 0.95,
|
|
133
|
+
... "p99": 0.98
|
|
134
|
+
... })
|
|
135
|
+
"""
|
|
136
|
+
self._data["percentiles"] = percentiles
|
|
137
|
+
return self
|
|
138
|
+
|
|
139
|
+
def set_trend(
|
|
140
|
+
self,
|
|
141
|
+
direction: str,
|
|
142
|
+
strength: float = 0.0,
|
|
143
|
+
vs_previous_pct: float = 0.0,
|
|
144
|
+
) -> MetricBuilder:
|
|
145
|
+
"""
|
|
146
|
+
Set trend information.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
direction: Trend direction (improving/declining/stable)
|
|
150
|
+
strength: Trend strength (0.0-1.0)
|
|
151
|
+
vs_previous_pct: Percentage change vs previous period
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Self for method chaining
|
|
155
|
+
|
|
156
|
+
Example:
|
|
157
|
+
>>> metric.set_trend("improving", strength=0.15, vs_previous_pct=8.5)
|
|
158
|
+
"""
|
|
159
|
+
self._data["trend_direction"] = direction
|
|
160
|
+
self._data["trend_strength"] = strength
|
|
161
|
+
self._data["vs_previous_period_pct"] = vs_previous_pct
|
|
162
|
+
return self
|
|
163
|
+
|
|
164
|
+
def add_session(self, session_id: str) -> MetricBuilder:
|
|
165
|
+
"""
|
|
166
|
+
Add a session to the aggregation.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
session_id: Session ID to include
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Self for method chaining
|
|
173
|
+
|
|
174
|
+
Example:
|
|
175
|
+
>>> metric.add_session("session-abc-123")
|
|
176
|
+
"""
|
|
177
|
+
if "sessions_in_period" not in self._data:
|
|
178
|
+
self._data["sessions_in_period"] = []
|
|
179
|
+
self._data["sessions_in_period"].append(session_id)
|
|
180
|
+
self._data["data_points_count"] = len(self._data["sessions_in_period"])
|
|
181
|
+
return self
|
|
182
|
+
|
|
183
|
+
def save(self) -> AggregatedMetric:
|
|
184
|
+
"""
|
|
185
|
+
Save the metric and return the AggregatedMetric instance.
|
|
186
|
+
|
|
187
|
+
Overrides BaseBuilder.save() to ensure metrics are saved
|
|
188
|
+
to the metrics directory.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Created AggregatedMetric node instance
|
|
192
|
+
"""
|
|
193
|
+
# Generate collision-resistant ID if not provided
|
|
194
|
+
if "id" not in self._data:
|
|
195
|
+
self._data["id"] = generate_id(
|
|
196
|
+
node_type="metric",
|
|
197
|
+
title=self._data.get("title", ""),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Import AggregatedMetric here to avoid circular imports
|
|
201
|
+
from htmlgraph.models import AggregatedMetric
|
|
202
|
+
|
|
203
|
+
node = AggregatedMetric(**self._data)
|
|
204
|
+
|
|
205
|
+
# Save to the metrics collection
|
|
206
|
+
if hasattr(self._sdk, "metrics") and self._sdk.metrics is not None:
|
|
207
|
+
graph = self._sdk.metrics._ensure_graph()
|
|
208
|
+
graph.add(node)
|
|
209
|
+
else:
|
|
210
|
+
# Fallback: create new graph
|
|
211
|
+
from htmlgraph.graph import HtmlGraph
|
|
212
|
+
|
|
213
|
+
graph_path = self._sdk._directory / "metrics"
|
|
214
|
+
graph = HtmlGraph(graph_path, auto_load=False)
|
|
215
|
+
graph.add(node)
|
|
216
|
+
|
|
217
|
+
return node
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Pattern builder for creating workflow pattern nodes.
|
|
5
|
+
|
|
6
|
+
Extends BaseBuilder with pattern-specific methods for
|
|
7
|
+
tracking optimal and anti-pattern tool sequences.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from htmlgraph.models import Pattern
|
|
16
|
+
from htmlgraph.sdk import SDK
|
|
17
|
+
|
|
18
|
+
from htmlgraph.builders.base import BaseBuilder
|
|
19
|
+
from htmlgraph.ids import generate_id
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PatternBuilder(BaseBuilder["PatternBuilder"]):
|
|
23
|
+
"""
|
|
24
|
+
Fluent builder for creating workflow patterns.
|
|
25
|
+
|
|
26
|
+
Patterns represent observed tool usage sequences that are
|
|
27
|
+
either optimal (should encourage) or anti-patterns (should warn against).
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
>>> sdk = SDK(agent="claude")
|
|
31
|
+
>>> pattern = sdk.patterns.create("Efficient Testing Pattern") \\
|
|
32
|
+
... .set_pattern_type("optimal") \\
|
|
33
|
+
... .set_sequence(["Edit", "Bash", "Edit"]) \\
|
|
34
|
+
... .set_success_rate(0.92) \\
|
|
35
|
+
... .set_recommendation("Write tests, run them, fix failures") \\
|
|
36
|
+
... .save()
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
node_type = "pattern"
|
|
40
|
+
|
|
41
|
+
def __init__(self, sdk: SDK, title: str, **kwargs: Any):
|
|
42
|
+
"""Initialize pattern builder with pattern-specific defaults."""
|
|
43
|
+
super().__init__(sdk, title, **kwargs)
|
|
44
|
+
# Set pattern-specific defaults
|
|
45
|
+
if "pattern_type" not in self._data:
|
|
46
|
+
self._data["pattern_type"] = "neutral"
|
|
47
|
+
if "sequence" not in self._data:
|
|
48
|
+
self._data["sequence"] = []
|
|
49
|
+
if "detection_count" not in self._data:
|
|
50
|
+
self._data["detection_count"] = 0
|
|
51
|
+
|
|
52
|
+
def set_pattern_type(self, ptype: str) -> PatternBuilder:
|
|
53
|
+
"""
|
|
54
|
+
Set pattern type: optimal, anti-pattern, or neutral.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
ptype: Pattern classification (optimal/anti-pattern/neutral)
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Self for method chaining
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
>>> pattern.set_pattern_type("optimal")
|
|
64
|
+
"""
|
|
65
|
+
self._data["pattern_type"] = ptype
|
|
66
|
+
return self
|
|
67
|
+
|
|
68
|
+
def set_sequence(self, sequence: list[str]) -> PatternBuilder:
|
|
69
|
+
"""
|
|
70
|
+
Set the tool sequence for this pattern.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
sequence: List of tool names in order (e.g., ["Edit", "Bash", "Read"])
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Self for method chaining
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
>>> pattern.set_sequence(["Edit", "Bash", "Bash", "Edit"])
|
|
80
|
+
"""
|
|
81
|
+
self._data["sequence"] = sequence
|
|
82
|
+
return self
|
|
83
|
+
|
|
84
|
+
def set_success_rate(self, rate: float) -> PatternBuilder:
|
|
85
|
+
"""
|
|
86
|
+
Set success rate (0.0-1.0).
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
rate: Success rate as decimal (0.0 = 0%, 1.0 = 100%)
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Self for method chaining
|
|
93
|
+
|
|
94
|
+
Example:
|
|
95
|
+
>>> pattern.set_success_rate(0.85)
|
|
96
|
+
"""
|
|
97
|
+
self._data["success_rate"] = rate
|
|
98
|
+
return self
|
|
99
|
+
|
|
100
|
+
def set_recommendation(self, rec: str) -> PatternBuilder:
|
|
101
|
+
"""
|
|
102
|
+
Set recommendation text for when this pattern is detected.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
rec: Recommendation message
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Self for method chaining
|
|
109
|
+
|
|
110
|
+
Example:
|
|
111
|
+
>>> pattern.set_recommendation("Consider running tests after changes")
|
|
112
|
+
"""
|
|
113
|
+
self._data["recommendation"] = rec
|
|
114
|
+
return self
|
|
115
|
+
|
|
116
|
+
def increment_detection(self) -> PatternBuilder:
|
|
117
|
+
"""
|
|
118
|
+
Increment detection count (number of times pattern was observed).
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Self for method chaining
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
>>> pattern.increment_detection()
|
|
125
|
+
"""
|
|
126
|
+
self._data["detection_count"] = self._data.get("detection_count", 0) + 1
|
|
127
|
+
return self
|
|
128
|
+
|
|
129
|
+
def set_detection_count(self, count: int) -> PatternBuilder:
|
|
130
|
+
"""
|
|
131
|
+
Set detection count directly.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
count: Number of times pattern was detected
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Self for method chaining
|
|
138
|
+
"""
|
|
139
|
+
self._data["detection_count"] = count
|
|
140
|
+
return self
|
|
141
|
+
|
|
142
|
+
def set_first_detected(self, dt: datetime) -> PatternBuilder:
|
|
143
|
+
"""
|
|
144
|
+
Set first detection timestamp.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
dt: Timestamp of first detection
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Self for method chaining
|
|
151
|
+
"""
|
|
152
|
+
self._data["first_detected"] = dt
|
|
153
|
+
return self
|
|
154
|
+
|
|
155
|
+
def set_last_detected(self, dt: datetime) -> PatternBuilder:
|
|
156
|
+
"""
|
|
157
|
+
Set last detection timestamp.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
dt: Timestamp of last detection
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Self for method chaining
|
|
164
|
+
"""
|
|
165
|
+
self._data["last_detected"] = dt
|
|
166
|
+
return self
|
|
167
|
+
|
|
168
|
+
def save(self) -> Pattern:
|
|
169
|
+
"""
|
|
170
|
+
Save the pattern and return the Node instance.
|
|
171
|
+
|
|
172
|
+
Overrides BaseBuilder.save() to ensure patterns are saved
|
|
173
|
+
to the patterns directory.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Created Pattern node instance
|
|
177
|
+
"""
|
|
178
|
+
# Generate collision-resistant ID if not provided
|
|
179
|
+
if "id" not in self._data:
|
|
180
|
+
self._data["id"] = generate_id(
|
|
181
|
+
node_type="pattern",
|
|
182
|
+
title=self._data.get("title", ""),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Import Pattern here to avoid circular imports
|
|
186
|
+
from htmlgraph.models import Pattern
|
|
187
|
+
|
|
188
|
+
node = Pattern(**self._data)
|
|
189
|
+
|
|
190
|
+
# Save to the patterns collection
|
|
191
|
+
if hasattr(self._sdk, "patterns") and self._sdk.patterns is not None:
|
|
192
|
+
graph = self._sdk.patterns._ensure_graph()
|
|
193
|
+
graph.add(node)
|
|
194
|
+
else:
|
|
195
|
+
# Fallback: create new graph
|
|
196
|
+
from htmlgraph.graph import HtmlGraph
|
|
197
|
+
|
|
198
|
+
graph_path = self._sdk._directory / "patterns"
|
|
199
|
+
graph = HtmlGraph(graph_path, auto_load=False)
|
|
200
|
+
graph.add(node)
|
|
201
|
+
|
|
202
|
+
return node
|