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
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from datetime import date, datetime
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Protocol
|
|
9
|
+
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from htmlgraph.sdk import SDK
|
|
14
|
+
|
|
15
|
+
_console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CommandError(Exception):
|
|
19
|
+
"""User-facing CLI error with an exit code."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, message: str, exit_code: int = 1) -> None:
|
|
22
|
+
super().__init__(message)
|
|
23
|
+
self.exit_code = exit_code
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class CommandResult:
|
|
28
|
+
data: Any = None
|
|
29
|
+
text: str | Iterable[str] | None = None
|
|
30
|
+
json_data: Any | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Formatter(Protocol):
|
|
34
|
+
def output(self, result: CommandResult) -> None: ...
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _serialize_json(value: Any) -> Any:
|
|
38
|
+
if value is None:
|
|
39
|
+
return None
|
|
40
|
+
if isinstance(value, (datetime, date)):
|
|
41
|
+
return value.isoformat()
|
|
42
|
+
if hasattr(value, "model_dump") and callable(getattr(value, "model_dump")):
|
|
43
|
+
return _serialize_json(value.model_dump())
|
|
44
|
+
if hasattr(value, "to_dict") and callable(getattr(value, "to_dict")):
|
|
45
|
+
return _serialize_json(value.to_dict())
|
|
46
|
+
if isinstance(value, dict):
|
|
47
|
+
return {key: _serialize_json(val) for key, val in value.items()}
|
|
48
|
+
if isinstance(value, (list, tuple, set)):
|
|
49
|
+
return [_serialize_json(item) for item in value]
|
|
50
|
+
return value
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class JsonFormatter:
|
|
54
|
+
def output(self, result: CommandResult) -> None:
|
|
55
|
+
payload = result.json_data if result.json_data is not None else result.data
|
|
56
|
+
_console.print(json.dumps(_serialize_json(payload), indent=2))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TextFormatter:
|
|
60
|
+
def output(self, result: CommandResult) -> None:
|
|
61
|
+
if result.text is None:
|
|
62
|
+
if result.data is not None:
|
|
63
|
+
_console.print(result.data)
|
|
64
|
+
return
|
|
65
|
+
if isinstance(result.text, str):
|
|
66
|
+
_console.print(result.text)
|
|
67
|
+
return
|
|
68
|
+
_console.print("\n".join(str(line) for line in result.text))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_formatter(format_name: str) -> Formatter:
|
|
72
|
+
if format_name == "json":
|
|
73
|
+
return JsonFormatter()
|
|
74
|
+
if format_name in ("text", "plain", ""):
|
|
75
|
+
return TextFormatter()
|
|
76
|
+
raise CommandError(f"Unknown output format '{format_name}'")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class BaseCommand:
|
|
80
|
+
def __init__(self) -> None:
|
|
81
|
+
self.graph_dir: str | None = None
|
|
82
|
+
self.agent: str | None = None
|
|
83
|
+
self._sdk: SDK | None = None
|
|
84
|
+
|
|
85
|
+
def validate(self) -> None:
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
def execute(self) -> CommandResult:
|
|
89
|
+
raise NotImplementedError
|
|
90
|
+
|
|
91
|
+
def get_sdk(self) -> Any:
|
|
92
|
+
if self.graph_dir is None:
|
|
93
|
+
raise CommandError("Missing graph directory for command execution.")
|
|
94
|
+
if self._sdk is None:
|
|
95
|
+
from htmlgraph.sdk import SDK
|
|
96
|
+
|
|
97
|
+
self._sdk = SDK(directory=self.graph_dir, agent=self.agent)
|
|
98
|
+
return self._sdk
|
|
99
|
+
|
|
100
|
+
def run(self, *, graph_dir: str, agent: str | None, output_format: str) -> None:
|
|
101
|
+
self.graph_dir = graph_dir
|
|
102
|
+
self.agent = agent
|
|
103
|
+
try:
|
|
104
|
+
self.validate()
|
|
105
|
+
result = self.execute()
|
|
106
|
+
formatter = get_formatter(output_format)
|
|
107
|
+
formatter.output(result)
|
|
108
|
+
except CommandError as exc:
|
|
109
|
+
error_console = Console(file=sys.stderr)
|
|
110
|
+
error_console.print(f"[red]Error: {exc}[/red]")
|
|
111
|
+
sys.exit(exc.exit_code)
|
|
112
|
+
except ValueError as exc:
|
|
113
|
+
error_console = Console(file=sys.stderr)
|
|
114
|
+
error_console.print(f"[red]Error: {exc}[/red]")
|
|
115
|
+
sys.exit(1)
|
|
@@ -15,6 +15,7 @@ from htmlgraph.collections.metric import MetricCollection
|
|
|
15
15
|
from htmlgraph.collections.pattern import PatternCollection
|
|
16
16
|
from htmlgraph.collections.phase import PhaseCollection
|
|
17
17
|
from htmlgraph.collections.spike import SpikeCollection
|
|
18
|
+
from htmlgraph.collections.task_delegation import TaskDelegationCollection
|
|
18
19
|
from htmlgraph.collections.todo import TodoCollection
|
|
19
20
|
|
|
20
21
|
__all__ = [
|
|
@@ -29,4 +30,5 @@ __all__ = [
|
|
|
29
30
|
"InsightCollection",
|
|
30
31
|
"MetricCollection",
|
|
31
32
|
"TodoCollection",
|
|
33
|
+
"TaskDelegationCollection",
|
|
32
34
|
]
|
htmlgraph/collections/base.py
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
1
7
|
"""
|
|
2
8
|
Base collection class for managing nodes.
|
|
3
9
|
|
|
@@ -5,7 +11,6 @@ Provides common collection functionality for all node types
|
|
|
5
11
|
with lazy-loading, filtering, and batch operations.
|
|
6
12
|
"""
|
|
7
13
|
|
|
8
|
-
from __future__ import annotations
|
|
9
14
|
|
|
10
15
|
from collections.abc import Callable, Iterator
|
|
11
16
|
from contextlib import contextmanager
|
|
@@ -69,9 +74,22 @@ class BaseCollection(Generic[CollectionT]):
|
|
|
69
74
|
self._collection_name = collection_name or self._collection_name
|
|
70
75
|
self._node_type = node_type or self._node_type
|
|
71
76
|
self._graph: HtmlGraph | None = None # Lazy-loaded
|
|
77
|
+
self._ref_manager: Any = None # Set by SDK during initialization
|
|
72
78
|
|
|
73
79
|
def _ensure_graph(self) -> HtmlGraph:
|
|
74
|
-
"""
|
|
80
|
+
"""
|
|
81
|
+
Get or initialize the graph for this collection.
|
|
82
|
+
|
|
83
|
+
Uses SDK's shared graph instances where available to avoid creating
|
|
84
|
+
multiple graph objects for the same collection. Creates a new instance
|
|
85
|
+
for unrecognized collections.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
HtmlGraph instance for this collection
|
|
89
|
+
|
|
90
|
+
Note:
|
|
91
|
+
This method is lazy - the graph is only loaded on first access.
|
|
92
|
+
"""
|
|
75
93
|
if self._graph is None:
|
|
76
94
|
# Use SDK's shared graph instances to avoid multiple graph objects
|
|
77
95
|
if self._collection_name == "features" and hasattr(self._sdk, "_graph"):
|
|
@@ -91,8 +109,72 @@ class BaseCollection(Generic[CollectionT]):
|
|
|
91
109
|
|
|
92
110
|
return self._graph
|
|
93
111
|
|
|
112
|
+
def __getattribute__(self, name: str) -> Any:
|
|
113
|
+
"""
|
|
114
|
+
Override attribute access to provide helpful error messages.
|
|
115
|
+
|
|
116
|
+
When an attribute doesn't exist, provides suggestions for common
|
|
117
|
+
mistakes and similar method names to improve discoverability.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
name: Attribute name being accessed
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
The requested attribute
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
AttributeError: With helpful suggestions if attribute not found
|
|
127
|
+
"""
|
|
128
|
+
try:
|
|
129
|
+
return object.__getattribute__(self, name)
|
|
130
|
+
except AttributeError as e:
|
|
131
|
+
# Get available methods
|
|
132
|
+
available = [m for m in dir(self) if not m.startswith("_")]
|
|
133
|
+
|
|
134
|
+
# Common mistakes mapping
|
|
135
|
+
common_mistakes = {
|
|
136
|
+
"mark_complete": "mark_done",
|
|
137
|
+
"complete": "Use complete(node_id) for single item or mark_done([ids]) for batch",
|
|
138
|
+
"finish": "mark_done",
|
|
139
|
+
"end": "mark_done",
|
|
140
|
+
"update_status": "edit() context manager or batch_update()",
|
|
141
|
+
"mark_as_done": "mark_done",
|
|
142
|
+
"set_done": "mark_done",
|
|
143
|
+
"complete_all": "mark_done",
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
suggestions = []
|
|
147
|
+
if name in common_mistakes:
|
|
148
|
+
suggestions.append(f"Did you mean: {common_mistakes[name]}")
|
|
149
|
+
|
|
150
|
+
# Find similar method names
|
|
151
|
+
similar = [
|
|
152
|
+
m
|
|
153
|
+
for m in available
|
|
154
|
+
if name.lower() in m.lower() or m.lower() in name.lower()
|
|
155
|
+
]
|
|
156
|
+
if similar:
|
|
157
|
+
suggestions.append(f"Similar methods: {', '.join(similar[:5])}")
|
|
158
|
+
|
|
159
|
+
# Build helpful error message
|
|
160
|
+
error_msg = f"'{type(self).__name__}' has no attribute '{name}'."
|
|
161
|
+
if suggestions:
|
|
162
|
+
error_msg += "\n\n" + "\n".join(suggestions)
|
|
163
|
+
error_msg += f"\n\nAvailable methods: {', '.join(available[:15])}"
|
|
164
|
+
error_msg += "\n\nTip: Use sdk.help() to see all available operations."
|
|
165
|
+
|
|
166
|
+
raise AttributeError(error_msg) from e
|
|
167
|
+
|
|
94
168
|
def __dir__(self) -> list[str]:
|
|
95
|
-
"""
|
|
169
|
+
"""
|
|
170
|
+
Return attributes with most useful ones first.
|
|
171
|
+
|
|
172
|
+
Orders attributes to show commonly-used methods first in auto-complete
|
|
173
|
+
and help() output, improving discoverability for new users.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
List of attribute names, ordered by priority then alphabetically
|
|
177
|
+
"""
|
|
96
178
|
priority = [
|
|
97
179
|
# Creation and retrieval
|
|
98
180
|
"create",
|
|
@@ -124,6 +206,39 @@ class BaseCollection(Generic[CollectionT]):
|
|
|
124
206
|
# Return priority items first, then regular, then dunder
|
|
125
207
|
return priority + regular + dunder
|
|
126
208
|
|
|
209
|
+
def set_ref_manager(self, ref_manager: Any) -> None:
|
|
210
|
+
"""
|
|
211
|
+
Set the ref manager for this collection.
|
|
212
|
+
|
|
213
|
+
Called by SDK during initialization to enable short ref support.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
ref_manager: RefManager instance from SDK
|
|
217
|
+
"""
|
|
218
|
+
self._ref_manager = ref_manager
|
|
219
|
+
|
|
220
|
+
def get_ref(self, node_id: str) -> str | None:
|
|
221
|
+
"""
|
|
222
|
+
Get short ref for a node in this collection.
|
|
223
|
+
|
|
224
|
+
Convenience method to get ref without accessing SDK directly.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
node_id: Full node ID like "feat-a1b2c3d4"
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Short ref like "@f1", or None if ref manager not available
|
|
231
|
+
|
|
232
|
+
Example:
|
|
233
|
+
>>> feature = sdk.features.get("feat-abc123")
|
|
234
|
+
>>> ref = sdk.features.get_ref(feature.id)
|
|
235
|
+
>>> logger.info("%s", ref) # "@f1"
|
|
236
|
+
"""
|
|
237
|
+
if self._ref_manager:
|
|
238
|
+
result = self._ref_manager.get_ref(node_id)
|
|
239
|
+
return cast(str | None, result)
|
|
240
|
+
return None
|
|
241
|
+
|
|
127
242
|
def create(
|
|
128
243
|
self, title: str, priority: str = "medium", status: str = "todo", **kwargs: Any
|
|
129
244
|
) -> Any:
|
|
@@ -356,7 +471,7 @@ class BaseCollection(Generic[CollectionT]):
|
|
|
356
471
|
|
|
357
472
|
Example:
|
|
358
473
|
>>> count = sdk.features.batch_delete(["feat-001", "feat-002", "feat-003"])
|
|
359
|
-
>>>
|
|
474
|
+
>>> logger.info(f"Deleted {count} features")
|
|
360
475
|
"""
|
|
361
476
|
graph = self._ensure_graph()
|
|
362
477
|
return cast(int, graph.batch_delete(node_ids))
|
|
@@ -418,7 +533,7 @@ class BaseCollection(Generic[CollectionT]):
|
|
|
418
533
|
|
|
419
534
|
return count
|
|
420
535
|
|
|
421
|
-
def mark_done(self, node_ids: list[str]) ->
|
|
536
|
+
def mark_done(self, node_ids: list[str]) -> dict[str, Any]:
|
|
422
537
|
"""
|
|
423
538
|
Batch mark nodes as done.
|
|
424
539
|
|
|
@@ -426,12 +541,55 @@ class BaseCollection(Generic[CollectionT]):
|
|
|
426
541
|
node_ids: List of node IDs to mark as done
|
|
427
542
|
|
|
428
543
|
Returns:
|
|
429
|
-
|
|
544
|
+
Dict with 'success_count', 'failed_ids', and 'warnings'
|
|
430
545
|
|
|
431
546
|
Example:
|
|
432
|
-
>>> sdk.features.mark_done(["feat-001", "feat-002"])
|
|
547
|
+
>>> result = sdk.features.mark_done(["feat-001", "feat-002"])
|
|
548
|
+
>>> logger.info(f"Completed {result['success_count']} of {len(node_ids)}")
|
|
549
|
+
>>> if result['failed_ids']:
|
|
550
|
+
... logger.info(f"Failed: {result['failed_ids']}")
|
|
433
551
|
"""
|
|
434
|
-
|
|
552
|
+
graph = self._ensure_graph()
|
|
553
|
+
results: dict[str, Any] = {"success_count": 0, "failed_ids": [], "warnings": []}
|
|
554
|
+
|
|
555
|
+
for node_id in node_ids:
|
|
556
|
+
try:
|
|
557
|
+
node = graph.get(node_id)
|
|
558
|
+
if not node:
|
|
559
|
+
results["failed_ids"].append(node_id)
|
|
560
|
+
results["warnings"].append(f"Node {node_id} not found")
|
|
561
|
+
continue
|
|
562
|
+
|
|
563
|
+
node.status = "done"
|
|
564
|
+
node.updated = datetime.now()
|
|
565
|
+
graph.update(node)
|
|
566
|
+
results["success_count"] += 1
|
|
567
|
+
|
|
568
|
+
# Log completion event to SQLite
|
|
569
|
+
try:
|
|
570
|
+
self._sdk._log_event(
|
|
571
|
+
event_type="tool_call",
|
|
572
|
+
tool_name="SDK.mark_done",
|
|
573
|
+
input_summary=f"Mark {self._node_type} done: {node_id}",
|
|
574
|
+
output_summary=f"Marked {node_id} as done",
|
|
575
|
+
context={
|
|
576
|
+
"collection": self._collection_name,
|
|
577
|
+
"node_id": node_id,
|
|
578
|
+
"node_type": self._node_type,
|
|
579
|
+
"title": node.title,
|
|
580
|
+
},
|
|
581
|
+
cost_tokens=25,
|
|
582
|
+
)
|
|
583
|
+
except Exception as e:
|
|
584
|
+
import logging
|
|
585
|
+
|
|
586
|
+
logging.debug(f"Event logging failed for mark_done: {e}")
|
|
587
|
+
|
|
588
|
+
except Exception as e:
|
|
589
|
+
results["failed_ids"].append(node_id)
|
|
590
|
+
results["warnings"].append(f"Failed to mark {node_id}: {str(e)}")
|
|
591
|
+
|
|
592
|
+
return results
|
|
435
593
|
|
|
436
594
|
def assign(self, node_ids: list[str], agent: str) -> int:
|
|
437
595
|
"""
|
|
@@ -454,13 +612,15 @@ class BaseCollection(Generic[CollectionT]):
|
|
|
454
612
|
"""
|
|
455
613
|
Start working on a node (feature/bug/etc).
|
|
456
614
|
|
|
457
|
-
Delegates to SessionManager
|
|
615
|
+
Delegates to SessionManager if available for smart tracking:
|
|
458
616
|
1. Check WIP limits
|
|
459
617
|
2. Ensure not claimed by others
|
|
460
618
|
3. Auto-claim for agent
|
|
461
619
|
4. Link to active session
|
|
462
620
|
5. Log 'FeatureStart' event
|
|
463
621
|
|
|
622
|
+
Falls back to simple status update if SessionManager not available.
|
|
623
|
+
|
|
464
624
|
Args:
|
|
465
625
|
node_id: Node ID to start
|
|
466
626
|
agent: Agent ID (defaults to SDK agent)
|
|
@@ -470,6 +630,10 @@ class BaseCollection(Generic[CollectionT]):
|
|
|
470
630
|
|
|
471
631
|
Raises:
|
|
472
632
|
NodeNotFoundError: If node not found
|
|
633
|
+
|
|
634
|
+
Example:
|
|
635
|
+
>>> sdk.features.start('feat-abc123')
|
|
636
|
+
>>> sdk.features.start('feat-xyz', agent='claude')
|
|
473
637
|
"""
|
|
474
638
|
agent = agent or self._sdk.agent
|
|
475
639
|
|
|
@@ -502,14 +666,17 @@ class BaseCollection(Generic[CollectionT]):
|
|
|
502
666
|
transcript_id: str | None = None,
|
|
503
667
|
) -> Node | None:
|
|
504
668
|
"""
|
|
505
|
-
|
|
669
|
+
Mark a node as complete.
|
|
506
670
|
|
|
507
|
-
Delegates to SessionManager
|
|
508
|
-
|
|
671
|
+
Delegates to SessionManager if available for event logging and
|
|
672
|
+
transcript linking:
|
|
673
|
+
1. Update status to 'done'
|
|
509
674
|
2. Log 'FeatureComplete' event
|
|
510
675
|
3. Release claim (optional behavior)
|
|
511
676
|
4. Link transcript if provided (for parallel agent tracking)
|
|
512
677
|
|
|
678
|
+
Falls back to simple status update if SessionManager not available.
|
|
679
|
+
|
|
513
680
|
Args:
|
|
514
681
|
node_id: Node ID to complete
|
|
515
682
|
agent: Agent ID (defaults to SDK agent)
|
|
@@ -521,6 +688,10 @@ class BaseCollection(Generic[CollectionT]):
|
|
|
521
688
|
|
|
522
689
|
Raises:
|
|
523
690
|
NodeNotFoundError: If node not found
|
|
691
|
+
|
|
692
|
+
Example:
|
|
693
|
+
>>> sdk.features.complete('feat-abc123')
|
|
694
|
+
>>> sdk.features.complete('feat-xyz', agent='claude', transcript_id='trans-123')
|
|
524
695
|
"""
|
|
525
696
|
agent = agent or self._sdk.agent
|
|
526
697
|
|
|
@@ -551,11 +722,13 @@ class BaseCollection(Generic[CollectionT]):
|
|
|
551
722
|
"""
|
|
552
723
|
Claim a node for an agent.
|
|
553
724
|
|
|
554
|
-
Delegates to SessionManager
|
|
725
|
+
Delegates to SessionManager if available for ownership tracking:
|
|
555
726
|
1. Check ownership rules
|
|
556
727
|
2. Update assignment
|
|
557
728
|
3. Log 'FeatureClaim' event
|
|
558
729
|
|
|
730
|
+
Falls back to simple assignment if SessionManager not available.
|
|
731
|
+
|
|
559
732
|
Args:
|
|
560
733
|
node_id: Node ID to claim
|
|
561
734
|
agent: Agent ID (defaults to SDK agent)
|
|
@@ -567,6 +740,10 @@ class BaseCollection(Generic[CollectionT]):
|
|
|
567
740
|
ValueError: If agent not provided and SDK has no agent
|
|
568
741
|
NodeNotFoundError: If node not found
|
|
569
742
|
ClaimConflictError: If node already claimed by different agent
|
|
743
|
+
|
|
744
|
+
Example:
|
|
745
|
+
>>> sdk.features.claim('feat-abc123')
|
|
746
|
+
>>> sdk.features.claim('feat-xyz', agent='claude')
|
|
570
747
|
"""
|
|
571
748
|
agent = agent or self._sdk.agent
|
|
572
749
|
if not agent:
|
|
@@ -601,11 +778,13 @@ class BaseCollection(Generic[CollectionT]):
|
|
|
601
778
|
"""
|
|
602
779
|
Release a claimed node.
|
|
603
780
|
|
|
604
|
-
Delegates to SessionManager
|
|
781
|
+
Delegates to SessionManager if available for ownership tracking:
|
|
605
782
|
1. Verify ownership
|
|
606
783
|
2. Clear assignment
|
|
607
784
|
3. Log 'FeatureRelease' event
|
|
608
785
|
|
|
786
|
+
Falls back to simple assignment clearing if SessionManager not available.
|
|
787
|
+
|
|
609
788
|
Args:
|
|
610
789
|
node_id: Node ID to release
|
|
611
790
|
agent: Agent ID (defaults to SDK agent)
|
|
@@ -615,6 +794,10 @@ class BaseCollection(Generic[CollectionT]):
|
|
|
615
794
|
|
|
616
795
|
Raises:
|
|
617
796
|
NodeNotFoundError: If node not found
|
|
797
|
+
|
|
798
|
+
Example:
|
|
799
|
+
>>> sdk.features.release('feat-abc123')
|
|
800
|
+
>>> sdk.features.release('feat-xyz', agent='claude')
|
|
618
801
|
"""
|
|
619
802
|
# SessionManager.release_feature requires an agent to verify ownership
|
|
620
803
|
agent = agent or self._sdk.agent
|
htmlgraph/collections/bug.py
CHANGED
htmlgraph/collections/chore.py
CHANGED
htmlgraph/collections/epic.py
CHANGED
htmlgraph/collections/feature.py
CHANGED
htmlgraph/collections/insight.py
CHANGED
htmlgraph/collections/metric.py
CHANGED
htmlgraph/collections/pattern.py
CHANGED
htmlgraph/collections/phase.py
CHANGED