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/base.py
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Base builder class for fluent node creation.
|
|
3
5
|
|
|
4
6
|
Provides common builder patterns shared across all node types.
|
|
5
7
|
"""
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
from typing import TYPE_CHECKING, Any, TypeVar, Generic
|
|
9
|
+
|
|
9
10
|
from datetime import datetime
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
10
12
|
|
|
11
13
|
if TYPE_CHECKING:
|
|
12
|
-
from htmlgraph.sdk import SDK
|
|
13
14
|
from htmlgraph.models import Node
|
|
15
|
+
from htmlgraph.sdk import SDK
|
|
14
16
|
|
|
15
|
-
from htmlgraph.models import Step, Edge
|
|
16
17
|
from htmlgraph.ids import generate_id
|
|
18
|
+
from htmlgraph.models import Edge, Step
|
|
17
19
|
|
|
18
20
|
# Generic type for the builder subclass
|
|
19
|
-
BuilderT = TypeVar(
|
|
21
|
+
BuilderT = TypeVar("BuilderT", bound="BaseBuilder")
|
|
22
|
+
|
|
23
|
+
# For type hints in helper methods
|
|
24
|
+
from typing_extensions import Self
|
|
20
25
|
|
|
21
26
|
|
|
22
27
|
class BaseBuilder(Generic[BuilderT]):
|
|
@@ -38,7 +43,7 @@ class BaseBuilder(Generic[BuilderT]):
|
|
|
38
43
|
|
|
39
44
|
node_type: str = "node" # Override in subclasses
|
|
40
45
|
|
|
41
|
-
def __init__(self, sdk:
|
|
46
|
+
def __init__(self, sdk: SDK, title: str, **kwargs: Any):
|
|
42
47
|
"""
|
|
43
48
|
Initialize builder.
|
|
44
49
|
|
|
@@ -56,9 +61,36 @@ class BaseBuilder(Generic[BuilderT]):
|
|
|
56
61
|
"steps": [],
|
|
57
62
|
"edges": {},
|
|
58
63
|
"properties": {},
|
|
59
|
-
**kwargs
|
|
64
|
+
**kwargs,
|
|
60
65
|
}
|
|
61
66
|
|
|
67
|
+
# Helper methods for common patterns
|
|
68
|
+
def _add_edge(
|
|
69
|
+
self, edge_type: str, target_id: str, relationship: str | None = None
|
|
70
|
+
) -> Self:
|
|
71
|
+
"""Add an edge to the node being built."""
|
|
72
|
+
if edge_type not in self._data["edges"]:
|
|
73
|
+
self._data["edges"][edge_type] = []
|
|
74
|
+
self._data["edges"][edge_type].append(
|
|
75
|
+
Edge(target_id=target_id, relationship=relationship or edge_type)
|
|
76
|
+
)
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
def _set_date(self, field_name: str, date_value: Any) -> Self:
|
|
80
|
+
"""Set a date field in properties, converting to ISO format if needed."""
|
|
81
|
+
iso_date = (
|
|
82
|
+
date_value.isoformat() if hasattr(date_value, "isoformat") else date_value
|
|
83
|
+
)
|
|
84
|
+
self._data["properties"][field_name] = iso_date
|
|
85
|
+
return self
|
|
86
|
+
|
|
87
|
+
def _append_to_list(self, field_name: str, value: Any) -> Self:
|
|
88
|
+
"""Append a value to a list field in properties, creating list if needed."""
|
|
89
|
+
if field_name not in self._data["properties"]:
|
|
90
|
+
self._data["properties"][field_name] = []
|
|
91
|
+
self._data["properties"][field_name].append(value)
|
|
92
|
+
return self
|
|
93
|
+
|
|
62
94
|
def set_priority(self, priority: str) -> BuilderT:
|
|
63
95
|
"""Set node priority (low, medium, high, critical)."""
|
|
64
96
|
self._data["priority"] = priority
|
|
@@ -87,21 +119,11 @@ class BaseBuilder(Generic[BuilderT]):
|
|
|
87
119
|
|
|
88
120
|
def blocks(self, node_id: str) -> BuilderT:
|
|
89
121
|
"""Add blocking relationship (this node blocks another)."""
|
|
90
|
-
|
|
91
|
-
self._data["edges"]["blocks"] = []
|
|
92
|
-
self._data["edges"]["blocks"].append(
|
|
93
|
-
Edge(target_id=node_id, relationship="blocks")
|
|
94
|
-
)
|
|
95
|
-
return self # type: ignore
|
|
122
|
+
return self._add_edge("blocks", node_id) # type: ignore
|
|
96
123
|
|
|
97
124
|
def blocked_by(self, node_id: str) -> BuilderT:
|
|
98
125
|
"""Add blocked-by relationship (this node is blocked by another)."""
|
|
99
|
-
|
|
100
|
-
self._data["edges"]["blocked_by"] = []
|
|
101
|
-
self._data["edges"]["blocked_by"].append(
|
|
102
|
-
Edge(target_id=node_id, relationship="blocked_by")
|
|
103
|
-
)
|
|
104
|
-
return self # type: ignore
|
|
126
|
+
return self._add_edge("blocked_by", node_id) # type: ignore
|
|
105
127
|
|
|
106
128
|
def set_track(self, track_id: str) -> BuilderT:
|
|
107
129
|
"""Link to a track."""
|
|
@@ -131,7 +153,7 @@ class BaseBuilder(Generic[BuilderT]):
|
|
|
131
153
|
self._data["handoff_timestamp"] = datetime.now()
|
|
132
154
|
return self # type: ignore
|
|
133
155
|
|
|
134
|
-
def save(self) ->
|
|
156
|
+
def save(self) -> Node:
|
|
135
157
|
"""
|
|
136
158
|
Save the node and return the Node instance.
|
|
137
159
|
|
|
@@ -140,6 +162,9 @@ class BaseBuilder(Generic[BuilderT]):
|
|
|
140
162
|
|
|
141
163
|
Returns:
|
|
142
164
|
Created Node instance
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
ValueError: If node type requires track_id but none is set
|
|
143
168
|
"""
|
|
144
169
|
# Generate collision-resistant ID if not provided
|
|
145
170
|
if "id" not in self._data:
|
|
@@ -148,28 +173,92 @@ class BaseBuilder(Generic[BuilderT]):
|
|
|
148
173
|
title=self._data.get("title", ""),
|
|
149
174
|
)
|
|
150
175
|
|
|
176
|
+
# Validate track_id requirement for features
|
|
177
|
+
node_type = self._data.get("type", self.node_type)
|
|
178
|
+
if node_type == "feature" and not self._data.get("track_id"):
|
|
179
|
+
# Get available tracks for helpful error message
|
|
180
|
+
try:
|
|
181
|
+
tracks = self._sdk.tracks.all()
|
|
182
|
+
track_options = "\n".join(
|
|
183
|
+
[f" - {track.id}: {track.title}" for track in tracks[:10]]
|
|
184
|
+
)
|
|
185
|
+
if len(tracks) > 10:
|
|
186
|
+
track_options += f"\n ... and {len(tracks) - 10} more tracks"
|
|
187
|
+
|
|
188
|
+
error_msg = (
|
|
189
|
+
f"Feature '{self._data.get('title', 'Unknown')}' requires a track linkage.\n\n"
|
|
190
|
+
f"Use: .set_track('track_id') to link to a track before saving.\n\n"
|
|
191
|
+
f"Available tracks:\n{track_options or ' (no tracks found)'}\n\n"
|
|
192
|
+
f"Create a track first: sdk.tracks.create('Track Title')"
|
|
193
|
+
)
|
|
194
|
+
except Exception:
|
|
195
|
+
# Fallback error message if we can't fetch tracks
|
|
196
|
+
error_msg = (
|
|
197
|
+
f"Feature '{self._data.get('title', 'Unknown')}' requires a track linkage.\n"
|
|
198
|
+
f"Use: .set_track('track_id') to link to a track before saving."
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
raise ValueError(error_msg)
|
|
202
|
+
|
|
151
203
|
# Import Node here to avoid circular imports
|
|
152
204
|
from htmlgraph.models import Node
|
|
153
|
-
from htmlgraph.graph import HtmlGraph
|
|
154
205
|
|
|
155
206
|
node = Node(**self._data)
|
|
156
207
|
|
|
157
|
-
# Save to the
|
|
158
|
-
#
|
|
208
|
+
# Save to the collection's shared graph (not a new instance)
|
|
209
|
+
# This ensures the node is visible via collection.get() immediately
|
|
159
210
|
collection_name = self._data.get("type", self.node_type) + "s"
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
211
|
+
collection = getattr(self._sdk, collection_name, None)
|
|
212
|
+
|
|
213
|
+
if collection is not None:
|
|
214
|
+
# Use the collection's shared graph
|
|
215
|
+
graph = collection._ensure_graph()
|
|
216
|
+
graph.add(node)
|
|
217
|
+
else:
|
|
218
|
+
# Fallback: create new graph (for collections not yet on SDK)
|
|
219
|
+
from htmlgraph.graph import HtmlGraph
|
|
220
|
+
|
|
221
|
+
graph_path = self._sdk._directory / collection_name
|
|
222
|
+
graph = HtmlGraph(graph_path, auto_load=False)
|
|
223
|
+
graph.add(node)
|
|
224
|
+
|
|
225
|
+
# Log creation event to SQLite for dashboard observability
|
|
226
|
+
try:
|
|
227
|
+
action_type = self._data.get("type", self.node_type)
|
|
228
|
+
self._sdk._log_event(
|
|
229
|
+
event_type="tool_call",
|
|
230
|
+
tool_name="SDK.create",
|
|
231
|
+
input_summary=f"Create {action_type}: {self._data.get('title', 'Untitled')}",
|
|
232
|
+
output_summary=f"Created {collection_name}/{node.id}",
|
|
233
|
+
context={
|
|
234
|
+
"collection": collection_name,
|
|
235
|
+
"node_id": node.id,
|
|
236
|
+
"node_type": action_type,
|
|
237
|
+
"title": node.title,
|
|
238
|
+
"status": self._data.get("status", "todo"),
|
|
239
|
+
"priority": self._data.get("priority", "medium"),
|
|
240
|
+
},
|
|
241
|
+
cost_tokens=50,
|
|
242
|
+
)
|
|
243
|
+
except Exception as e:
|
|
244
|
+
# Never break save because of logging
|
|
245
|
+
import logging
|
|
246
|
+
|
|
247
|
+
logging.debug(f"Event logging failed: {e}")
|
|
163
248
|
|
|
164
|
-
#
|
|
165
|
-
if hasattr(self._sdk,
|
|
249
|
+
# Also log via SessionManager for backward compatibility
|
|
250
|
+
if hasattr(self._sdk, "session_manager") and self._sdk.agent:
|
|
166
251
|
try:
|
|
167
252
|
self._sdk.session_manager._maybe_log_work_item_action(
|
|
168
253
|
agent=self._sdk.agent,
|
|
169
254
|
tool="FeatureCreate",
|
|
170
255
|
summary=f"Created: {collection_name}/{node.id}",
|
|
171
256
|
feature_id=node.id,
|
|
172
|
-
payload={
|
|
257
|
+
payload={
|
|
258
|
+
"collection": collection_name,
|
|
259
|
+
"action": "create",
|
|
260
|
+
"title": node.title,
|
|
261
|
+
},
|
|
173
262
|
)
|
|
174
263
|
except Exception:
|
|
175
264
|
# Never break save because of logging
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Bug builder for creating bug report nodes.
|
|
5
|
+
|
|
6
|
+
Extends BaseBuilder with bug-specific methods like
|
|
7
|
+
severity and reproduction steps.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from htmlgraph.sdk import SDK
|
|
15
|
+
|
|
16
|
+
from htmlgraph.builders.base import BaseBuilder
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BugBuilder(BaseBuilder["BugBuilder"]):
|
|
20
|
+
"""
|
|
21
|
+
Fluent builder for creating bugs.
|
|
22
|
+
|
|
23
|
+
Inherits common builder methods from BaseBuilder and adds
|
|
24
|
+
bug-specific methods for issue tracking:
|
|
25
|
+
- severity: Bug severity level
|
|
26
|
+
- repro_steps: Steps to reproduce
|
|
27
|
+
- expected/actual: Expected vs actual behavior
|
|
28
|
+
- affected_version: Version where bug occurs
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> sdk = SDK(agent="claude")
|
|
32
|
+
>>> bug = sdk.bugs.create("Login button unresponsive") \\
|
|
33
|
+
... .set_priority("critical") \\
|
|
34
|
+
... .set_severity("high") \\
|
|
35
|
+
... .set_repro_steps(["Go to login", "Click button", "Nothing happens"]) \\
|
|
36
|
+
... .save()
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
node_type = "bug"
|
|
40
|
+
|
|
41
|
+
def __init__(self, sdk: SDK, title: str, **kwargs: Any):
|
|
42
|
+
"""Initialize bug builder with agent attribution."""
|
|
43
|
+
super().__init__(sdk, title, **kwargs)
|
|
44
|
+
# Auto-assign agent from SDK for work tracking
|
|
45
|
+
if sdk._agent_id:
|
|
46
|
+
self._data["agent_assigned"] = sdk._agent_id
|
|
47
|
+
elif "agent_assigned" not in self._data:
|
|
48
|
+
# Log warning if agent not assigned (defensive check)
|
|
49
|
+
import logging
|
|
50
|
+
|
|
51
|
+
logging.warning(
|
|
52
|
+
f"Creating bug '{self._data.get('title', 'Unknown')}' without agent attribution. "
|
|
53
|
+
"Pass agent='name' to SDK() initialization."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def set_severity(self, severity: str) -> BugBuilder:
|
|
57
|
+
"""
|
|
58
|
+
Set bug severity level.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
severity: Severity level (low, medium, high, critical)
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Self for method chaining
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
>>> bug.set_severity("critical")
|
|
68
|
+
"""
|
|
69
|
+
self._data["severity"] = severity
|
|
70
|
+
return self
|
|
71
|
+
|
|
72
|
+
def set_repro_steps(self, steps: list[str]) -> BugBuilder:
|
|
73
|
+
"""
|
|
74
|
+
Set steps to reproduce the bug.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
steps: List of reproduction steps
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Self for method chaining
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
>>> bug.set_repro_steps(["Open app", "Click login", "Enter credentials"])
|
|
84
|
+
"""
|
|
85
|
+
self._data["repro_steps"] = steps
|
|
86
|
+
return self
|
|
87
|
+
|
|
88
|
+
def set_expected_behavior(self, expected: str) -> BugBuilder:
|
|
89
|
+
"""
|
|
90
|
+
Set expected behavior.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
expected: What should happen
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Self for method chaining
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
>>> bug.set_expected_behavior("User should be logged in")
|
|
100
|
+
"""
|
|
101
|
+
self._data["expected_behavior"] = expected
|
|
102
|
+
return self
|
|
103
|
+
|
|
104
|
+
def set_actual_behavior(self, actual: str) -> BugBuilder:
|
|
105
|
+
"""
|
|
106
|
+
Set actual (buggy) behavior.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
actual: What actually happens
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Self for method chaining
|
|
113
|
+
|
|
114
|
+
Example:
|
|
115
|
+
>>> bug.set_actual_behavior("Button does nothing")
|
|
116
|
+
"""
|
|
117
|
+
self._data["actual_behavior"] = actual
|
|
118
|
+
return self
|
|
119
|
+
|
|
120
|
+
def set_affected_version(self, version: str) -> BugBuilder:
|
|
121
|
+
"""
|
|
122
|
+
Set the affected version.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
version: Version string where bug occurs
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Self for method chaining
|
|
129
|
+
|
|
130
|
+
Example:
|
|
131
|
+
>>> bug.set_affected_version("1.2.3")
|
|
132
|
+
"""
|
|
133
|
+
self._data["affected_version"] = version
|
|
134
|
+
return self
|
|
135
|
+
|
|
136
|
+
def set_environment(self, environment: str) -> BugBuilder:
|
|
137
|
+
"""
|
|
138
|
+
Set the environment where bug occurs.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
environment: Environment description (e.g., "Chrome 120, macOS")
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Self for method chaining
|
|
145
|
+
|
|
146
|
+
Example:
|
|
147
|
+
>>> bug.set_environment("Chrome 120, macOS Sonoma")
|
|
148
|
+
"""
|
|
149
|
+
self._data["environment"] = environment
|
|
150
|
+
return self
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Chore builder for creating maintenance task nodes.
|
|
5
|
+
|
|
6
|
+
Extends BaseBuilder with chore-specific methods like
|
|
7
|
+
chore type and recurrence.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from htmlgraph.sdk import SDK
|
|
15
|
+
|
|
16
|
+
from htmlgraph.builders.base import BaseBuilder
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ChoreBuilder(BaseBuilder["ChoreBuilder"]):
|
|
20
|
+
"""
|
|
21
|
+
Fluent builder for creating chores.
|
|
22
|
+
|
|
23
|
+
Inherits common builder methods from BaseBuilder and adds
|
|
24
|
+
chore-specific methods for maintenance work:
|
|
25
|
+
- chore_type: Classification of chore
|
|
26
|
+
- is_recurring: Whether this repeats
|
|
27
|
+
- recurrence_interval: How often it recurs
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
>>> sdk = SDK(agent="claude")
|
|
31
|
+
>>> chore = sdk.chores.create("Update dependencies") \\
|
|
32
|
+
... .set_priority("low") \\
|
|
33
|
+
... .set_chore_type("maintenance") \\
|
|
34
|
+
... .set_recurring(interval_days=30) \\
|
|
35
|
+
... .save()
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
node_type = "chore"
|
|
39
|
+
|
|
40
|
+
def __init__(self, sdk: SDK, title: str, **kwargs: Any):
|
|
41
|
+
"""Initialize chore builder with agent attribution."""
|
|
42
|
+
super().__init__(sdk, title, **kwargs)
|
|
43
|
+
# Auto-assign agent from SDK for work tracking
|
|
44
|
+
if sdk._agent_id:
|
|
45
|
+
self._data["agent_assigned"] = sdk._agent_id
|
|
46
|
+
elif "agent_assigned" not in self._data:
|
|
47
|
+
# Log warning if agent not assigned (defensive check)
|
|
48
|
+
import logging
|
|
49
|
+
|
|
50
|
+
logging.warning(
|
|
51
|
+
f"Creating chore '{self._data.get('title', 'Unknown')}' without agent attribution. "
|
|
52
|
+
"Pass agent='name' to SDK() initialization."
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def set_chore_type(self, chore_type: str) -> ChoreBuilder:
|
|
56
|
+
"""
|
|
57
|
+
Set the type of chore.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
chore_type: Type (maintenance, refactor, cleanup, upgrade, documentation)
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Self for method chaining
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
>>> chore.set_chore_type("maintenance")
|
|
67
|
+
"""
|
|
68
|
+
self._data["chore_type"] = chore_type
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def set_recurring(self, interval_days: int | None = None) -> ChoreBuilder:
|
|
72
|
+
"""
|
|
73
|
+
Mark chore as recurring with optional interval.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
interval_days: Days between occurrences (optional)
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Self for method chaining
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
>>> chore.set_recurring(interval_days=7) # Weekly
|
|
83
|
+
"""
|
|
84
|
+
self._data["is_recurring"] = True
|
|
85
|
+
if interval_days:
|
|
86
|
+
self._data["recurrence_interval_days"] = interval_days
|
|
87
|
+
return self
|
|
88
|
+
|
|
89
|
+
def set_scope(self, scope: str) -> ChoreBuilder:
|
|
90
|
+
"""
|
|
91
|
+
Set the scope of the chore.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
scope: Scope description (e.g., "entire codebase", "auth module")
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Self for method chaining
|
|
98
|
+
|
|
99
|
+
Example:
|
|
100
|
+
>>> chore.set_scope("authentication module")
|
|
101
|
+
"""
|
|
102
|
+
self._data["scope"] = scope
|
|
103
|
+
return self
|
|
104
|
+
|
|
105
|
+
def set_estimated_effort(self, hours: float) -> ChoreBuilder:
|
|
106
|
+
"""
|
|
107
|
+
Set estimated effort in hours.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
hours: Estimated hours to complete
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Self for method chaining
|
|
114
|
+
|
|
115
|
+
Example:
|
|
116
|
+
>>> chore.set_estimated_effort(2.5)
|
|
117
|
+
"""
|
|
118
|
+
self._data["estimated_effort_hours"] = hours
|
|
119
|
+
return self
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Epic builder for creating large body of work nodes.
|
|
5
|
+
|
|
6
|
+
Extends BaseBuilder with epic-specific methods like
|
|
7
|
+
child features and milestones.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
from datetime import date
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from htmlgraph.sdk import SDK
|
|
16
|
+
|
|
17
|
+
from htmlgraph.builders.base import BaseBuilder
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EpicBuilder(BaseBuilder["EpicBuilder"]):
|
|
21
|
+
"""
|
|
22
|
+
Fluent builder for creating epics.
|
|
23
|
+
|
|
24
|
+
Inherits common builder methods from BaseBuilder and adds
|
|
25
|
+
epic-specific methods for large initiatives:
|
|
26
|
+
- child_features: Features contained in this epic
|
|
27
|
+
- target_date: Target completion date
|
|
28
|
+
- success_criteria: How success is measured
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> sdk = SDK(agent="claude")
|
|
32
|
+
>>> epic = sdk.epics.create("v2.0 Release") \\
|
|
33
|
+
... .set_priority("high") \\
|
|
34
|
+
... .set_target_date(date(2025, 3, 1)) \\
|
|
35
|
+
... .add_child_feature("feat-001") \\
|
|
36
|
+
... .add_child_feature("feat-002") \\
|
|
37
|
+
... .save()
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
node_type = "epic"
|
|
41
|
+
|
|
42
|
+
def __init__(self, sdk: SDK, title: str, **kwargs: Any):
|
|
43
|
+
"""Initialize epic builder with agent attribution."""
|
|
44
|
+
super().__init__(sdk, title, **kwargs)
|
|
45
|
+
# Auto-assign agent from SDK for work tracking
|
|
46
|
+
if sdk._agent_id:
|
|
47
|
+
self._data["agent_assigned"] = sdk._agent_id
|
|
48
|
+
elif "agent_assigned" not in self._data:
|
|
49
|
+
# Log warning if agent not assigned (defensive check)
|
|
50
|
+
import logging
|
|
51
|
+
|
|
52
|
+
logging.warning(
|
|
53
|
+
f"Creating epic '{self._data.get('title', 'Unknown')}' without agent attribution. "
|
|
54
|
+
"Pass agent='name' to SDK() initialization."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def add_child_feature(self, feature_id: str) -> EpicBuilder:
|
|
58
|
+
"""
|
|
59
|
+
Add a feature as a child of this epic.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
feature_id: ID of the child feature
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Self for method chaining
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
>>> epic.add_child_feature("feat-abc123")
|
|
69
|
+
"""
|
|
70
|
+
return self._add_edge("contains", feature_id)
|
|
71
|
+
|
|
72
|
+
def add_child_features(self, feature_ids: list[str]) -> EpicBuilder:
|
|
73
|
+
"""
|
|
74
|
+
Add multiple features as children of this epic.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
feature_ids: List of child feature IDs
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Self for method chaining
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
>>> epic.add_child_features(["feat-001", "feat-002", "feat-003"])
|
|
84
|
+
"""
|
|
85
|
+
for fid in feature_ids:
|
|
86
|
+
self.add_child_feature(fid)
|
|
87
|
+
return self
|
|
88
|
+
|
|
89
|
+
def set_target_date(self, target: date) -> EpicBuilder:
|
|
90
|
+
"""
|
|
91
|
+
Set target completion date.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
target: Target date for epic completion
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Self for method chaining
|
|
98
|
+
|
|
99
|
+
Example:
|
|
100
|
+
>>> epic.set_target_date(date(2025, 6, 1))
|
|
101
|
+
"""
|
|
102
|
+
return self._set_date("target_date", target)
|
|
103
|
+
|
|
104
|
+
def set_success_criteria(self, criteria: list[str]) -> EpicBuilder:
|
|
105
|
+
"""
|
|
106
|
+
Set success criteria for the epic.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
criteria: List of success criteria
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Self for method chaining
|
|
113
|
+
|
|
114
|
+
Example:
|
|
115
|
+
>>> epic.set_success_criteria(["100% test coverage", "Zero critical bugs"])
|
|
116
|
+
"""
|
|
117
|
+
self._data["success_criteria"] = criteria
|
|
118
|
+
return self
|
|
119
|
+
|
|
120
|
+
def set_business_value(self, value: str) -> EpicBuilder:
|
|
121
|
+
"""
|
|
122
|
+
Set the business value statement.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
value: Business value description
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Self for method chaining
|
|
129
|
+
|
|
130
|
+
Example:
|
|
131
|
+
>>> epic.set_business_value("Increase user retention by 20%")
|
|
132
|
+
"""
|
|
133
|
+
self._data["business_value"] = value
|
|
134
|
+
return self
|
|
135
|
+
|
|
136
|
+
def set_stakeholder(self, stakeholder: str) -> EpicBuilder:
|
|
137
|
+
"""
|
|
138
|
+
Set the primary stakeholder.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
stakeholder: Stakeholder name or role
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Self for method chaining
|
|
145
|
+
|
|
146
|
+
Example:
|
|
147
|
+
>>> epic.set_stakeholder("Product Team")
|
|
148
|
+
"""
|
|
149
|
+
self._data["stakeholder"] = stakeholder
|
|
150
|
+
return self
|