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,581 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FeatureRepository - Abstract interface for Feature data access.
|
|
3
|
+
|
|
4
|
+
Unifies all data access patterns for Features across HtmlGraph.
|
|
5
|
+
Implementations handle:
|
|
6
|
+
- HTML file storage + SQLite database
|
|
7
|
+
- Lazy loading and caching
|
|
8
|
+
- Query building and filtering
|
|
9
|
+
- Concurrent access safety
|
|
10
|
+
- Event logging and session tracking
|
|
11
|
+
|
|
12
|
+
All implementations MUST pass FeatureRepositoryComplianceTests.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import builtins
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from collections.abc import Callable
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class RepositoryQuery:
|
|
24
|
+
"""
|
|
25
|
+
Query builder for chaining filters.
|
|
26
|
+
|
|
27
|
+
Supports method chaining:
|
|
28
|
+
repo.where(status='todo').where(priority='high').execute()
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
filters: dict[str, Any]
|
|
32
|
+
|
|
33
|
+
def execute(self) -> list[Any]:
|
|
34
|
+
"""Execute the query and return results."""
|
|
35
|
+
raise NotImplementedError("Subclass must implement")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class FeatureRepositoryError(Exception):
|
|
39
|
+
"""Base exception for repository operations."""
|
|
40
|
+
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class FeatureNotFoundError(FeatureRepositoryError):
|
|
45
|
+
"""Raised when a feature is not found."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, feature_id: str):
|
|
48
|
+
self.feature_id = feature_id
|
|
49
|
+
super().__init__(f"Feature not found: {feature_id}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class FeatureValidationError(FeatureRepositoryError):
|
|
53
|
+
"""Raised when feature data fails validation."""
|
|
54
|
+
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class FeatureConcurrencyError(FeatureRepositoryError):
|
|
59
|
+
"""Raised when concurrent modification detected."""
|
|
60
|
+
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class FeatureRepository(ABC):
|
|
65
|
+
"""
|
|
66
|
+
Abstract interface for Feature data access.
|
|
67
|
+
|
|
68
|
+
Unifies access to Features stored in HTML files and SQLite database.
|
|
69
|
+
|
|
70
|
+
CONTRACT:
|
|
71
|
+
1. **Identity Invariant**: get(id) returns same object instance for same feature
|
|
72
|
+
2. **Atomicity**: write operations are atomic (all-or-nothing)
|
|
73
|
+
3. **Consistency**: cache stays in sync with storage
|
|
74
|
+
4. **Isolation**: concurrent operations don't corrupt state
|
|
75
|
+
5. **Error Handling**: all errors preserve full context
|
|
76
|
+
|
|
77
|
+
CACHING BEHAVIOR:
|
|
78
|
+
- Single object instances per feature (identity, not just equality)
|
|
79
|
+
- Automatic cache invalidation on writes
|
|
80
|
+
- Optional auto-load on first access
|
|
81
|
+
|
|
82
|
+
PERFORMANCE:
|
|
83
|
+
- get(id): O(1) cached, O(log n) uncached
|
|
84
|
+
- list(): O(n) where n = features
|
|
85
|
+
- where(**kwargs): O(n) with early termination
|
|
86
|
+
- batch_get(): O(k) where k = batch size
|
|
87
|
+
- batch_update(): O(k) vectorized
|
|
88
|
+
|
|
89
|
+
THREAD SAFETY:
|
|
90
|
+
- Implementations should be thread-safe
|
|
91
|
+
- Concurrent reads allowed
|
|
92
|
+
- Concurrent writes serialized (via database locks or explicit locking)
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
# ===== READ OPERATIONS =====
|
|
96
|
+
|
|
97
|
+
@abstractmethod
|
|
98
|
+
def get(self, feature_id: str) -> Any | None:
|
|
99
|
+
"""
|
|
100
|
+
Get single feature by ID.
|
|
101
|
+
|
|
102
|
+
Returns same object instance for multiple calls with same ID.
|
|
103
|
+
Implements identity caching (is, not ==).
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
feature_id: Feature ID to retrieve (e.g., "feat-abc123")
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Feature object if found, None if not found
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
ValueError: If feature_id is invalid format
|
|
113
|
+
|
|
114
|
+
Performance: O(1) if cached, O(log n) if uncached
|
|
115
|
+
|
|
116
|
+
Examples:
|
|
117
|
+
>>> feature = repo.get("feat-001")
|
|
118
|
+
>>> feature2 = repo.get("feat-001")
|
|
119
|
+
>>> assert feature is feature2 # Identity, not just equality
|
|
120
|
+
>>> assert feature is not None
|
|
121
|
+
"""
|
|
122
|
+
...
|
|
123
|
+
|
|
124
|
+
@abstractmethod
|
|
125
|
+
def list(self, filters: dict[str, Any] | None = None) -> list[Any]:
|
|
126
|
+
"""
|
|
127
|
+
List all features with optional filters.
|
|
128
|
+
|
|
129
|
+
Returns empty list if no matches, never None.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
filters: Optional dict of attribute->value filters.
|
|
133
|
+
Empty/None dict means no filters (returns all).
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
List of Feature objects (empty list if no matches)
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
FeatureValidationError: If filter keys are invalid
|
|
140
|
+
|
|
141
|
+
Performance: O(n) where n = total features
|
|
142
|
+
|
|
143
|
+
Examples:
|
|
144
|
+
>>> all_features = repo.list()
|
|
145
|
+
>>> assert isinstance(all_features, list)
|
|
146
|
+
>>> todo_features = repo.list({"status": "todo"})
|
|
147
|
+
>>> multiple = repo.list({"status": "todo", "priority": "high"})
|
|
148
|
+
"""
|
|
149
|
+
...
|
|
150
|
+
|
|
151
|
+
@abstractmethod
|
|
152
|
+
def where(self, **kwargs: Any) -> RepositoryQuery:
|
|
153
|
+
"""
|
|
154
|
+
Build a filtered query with chaining support.
|
|
155
|
+
|
|
156
|
+
Supports method chaining for composable queries:
|
|
157
|
+
repo.where(status='todo').where(priority='high').execute()
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
**kwargs: Attribute->value filter pairs.
|
|
161
|
+
Common: status, priority, assigned_to, track_id
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
RepositoryQuery object that can be further filtered or executed
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
FeatureValidationError: If invalid attribute names
|
|
168
|
+
|
|
169
|
+
Examples:
|
|
170
|
+
>>> query = repo.where(status='todo')
|
|
171
|
+
>>> query2 = query.where(priority='high') # Chaining
|
|
172
|
+
>>> results = query2.execute()
|
|
173
|
+
>>> assert all(f.status == 'todo' for f in results)
|
|
174
|
+
>>> assert all(f.priority == 'high' for f in results)
|
|
175
|
+
"""
|
|
176
|
+
...
|
|
177
|
+
|
|
178
|
+
@abstractmethod
|
|
179
|
+
def by_track(self, track_id: str) -> builtins.list[Any]:
|
|
180
|
+
"""
|
|
181
|
+
Get all features belonging to a track.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
track_id: Track ID to filter by
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
List of features in track (empty if track has no features)
|
|
188
|
+
|
|
189
|
+
Raises:
|
|
190
|
+
ValueError: If track_id is invalid format
|
|
191
|
+
|
|
192
|
+
Performance: O(n) with early termination on match
|
|
193
|
+
|
|
194
|
+
Examples:
|
|
195
|
+
>>> features = repo.by_track("track-planning")
|
|
196
|
+
>>> assert all(f.track_id == "track-planning" for f in features)
|
|
197
|
+
"""
|
|
198
|
+
...
|
|
199
|
+
|
|
200
|
+
@abstractmethod
|
|
201
|
+
def by_status(self, status: str) -> builtins.list[Any]:
|
|
202
|
+
"""
|
|
203
|
+
Filter features by status.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
status: Status to filter by (e.g., 'todo', 'in-progress', 'done')
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
List of matching features (empty if no matches)
|
|
210
|
+
|
|
211
|
+
Performance: O(n) with early termination
|
|
212
|
+
|
|
213
|
+
Examples:
|
|
214
|
+
>>> done_features = repo.by_status("done")
|
|
215
|
+
>>> active = repo.by_status("in-progress")
|
|
216
|
+
"""
|
|
217
|
+
...
|
|
218
|
+
|
|
219
|
+
@abstractmethod
|
|
220
|
+
def by_priority(self, priority: str) -> builtins.list[Any]:
|
|
221
|
+
"""
|
|
222
|
+
Filter features by priority.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
priority: Priority level (e.g., 'low', 'medium', 'high', 'critical')
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
List of matching features
|
|
229
|
+
|
|
230
|
+
Performance: O(n)
|
|
231
|
+
|
|
232
|
+
Examples:
|
|
233
|
+
>>> critical = repo.by_priority("critical")
|
|
234
|
+
>>> important = repo.by_priority("high")
|
|
235
|
+
"""
|
|
236
|
+
...
|
|
237
|
+
|
|
238
|
+
@abstractmethod
|
|
239
|
+
def by_assigned_to(self, agent: str) -> builtins.list[Any]:
|
|
240
|
+
"""
|
|
241
|
+
Get features assigned to an agent.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
agent: Agent ID (e.g., 'claude', 'gpt4')
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
Features assigned to agent
|
|
248
|
+
|
|
249
|
+
Examples:
|
|
250
|
+
>>> my_work = repo.by_assigned_to("claude")
|
|
251
|
+
"""
|
|
252
|
+
...
|
|
253
|
+
|
|
254
|
+
@abstractmethod
|
|
255
|
+
def batch_get(self, feature_ids: builtins.list[str]) -> builtins.list[Any]:
|
|
256
|
+
"""
|
|
257
|
+
Bulk retrieve multiple features.
|
|
258
|
+
|
|
259
|
+
More efficient than multiple get() calls (vectorized).
|
|
260
|
+
Returns partial results if some features not found.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
feature_ids: List of feature IDs
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
List of found features (in order of input, with None for missing)
|
|
267
|
+
or list of only found features (implementation-dependent)
|
|
268
|
+
|
|
269
|
+
Raises:
|
|
270
|
+
ValueError: If feature_ids is not a list
|
|
271
|
+
|
|
272
|
+
Performance: O(k) where k = batch size
|
|
273
|
+
|
|
274
|
+
Examples:
|
|
275
|
+
>>> ids = ["feat-001", "feat-002", "feat-003"]
|
|
276
|
+
>>> features = repo.batch_get(ids)
|
|
277
|
+
>>> assert len(features) <= len(ids)
|
|
278
|
+
"""
|
|
279
|
+
...
|
|
280
|
+
|
|
281
|
+
# ===== WRITE OPERATIONS =====
|
|
282
|
+
|
|
283
|
+
@abstractmethod
|
|
284
|
+
def create(self, title: str, **kwargs: Any) -> Any:
|
|
285
|
+
"""
|
|
286
|
+
Create new feature.
|
|
287
|
+
|
|
288
|
+
Generates ID if not provided.
|
|
289
|
+
Saves to storage immediately.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
title: Feature title (required)
|
|
293
|
+
**kwargs: Additional properties (priority, status, track_id, etc.)
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
Created Feature object (with generated ID)
|
|
297
|
+
|
|
298
|
+
Raises:
|
|
299
|
+
FeatureValidationError: If invalid data provided
|
|
300
|
+
FeatureRepositoryError: If create fails
|
|
301
|
+
|
|
302
|
+
Performance: O(1) cached write
|
|
303
|
+
|
|
304
|
+
Examples:
|
|
305
|
+
>>> feature = repo.create("User Authentication")
|
|
306
|
+
>>> assert feature.id is not None
|
|
307
|
+
>>> feature2 = repo.create("API Rate Limiting", priority="high")
|
|
308
|
+
"""
|
|
309
|
+
...
|
|
310
|
+
|
|
311
|
+
@abstractmethod
|
|
312
|
+
def save(self, feature: Any) -> Any:
|
|
313
|
+
"""
|
|
314
|
+
Save existing feature (update or insert).
|
|
315
|
+
|
|
316
|
+
If feature.id exists in repo, updates. Otherwise inserts.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
feature: Feature object to save
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
Saved feature (same instance)
|
|
323
|
+
|
|
324
|
+
Raises:
|
|
325
|
+
FeatureValidationError: If feature is invalid
|
|
326
|
+
FeatureConcurrencyError: If feature was modified elsewhere
|
|
327
|
+
|
|
328
|
+
Performance: O(1)
|
|
329
|
+
|
|
330
|
+
Examples:
|
|
331
|
+
>>> feature = repo.get("feat-001")
|
|
332
|
+
>>> feature.status = "in-progress"
|
|
333
|
+
>>> repo.save(feature)
|
|
334
|
+
"""
|
|
335
|
+
...
|
|
336
|
+
|
|
337
|
+
@abstractmethod
|
|
338
|
+
def batch_update(
|
|
339
|
+
self, feature_ids: builtins.list[str], updates: dict[str, Any]
|
|
340
|
+
) -> int:
|
|
341
|
+
"""
|
|
342
|
+
Vectorized batch update operation.
|
|
343
|
+
|
|
344
|
+
Updates all specified features with same values.
|
|
345
|
+
More efficient than individual saves.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
feature_ids: List of feature IDs to update
|
|
349
|
+
updates: Dict of attribute->value to set
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
Number of features successfully updated
|
|
353
|
+
|
|
354
|
+
Raises:
|
|
355
|
+
FeatureValidationError: If invalid updates
|
|
356
|
+
|
|
357
|
+
Performance: O(k) vectorized where k = batch size
|
|
358
|
+
|
|
359
|
+
Examples:
|
|
360
|
+
>>> count = repo.batch_update(
|
|
361
|
+
... ["feat-1", "feat-2", "feat-3"],
|
|
362
|
+
... {"status": "done", "priority": "low"}
|
|
363
|
+
... )
|
|
364
|
+
>>> assert count == 3
|
|
365
|
+
"""
|
|
366
|
+
...
|
|
367
|
+
|
|
368
|
+
@abstractmethod
|
|
369
|
+
def delete(self, feature_id: str) -> bool:
|
|
370
|
+
"""
|
|
371
|
+
Delete a feature by ID.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
feature_id: Feature ID to delete
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
True if deleted, False if not found
|
|
378
|
+
|
|
379
|
+
Raises:
|
|
380
|
+
FeatureValidationError: If feature_id invalid
|
|
381
|
+
|
|
382
|
+
Performance: O(1) cache removal, O(log n) storage deletion
|
|
383
|
+
|
|
384
|
+
Examples:
|
|
385
|
+
>>> success = repo.delete("feat-001")
|
|
386
|
+
>>> assert success is True or success is False
|
|
387
|
+
"""
|
|
388
|
+
...
|
|
389
|
+
|
|
390
|
+
@abstractmethod
|
|
391
|
+
def batch_delete(self, feature_ids: builtins.list[str]) -> int:
|
|
392
|
+
"""
|
|
393
|
+
Delete multiple features.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
feature_ids: List of feature IDs to delete
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
Number of features successfully deleted
|
|
400
|
+
|
|
401
|
+
Raises:
|
|
402
|
+
ValueError: If feature_ids not a list
|
|
403
|
+
|
|
404
|
+
Performance: O(k) where k = batch size
|
|
405
|
+
|
|
406
|
+
Examples:
|
|
407
|
+
>>> count = repo.batch_delete(["feat-1", "feat-2"])
|
|
408
|
+
>>> assert count == 2
|
|
409
|
+
"""
|
|
410
|
+
...
|
|
411
|
+
|
|
412
|
+
# ===== ADVANCED QUERIES =====
|
|
413
|
+
|
|
414
|
+
@abstractmethod
|
|
415
|
+
def find_dependencies(self, feature_id: str) -> builtins.list[Any]:
|
|
416
|
+
"""
|
|
417
|
+
Find transitive feature dependencies.
|
|
418
|
+
|
|
419
|
+
Returns features that MUST be completed before given feature.
|
|
420
|
+
Traverses dependency graph to find all transitive deps.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
feature_id: Feature to find dependencies for
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
List of features this feature depends on (transitive closure)
|
|
427
|
+
|
|
428
|
+
Raises:
|
|
429
|
+
FeatureNotFoundError: If feature not found
|
|
430
|
+
|
|
431
|
+
Performance: O(n) graph traversal
|
|
432
|
+
|
|
433
|
+
Examples:
|
|
434
|
+
>>> deps = repo.find_dependencies("feat-auth")
|
|
435
|
+
>>> # Returns all features that must be done before auth
|
|
436
|
+
>>> assert all(f.id != "feat-auth" for f in deps)
|
|
437
|
+
"""
|
|
438
|
+
...
|
|
439
|
+
|
|
440
|
+
@abstractmethod
|
|
441
|
+
def find_blocking(self, feature_id: str) -> builtins.list[Any]:
|
|
442
|
+
"""
|
|
443
|
+
Find what blocks this feature.
|
|
444
|
+
|
|
445
|
+
Inverse of dependencies: features that depend ON this feature.
|
|
446
|
+
Returns features blocked by given feature.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
feature_id: Feature to find blockers for
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
Features that depend on this feature
|
|
453
|
+
|
|
454
|
+
Raises:
|
|
455
|
+
FeatureNotFoundError: If feature not found
|
|
456
|
+
|
|
457
|
+
Examples:
|
|
458
|
+
>>> blockers = repo.find_blocking("feat-database-migration")
|
|
459
|
+
>>> # Returns all features waiting on this one
|
|
460
|
+
"""
|
|
461
|
+
...
|
|
462
|
+
|
|
463
|
+
@abstractmethod
|
|
464
|
+
def filter(self, predicate: Callable[[Any], bool]) -> builtins.list[Any]:
|
|
465
|
+
"""
|
|
466
|
+
Filter features with custom predicate function.
|
|
467
|
+
|
|
468
|
+
For complex queries not covered by standard filters.
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
predicate: Function that takes Feature and returns True/False
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
Features matching predicate
|
|
475
|
+
|
|
476
|
+
Examples:
|
|
477
|
+
>>> recent = repo.filter(
|
|
478
|
+
... lambda f: (datetime.now() - f.created).days < 7
|
|
479
|
+
... )
|
|
480
|
+
>>> contains_auth = repo.filter(
|
|
481
|
+
... lambda f: "auth" in f.title.lower()
|
|
482
|
+
... )
|
|
483
|
+
"""
|
|
484
|
+
...
|
|
485
|
+
|
|
486
|
+
# ===== CACHE/LIFECYCLE MANAGEMENT =====
|
|
487
|
+
|
|
488
|
+
@abstractmethod
|
|
489
|
+
def invalidate_cache(self, feature_id: str | None = None) -> None:
|
|
490
|
+
"""
|
|
491
|
+
Invalidate cache for single feature or all features.
|
|
492
|
+
|
|
493
|
+
Forces reload from storage on next access.
|
|
494
|
+
Used when external process modifies storage.
|
|
495
|
+
|
|
496
|
+
Args:
|
|
497
|
+
feature_id: Specific feature to invalidate, or None for all
|
|
498
|
+
|
|
499
|
+
Examples:
|
|
500
|
+
>>> repo.invalidate_cache("feat-001") # Single feature
|
|
501
|
+
>>> repo.invalidate_cache() # Clear entire cache
|
|
502
|
+
"""
|
|
503
|
+
...
|
|
504
|
+
|
|
505
|
+
@abstractmethod
|
|
506
|
+
def reload(self) -> None:
|
|
507
|
+
"""
|
|
508
|
+
Force reload all features from storage.
|
|
509
|
+
|
|
510
|
+
Invalidates all caches and reloads from disk/database.
|
|
511
|
+
Useful for external changes or cache reconciliation.
|
|
512
|
+
|
|
513
|
+
Examples:
|
|
514
|
+
>>> repo.reload() # Force refresh from storage
|
|
515
|
+
"""
|
|
516
|
+
...
|
|
517
|
+
|
|
518
|
+
@property
|
|
519
|
+
@abstractmethod
|
|
520
|
+
def auto_load(self) -> bool:
|
|
521
|
+
"""
|
|
522
|
+
Whether auto-loading is enabled.
|
|
523
|
+
|
|
524
|
+
If True, features auto-load on first access.
|
|
525
|
+
If False, manual reload() required.
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
True if auto-loading enabled, False otherwise
|
|
529
|
+
"""
|
|
530
|
+
...
|
|
531
|
+
|
|
532
|
+
@auto_load.setter
|
|
533
|
+
@abstractmethod
|
|
534
|
+
def auto_load(self, enabled: bool) -> None:
|
|
535
|
+
"""
|
|
536
|
+
Enable/disable auto-loading.
|
|
537
|
+
|
|
538
|
+
Args:
|
|
539
|
+
enabled: True to enable auto-load, False to disable
|
|
540
|
+
"""
|
|
541
|
+
...
|
|
542
|
+
|
|
543
|
+
# ===== UTILITY METHODS =====
|
|
544
|
+
|
|
545
|
+
@abstractmethod
|
|
546
|
+
def count(self, filters: dict[str, Any] | None = None) -> int:
|
|
547
|
+
"""
|
|
548
|
+
Count features matching filters.
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
filters: Optional filters (same as list())
|
|
552
|
+
|
|
553
|
+
Returns:
|
|
554
|
+
Number of matching features
|
|
555
|
+
|
|
556
|
+
Performance: O(n) or O(1) if optimized with SQL count
|
|
557
|
+
|
|
558
|
+
Examples:
|
|
559
|
+
>>> total = repo.count()
|
|
560
|
+
>>> todo_count = repo.count({"status": "todo"})
|
|
561
|
+
"""
|
|
562
|
+
...
|
|
563
|
+
|
|
564
|
+
@abstractmethod
|
|
565
|
+
def exists(self, feature_id: str) -> bool:
|
|
566
|
+
"""
|
|
567
|
+
Check if feature exists without loading it.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
feature_id: Feature ID to check
|
|
571
|
+
|
|
572
|
+
Returns:
|
|
573
|
+
True if exists, False otherwise
|
|
574
|
+
|
|
575
|
+
Performance: O(1) if optimized
|
|
576
|
+
|
|
577
|
+
Examples:
|
|
578
|
+
>>> if repo.exists("feat-001"):
|
|
579
|
+
... feature = repo.get("feat-001")
|
|
580
|
+
"""
|
|
581
|
+
...
|