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,620 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FilterService - Unified interface for all filtering operations.
|
|
3
|
+
|
|
4
|
+
Consolidates 39+ filter duplications across CLI, SDK, and Collections.
|
|
5
|
+
Single source of truth for all filter logic.
|
|
6
|
+
|
|
7
|
+
Provides:
|
|
8
|
+
- Atomic filter creation and composition
|
|
9
|
+
- Standard filters (status, priority, assigned_to, dates)
|
|
10
|
+
- Custom predicates and complex queries
|
|
11
|
+
- Filter compilation for performance
|
|
12
|
+
- Boolean combinations (AND, OR, NOT)
|
|
13
|
+
|
|
14
|
+
All implementations MUST pass FilterServiceComplianceTests.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from abc import ABC, abstractmethod
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
from enum import Enum
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FilterOperator(Enum):
|
|
26
|
+
"""Standard filter operators."""
|
|
27
|
+
|
|
28
|
+
EQUALS = "=="
|
|
29
|
+
NOT_EQUALS = "!="
|
|
30
|
+
GREATER_THAN = ">"
|
|
31
|
+
GREATER_EQUAL = ">="
|
|
32
|
+
LESS_THAN = "<"
|
|
33
|
+
LESS_EQUAL = "<="
|
|
34
|
+
IN = "in"
|
|
35
|
+
NOT_IN = "not_in"
|
|
36
|
+
CONTAINS = "contains"
|
|
37
|
+
STARTS_WITH = "starts_with"
|
|
38
|
+
ENDS_WITH = "ends_with"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class FilterLogic(Enum):
|
|
42
|
+
"""Combination logic for multiple filters."""
|
|
43
|
+
|
|
44
|
+
AND = "and"
|
|
45
|
+
OR = "or"
|
|
46
|
+
NOT = "not"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class Filter:
|
|
51
|
+
"""
|
|
52
|
+
Atomic filter with field, operator, and value.
|
|
53
|
+
|
|
54
|
+
Supports:
|
|
55
|
+
- Single field filtering (status == 'todo')
|
|
56
|
+
- Range filtering (priority >= 'high')
|
|
57
|
+
- List membership (assigned_to in ['alice', 'bob'])
|
|
58
|
+
- Custom predicates (lambda f: len(f.title) > 10)
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
field: str # Attribute name or None for custom
|
|
62
|
+
operator: FilterOperator | str # Filter operator
|
|
63
|
+
value: Any # Value to filter by
|
|
64
|
+
predicate: Callable[[Any], bool] | None = None # Custom predicate function
|
|
65
|
+
logic: FilterLogic | None = None # Combination logic (for compound filters)
|
|
66
|
+
sub_filters: list["Filter"] | None = None # For compound filters
|
|
67
|
+
|
|
68
|
+
def __call__(self, item: Any) -> bool:
|
|
69
|
+
"""Apply filter to item. Allows: filter(item) syntax."""
|
|
70
|
+
raise NotImplementedError("Use compiled filter or apply() method")
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def is_custom(self) -> bool:
|
|
74
|
+
"""True if this is a custom predicate filter."""
|
|
75
|
+
return self.predicate is not None
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def is_compound(self) -> bool:
|
|
79
|
+
"""True if this combines multiple filters."""
|
|
80
|
+
return self.logic is not None and self.sub_filters is not None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class FilterServiceError(Exception):
|
|
84
|
+
"""Base exception for filter operations."""
|
|
85
|
+
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class InvalidFilterError(FilterServiceError):
|
|
90
|
+
"""Raised when filter configuration is invalid."""
|
|
91
|
+
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class FilterService(ABC):
|
|
96
|
+
"""
|
|
97
|
+
Unified interface for all filtering operations.
|
|
98
|
+
|
|
99
|
+
Consolidates duplicated filter logic from:
|
|
100
|
+
- CLI commands (filter by status, priority, etc.)
|
|
101
|
+
- SDK collections (multiple filtering methods)
|
|
102
|
+
- Work item queries
|
|
103
|
+
- Analytics recommendations
|
|
104
|
+
|
|
105
|
+
CONTRACT:
|
|
106
|
+
1. **Correctness**: Filters return exactly matching items
|
|
107
|
+
2. **Consistency**: Same filter always returns same results
|
|
108
|
+
3. **Completeness**: All standard filters supported
|
|
109
|
+
4. **Composability**: Filters can be combined safely
|
|
110
|
+
5. **Performance**: Filters compiled once, applied efficiently
|
|
111
|
+
|
|
112
|
+
FILTER TYPES:
|
|
113
|
+
1. **Atomic**: Single field filter (status == 'todo')
|
|
114
|
+
2. **Standard**: Pre-built common filters (status_is, priority_gte, etc.)
|
|
115
|
+
3. **Custom**: User-provided predicates (lambda f: f.title.startswith('API'))
|
|
116
|
+
4. **Compound**: Multiple filters combined (AND, OR, NOT)
|
|
117
|
+
|
|
118
|
+
PERFORMANCE:
|
|
119
|
+
- create_filter(): O(1) validation
|
|
120
|
+
- compile(): O(1) filter->callable conversion
|
|
121
|
+
- apply(): O(n) where n = items
|
|
122
|
+
- apply() with compiled filter: O(n*k) where k = filter complexity
|
|
123
|
+
- apply() with index: O(log n) if available
|
|
124
|
+
|
|
125
|
+
THREAD SAFETY:
|
|
126
|
+
- Filters are immutable after creation
|
|
127
|
+
- Safe to share compiled filters across threads
|
|
128
|
+
- apply() thread-safe on separate item lists
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
# ===== ATOMIC FILTER CREATION =====
|
|
132
|
+
|
|
133
|
+
@abstractmethod
|
|
134
|
+
def create_filter(
|
|
135
|
+
self, field: str, operator: FilterOperator | str, value: Any
|
|
136
|
+
) -> Filter:
|
|
137
|
+
"""
|
|
138
|
+
Create atomic filter for single field.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
field: Attribute name to filter by
|
|
142
|
+
operator: FilterOperator or string operator ("==", "!=", ">", etc.)
|
|
143
|
+
value: Value to compare against
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Filter object ready to apply or combine
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
InvalidFilterError: If operator or field invalid
|
|
150
|
+
|
|
151
|
+
Performance: O(1)
|
|
152
|
+
|
|
153
|
+
Examples:
|
|
154
|
+
>>> filter1 = service.create_filter("status", "==", "todo")
|
|
155
|
+
>>> filter2 = service.create_filter("priority", ">=", "high")
|
|
156
|
+
>>> filter3 = service.create_filter("assigned_to", "in", ["alice", "bob"])
|
|
157
|
+
"""
|
|
158
|
+
...
|
|
159
|
+
|
|
160
|
+
@abstractmethod
|
|
161
|
+
def custom(self, predicate: Callable[[Any], bool]) -> Filter:
|
|
162
|
+
"""
|
|
163
|
+
Create custom filter with arbitrary predicate.
|
|
164
|
+
|
|
165
|
+
For filtering not covered by standard filters.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
predicate: Function taking item, returning True if matches
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Custom Filter object
|
|
172
|
+
|
|
173
|
+
Examples:
|
|
174
|
+
>>> by_length = service.custom(lambda f: len(f.title) > 10)
|
|
175
|
+
>>> by_date = service.custom(lambda f: (datetime.now() - f.created).days < 7)
|
|
176
|
+
>>> by_keyword = service.custom(lambda f: "auth" in f.title.lower())
|
|
177
|
+
"""
|
|
178
|
+
...
|
|
179
|
+
|
|
180
|
+
# ===== STANDARD FILTERS (COMMON PATTERNS) =====
|
|
181
|
+
|
|
182
|
+
@abstractmethod
|
|
183
|
+
def status_is(self, status: str) -> Filter:
|
|
184
|
+
"""
|
|
185
|
+
Filter by exact status match.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
status: Status value ('todo', 'in-progress', 'done', etc.)
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Filter matching items with exact status
|
|
192
|
+
|
|
193
|
+
Examples:
|
|
194
|
+
>>> repo.apply(items, service.status_is("todo"))
|
|
195
|
+
"""
|
|
196
|
+
...
|
|
197
|
+
|
|
198
|
+
@abstractmethod
|
|
199
|
+
def priority_gte(self, priority: str) -> Filter:
|
|
200
|
+
"""
|
|
201
|
+
Filter by priority >= threshold.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
priority: Minimum priority ('low', 'medium', 'high', 'critical')
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Filter matching items with priority >= threshold
|
|
208
|
+
|
|
209
|
+
Examples:
|
|
210
|
+
>>> high_priority = service.priority_gte("high")
|
|
211
|
+
>>> items = repo.apply(all_items, high_priority)
|
|
212
|
+
"""
|
|
213
|
+
...
|
|
214
|
+
|
|
215
|
+
@abstractmethod
|
|
216
|
+
def priority_lte(self, priority: str) -> Filter:
|
|
217
|
+
"""
|
|
218
|
+
Filter by priority <= threshold.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
priority: Maximum priority ('low', 'medium', 'high', 'critical')
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Filter matching items with priority <= threshold
|
|
225
|
+
"""
|
|
226
|
+
...
|
|
227
|
+
|
|
228
|
+
@abstractmethod
|
|
229
|
+
def assigned_to(self, agent: str) -> Filter:
|
|
230
|
+
"""
|
|
231
|
+
Filter by assignment to specific agent.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
agent: Agent ID (e.g., 'claude', 'gpt4')
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Filter matching items assigned to agent
|
|
238
|
+
|
|
239
|
+
Examples:
|
|
240
|
+
>>> my_work = service.assigned_to("claude")
|
|
241
|
+
"""
|
|
242
|
+
...
|
|
243
|
+
|
|
244
|
+
@abstractmethod
|
|
245
|
+
def created_after(self, date: datetime) -> Filter:
|
|
246
|
+
"""
|
|
247
|
+
Filter by creation date after threshold.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
date: Cutoff datetime
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Filter matching items created > date
|
|
254
|
+
|
|
255
|
+
Examples:
|
|
256
|
+
>>> this_week = service.created_after(datetime.now() - timedelta(days=7))
|
|
257
|
+
"""
|
|
258
|
+
...
|
|
259
|
+
|
|
260
|
+
@abstractmethod
|
|
261
|
+
def created_before(self, date: datetime) -> Filter:
|
|
262
|
+
"""
|
|
263
|
+
Filter by creation date before threshold.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
date: Cutoff datetime
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Filter matching items created < date
|
|
270
|
+
"""
|
|
271
|
+
...
|
|
272
|
+
|
|
273
|
+
@abstractmethod
|
|
274
|
+
def updated_after(self, date: datetime) -> Filter:
|
|
275
|
+
"""
|
|
276
|
+
Filter by last update after threshold.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
date: Cutoff datetime
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Filter matching items updated > date
|
|
283
|
+
"""
|
|
284
|
+
...
|
|
285
|
+
|
|
286
|
+
@abstractmethod
|
|
287
|
+
def updated_before(self, date: datetime) -> Filter:
|
|
288
|
+
"""
|
|
289
|
+
Filter by last update before threshold.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
date: Cutoff datetime
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Filter matching items updated < date
|
|
296
|
+
"""
|
|
297
|
+
...
|
|
298
|
+
|
|
299
|
+
@abstractmethod
|
|
300
|
+
def any_of(self, field: str, values: list[Any]) -> Filter:
|
|
301
|
+
"""
|
|
302
|
+
Filter where field value is in set (IN operator).
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
field: Attribute to check
|
|
306
|
+
values: List of acceptable values
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Filter matching items where field in values
|
|
310
|
+
|
|
311
|
+
Examples:
|
|
312
|
+
>>> statuses = service.any_of("status", ["todo", "in-progress"])
|
|
313
|
+
>>> teams = service.any_of("assigned_to", ["team-a", "team-b"])
|
|
314
|
+
"""
|
|
315
|
+
...
|
|
316
|
+
|
|
317
|
+
@abstractmethod
|
|
318
|
+
def none_of(self, field: str, values: list[Any]) -> Filter:
|
|
319
|
+
"""
|
|
320
|
+
Filter where field value is NOT in set (NOT IN operator).
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
field: Attribute to check
|
|
324
|
+
values: List of excluded values
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
Filter matching items where field not in values
|
|
328
|
+
|
|
329
|
+
Examples:
|
|
330
|
+
>>> exclude_done = service.none_of("status", ["done"])
|
|
331
|
+
"""
|
|
332
|
+
...
|
|
333
|
+
|
|
334
|
+
@abstractmethod
|
|
335
|
+
def text_contains(self, field: str, text: str) -> Filter:
|
|
336
|
+
"""
|
|
337
|
+
Filter where text field contains substring.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
field: Attribute to check
|
|
341
|
+
text: Substring to search for
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
Filter matching items where field contains text
|
|
345
|
+
|
|
346
|
+
Examples:
|
|
347
|
+
>>> auth_related = service.text_contains("title", "auth")
|
|
348
|
+
"""
|
|
349
|
+
...
|
|
350
|
+
|
|
351
|
+
@abstractmethod
|
|
352
|
+
def text_starts_with(self, field: str, prefix: str) -> Filter:
|
|
353
|
+
"""
|
|
354
|
+
Filter where text field starts with prefix.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
field: Attribute to check
|
|
358
|
+
prefix: Required prefix
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
Filter matching items where field starts with prefix
|
|
362
|
+
"""
|
|
363
|
+
...
|
|
364
|
+
|
|
365
|
+
@abstractmethod
|
|
366
|
+
def range(
|
|
367
|
+
self, field: str, min_value: Any | None = None, max_value: Any | None = None
|
|
368
|
+
) -> Filter:
|
|
369
|
+
"""
|
|
370
|
+
Filter where numeric field is in range.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
field: Numeric attribute to check
|
|
374
|
+
min_value: Minimum value (inclusive, None = no minimum)
|
|
375
|
+
max_value: Maximum value (inclusive, None = no maximum)
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
Filter matching items in range
|
|
379
|
+
|
|
380
|
+
Examples:
|
|
381
|
+
>>> high_impact = service.range("impact_score", min_value=0.8)
|
|
382
|
+
>>> this_quarter = service.range("priority_score", min_value=0.5, max_value=1.0)
|
|
383
|
+
"""
|
|
384
|
+
...
|
|
385
|
+
|
|
386
|
+
# ===== FILTER COMPOSITION =====
|
|
387
|
+
|
|
388
|
+
@abstractmethod
|
|
389
|
+
def combine(
|
|
390
|
+
self, filters: list[Filter], logic: FilterLogic | str = FilterLogic.AND
|
|
391
|
+
) -> Filter:
|
|
392
|
+
"""
|
|
393
|
+
Combine multiple filters with boolean logic.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
filters: List of Filter objects to combine
|
|
397
|
+
logic: Combination logic ('and', 'or', 'not')
|
|
398
|
+
|
|
399
|
+
Returns:
|
|
400
|
+
Compound Filter that applies all filters
|
|
401
|
+
|
|
402
|
+
Raises:
|
|
403
|
+
InvalidFilterError: If incompatible filters
|
|
404
|
+
|
|
405
|
+
Examples:
|
|
406
|
+
>>> status_todo = service.status_is("todo")
|
|
407
|
+
>>> high_priority = service.priority_gte("high")
|
|
408
|
+
>>> combined = service.combine([status_todo, high_priority])
|
|
409
|
+
>>> important_work = repo.apply(items, combined)
|
|
410
|
+
|
|
411
|
+
>>> recent = service.created_after(datetime.now() - timedelta(days=7))
|
|
412
|
+
>>> exclude_done = service.none_of("status", ["done"])
|
|
413
|
+
>>> recent_active = service.combine([recent, exclude_done])
|
|
414
|
+
"""
|
|
415
|
+
...
|
|
416
|
+
|
|
417
|
+
@abstractmethod
|
|
418
|
+
def all_of(self, *filters: Filter) -> Filter:
|
|
419
|
+
"""
|
|
420
|
+
Shorthand for combine(filters, AND).
|
|
421
|
+
|
|
422
|
+
Filters must ALL match for item to pass.
|
|
423
|
+
|
|
424
|
+
Examples:
|
|
425
|
+
>>> f1 = service.status_is("todo")
|
|
426
|
+
>>> f2 = service.priority_gte("high")
|
|
427
|
+
>>> f3 = service.assigned_to("claude")
|
|
428
|
+
>>> result = service.all_of(f1, f2, f3)
|
|
429
|
+
"""
|
|
430
|
+
...
|
|
431
|
+
|
|
432
|
+
@abstractmethod
|
|
433
|
+
def any(self, *filters: Filter) -> Filter:
|
|
434
|
+
"""
|
|
435
|
+
Shorthand for combine(filters, OR).
|
|
436
|
+
|
|
437
|
+
If ANY filter matches, item passes.
|
|
438
|
+
|
|
439
|
+
Examples:
|
|
440
|
+
>>> f1 = service.status_is("done")
|
|
441
|
+
>>> f2 = service.assigned_to("alice")
|
|
442
|
+
>>> result = service.any(f1, f2) # Done items OR Alice's items
|
|
443
|
+
"""
|
|
444
|
+
...
|
|
445
|
+
|
|
446
|
+
@abstractmethod
|
|
447
|
+
def not_filter(self, filter: Filter) -> Filter:
|
|
448
|
+
"""
|
|
449
|
+
Negate a filter (logical NOT).
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
filter: Filter to negate
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
Filter that matches items NOT matching input filter
|
|
456
|
+
|
|
457
|
+
Examples:
|
|
458
|
+
>>> not_done = service.not_filter(service.status_is("done"))
|
|
459
|
+
>>> not_critical = service.not_filter(service.priority_gte("critical"))
|
|
460
|
+
"""
|
|
461
|
+
...
|
|
462
|
+
|
|
463
|
+
# ===== FILTER VALIDATION & COMPILATION =====
|
|
464
|
+
|
|
465
|
+
@abstractmethod
|
|
466
|
+
def validate(self, filter: Filter) -> bool:
|
|
467
|
+
"""
|
|
468
|
+
Validate filter is well-formed and applicable.
|
|
469
|
+
|
|
470
|
+
Checks:
|
|
471
|
+
- Valid operators
|
|
472
|
+
- Valid field names (if known)
|
|
473
|
+
- Compatible value types
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
filter: Filter to validate
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
True if valid, False if invalid
|
|
480
|
+
|
|
481
|
+
Examples:
|
|
482
|
+
>>> f = service.create_filter("status", "==", "todo")
|
|
483
|
+
>>> assert service.validate(f)
|
|
484
|
+
"""
|
|
485
|
+
...
|
|
486
|
+
|
|
487
|
+
@abstractmethod
|
|
488
|
+
def compile(self, filter: Filter) -> Callable[[Any], bool]:
|
|
489
|
+
"""
|
|
490
|
+
Pre-compile filter to fast callable.
|
|
491
|
+
|
|
492
|
+
Optimization: convert filter to native Python function
|
|
493
|
+
for faster application. Can cache result.
|
|
494
|
+
|
|
495
|
+
Args:
|
|
496
|
+
filter: Filter to compile
|
|
497
|
+
|
|
498
|
+
Returns:
|
|
499
|
+
Callable that takes item and returns True/False
|
|
500
|
+
|
|
501
|
+
Raises:
|
|
502
|
+
InvalidFilterError: If filter invalid
|
|
503
|
+
|
|
504
|
+
Performance: O(1) compilation, O(1) per application
|
|
505
|
+
|
|
506
|
+
Examples:
|
|
507
|
+
>>> f = service.create_filter("status", "==", "todo")
|
|
508
|
+
>>> compiled = service.compile(f)
|
|
509
|
+
>>> matching = [item for item in items if compiled(item)]
|
|
510
|
+
"""
|
|
511
|
+
...
|
|
512
|
+
|
|
513
|
+
# ===== FILTER APPLICATION =====
|
|
514
|
+
|
|
515
|
+
@abstractmethod
|
|
516
|
+
def apply(
|
|
517
|
+
self, items: list[Any], filter: Filter, limit: int | None = None
|
|
518
|
+
) -> list[Any]:
|
|
519
|
+
"""
|
|
520
|
+
Apply filter to item list.
|
|
521
|
+
|
|
522
|
+
Args:
|
|
523
|
+
items: List of items to filter
|
|
524
|
+
filter: Filter to apply
|
|
525
|
+
limit: Max results to return (None = all)
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
List of items matching filter
|
|
529
|
+
|
|
530
|
+
Performance: O(n) worst case, O(k) best case with early termination
|
|
531
|
+
|
|
532
|
+
Examples:
|
|
533
|
+
>>> todo_items = service.apply(all_items, service.status_is("todo"))
|
|
534
|
+
>>> top_10 = service.apply(all_items, service.priority_gte("high"), limit=10)
|
|
535
|
+
"""
|
|
536
|
+
...
|
|
537
|
+
|
|
538
|
+
@abstractmethod
|
|
539
|
+
def apply_compiled(
|
|
540
|
+
self,
|
|
541
|
+
items: list[Any],
|
|
542
|
+
compiled_filter: Callable[[Any], bool],
|
|
543
|
+
limit: int | None = None,
|
|
544
|
+
) -> list[Any]:
|
|
545
|
+
"""
|
|
546
|
+
Apply pre-compiled filter to items.
|
|
547
|
+
|
|
548
|
+
More efficient than apply() when using same filter multiple times.
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
items: List of items to filter
|
|
552
|
+
compiled_filter: Pre-compiled filter from compile()
|
|
553
|
+
limit: Max results (None = all)
|
|
554
|
+
|
|
555
|
+
Returns:
|
|
556
|
+
List of matching items
|
|
557
|
+
|
|
558
|
+
Performance: O(n) with minimal overhead
|
|
559
|
+
|
|
560
|
+
Examples:
|
|
561
|
+
>>> compiled = service.compile(service.status_is("todo"))
|
|
562
|
+
>>> batch1 = service.apply_compiled(items1, compiled)
|
|
563
|
+
>>> batch2 = service.apply_compiled(items2, compiled)
|
|
564
|
+
"""
|
|
565
|
+
...
|
|
566
|
+
|
|
567
|
+
@abstractmethod
|
|
568
|
+
def filter_count(self, items: list[Any], filter: Filter) -> int:
|
|
569
|
+
"""
|
|
570
|
+
Count items matching filter without materializing list.
|
|
571
|
+
|
|
572
|
+
Args:
|
|
573
|
+
items: List of items to count
|
|
574
|
+
filter: Filter to apply
|
|
575
|
+
|
|
576
|
+
Returns:
|
|
577
|
+
Number of matching items
|
|
578
|
+
|
|
579
|
+
Performance: O(n) but avoids list allocation
|
|
580
|
+
|
|
581
|
+
Examples:
|
|
582
|
+
>>> todo_count = service.filter_count(items, service.status_is("todo"))
|
|
583
|
+
"""
|
|
584
|
+
...
|
|
585
|
+
|
|
586
|
+
# ===== UTILITY METHODS =====
|
|
587
|
+
|
|
588
|
+
@abstractmethod
|
|
589
|
+
def describe(self, filter: Filter) -> str:
|
|
590
|
+
"""
|
|
591
|
+
Get human-readable description of filter.
|
|
592
|
+
|
|
593
|
+
Useful for logging, UI display, debugging.
|
|
594
|
+
|
|
595
|
+
Args:
|
|
596
|
+
filter: Filter to describe
|
|
597
|
+
|
|
598
|
+
Returns:
|
|
599
|
+
Human-readable string
|
|
600
|
+
|
|
601
|
+
Examples:
|
|
602
|
+
>>> f = service.create_filter("status", "==", "todo")
|
|
603
|
+
>>> print(service.describe(f)) # "status is 'todo'"
|
|
604
|
+
"""
|
|
605
|
+
...
|
|
606
|
+
|
|
607
|
+
@abstractmethod
|
|
608
|
+
def get_standard_filters(self) -> dict[str, Callable]:
|
|
609
|
+
"""
|
|
610
|
+
Get all available standard filters.
|
|
611
|
+
|
|
612
|
+
Returns:
|
|
613
|
+
Dict of filter_name -> filter_factory_method
|
|
614
|
+
|
|
615
|
+
Examples:
|
|
616
|
+
>>> filters = service.get_standard_filters()
|
|
617
|
+
>>> for name in filters.keys():
|
|
618
|
+
... print(f"Available: {name}")
|
|
619
|
+
"""
|
|
620
|
+
...
|