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
htmlgraph/cigs/models.py
ADDED
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CIGS Data Models - Violation tracking and cost analysis.
|
|
3
|
+
|
|
4
|
+
Provides comprehensive data structures for tracking delegation violations,
|
|
5
|
+
pattern detection, autonomy management, and cost accounting in the
|
|
6
|
+
Computational Imperative Guidance System.
|
|
7
|
+
|
|
8
|
+
Classes:
|
|
9
|
+
- ViolationType: Enum of violation categories
|
|
10
|
+
- ViolationRecord: Single violation with context and cost impact
|
|
11
|
+
- SessionViolationSummary: Aggregated session metrics
|
|
12
|
+
- PatternRecord: Detected behavioral patterns
|
|
13
|
+
- AutonomyLevel: Agent autonomy recommendations
|
|
14
|
+
- CostMetrics: Token cost analysis per session
|
|
15
|
+
- TokenCost: Per-operation token costs
|
|
16
|
+
- CostPrediction: Cost projection for operations
|
|
17
|
+
- OperationClassification: Tool operation classification
|
|
18
|
+
|
|
19
|
+
Design Reference:
|
|
20
|
+
Part 3, Section 3.1 of computational-imperative-guidance-system-design.md
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
from dataclasses import asdict, dataclass, field
|
|
25
|
+
from datetime import datetime
|
|
26
|
+
from enum import Enum
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ViolationType(Enum):
|
|
30
|
+
"""Types of delegation violations.
|
|
31
|
+
|
|
32
|
+
Categories:
|
|
33
|
+
- DIRECT_EXPLORATION: Read/Grep/Glob when should delegate to spawn_gemini()
|
|
34
|
+
- DIRECT_IMPLEMENTATION: Edit/Write when should delegate to spawn_codex()
|
|
35
|
+
- DIRECT_TESTING: pytest/npm test directly instead of via Task()
|
|
36
|
+
- DIRECT_GIT: git commands directly instead of via spawn_copilot()
|
|
37
|
+
- EXPLORATION_SEQUENCE: 3+ exploration tools in sequence (indicates research work)
|
|
38
|
+
- IGNORED_WARNING: Proceeded after imperative warning from PreToolUse hook
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
DIRECT_EXPLORATION = "direct_exploration"
|
|
42
|
+
DIRECT_IMPLEMENTATION = "direct_implementation"
|
|
43
|
+
DIRECT_TESTING = "direct_testing"
|
|
44
|
+
DIRECT_GIT = "direct_git"
|
|
45
|
+
EXPLORATION_SEQUENCE = "exploration_sequence"
|
|
46
|
+
IGNORED_WARNING = "ignored_warning"
|
|
47
|
+
|
|
48
|
+
def __str__(self) -> str:
|
|
49
|
+
"""Return human-readable violation type name."""
|
|
50
|
+
names = {
|
|
51
|
+
ViolationType.DIRECT_EXPLORATION: "Direct Exploration",
|
|
52
|
+
ViolationType.DIRECT_IMPLEMENTATION: "Direct Implementation",
|
|
53
|
+
ViolationType.DIRECT_TESTING: "Direct Testing",
|
|
54
|
+
ViolationType.DIRECT_GIT: "Direct Git",
|
|
55
|
+
ViolationType.EXPLORATION_SEQUENCE: "Exploration Sequence",
|
|
56
|
+
ViolationType.IGNORED_WARNING: "Ignored Warning",
|
|
57
|
+
}
|
|
58
|
+
return names.get(self, self.value)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class ViolationRecord:
|
|
63
|
+
"""Record of a single delegation violation.
|
|
64
|
+
|
|
65
|
+
Tracks a violation event including the tool used, context, cost impact,
|
|
66
|
+
and escalation level. Used for session analytics and pattern detection.
|
|
67
|
+
|
|
68
|
+
Attributes:
|
|
69
|
+
id: Unique violation ID (e.g., "viol-001")
|
|
70
|
+
session_id: Session where violation occurred
|
|
71
|
+
timestamp: When violation was recorded
|
|
72
|
+
tool: Tool name that was used directly (Read, Grep, Edit, etc.)
|
|
73
|
+
tool_params: Parameters passed to tool for context
|
|
74
|
+
violation_type: Category of violation (DIRECT_EXPLORATION, etc.)
|
|
75
|
+
context_before: Description of what Claude was trying to accomplish
|
|
76
|
+
should_have_delegated_to: Recommended delegation target (spawn_gemini, Task, etc.)
|
|
77
|
+
actual_cost_tokens: Tokens consumed by direct execution
|
|
78
|
+
optimal_cost_tokens: Tokens if delegated properly
|
|
79
|
+
waste_tokens: Difference (actual - optimal)
|
|
80
|
+
warning_level: Escalation level (1=first, 2=second, 3=circuit_breaker)
|
|
81
|
+
was_warned: Whether PreToolUse hook warned before execution
|
|
82
|
+
warning_ignored: Whether Claude proceeded despite warning
|
|
83
|
+
agent: Agent that caused violation (default: "claude-code")
|
|
84
|
+
feature_id: Feature ID if violation occurred during feature work
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
id: str
|
|
88
|
+
session_id: str
|
|
89
|
+
timestamp: datetime
|
|
90
|
+
tool: str
|
|
91
|
+
tool_params: dict
|
|
92
|
+
violation_type: ViolationType
|
|
93
|
+
|
|
94
|
+
context_before: str | None = None
|
|
95
|
+
should_have_delegated_to: str = ""
|
|
96
|
+
actual_cost_tokens: int = 0
|
|
97
|
+
optimal_cost_tokens: int = 0
|
|
98
|
+
waste_tokens: int = 0
|
|
99
|
+
|
|
100
|
+
warning_level: int = 1
|
|
101
|
+
was_warned: bool = False
|
|
102
|
+
warning_ignored: bool = False
|
|
103
|
+
|
|
104
|
+
agent: str = "claude-code"
|
|
105
|
+
feature_id: str | None = None
|
|
106
|
+
|
|
107
|
+
def to_dict(self) -> dict:
|
|
108
|
+
"""Convert to dictionary, handling enum and datetime serialization."""
|
|
109
|
+
return {
|
|
110
|
+
"id": self.id,
|
|
111
|
+
"session_id": self.session_id,
|
|
112
|
+
"timestamp": self.timestamp.isoformat(),
|
|
113
|
+
"tool": self.tool,
|
|
114
|
+
"tool_params": self.tool_params,
|
|
115
|
+
"violation_type": self.violation_type.value,
|
|
116
|
+
"context_before": self.context_before,
|
|
117
|
+
"should_have_delegated_to": self.should_have_delegated_to,
|
|
118
|
+
"actual_cost_tokens": self.actual_cost_tokens,
|
|
119
|
+
"optimal_cost_tokens": self.optimal_cost_tokens,
|
|
120
|
+
"waste_tokens": self.waste_tokens,
|
|
121
|
+
"warning_level": self.warning_level,
|
|
122
|
+
"was_warned": self.was_warned,
|
|
123
|
+
"warning_ignored": self.warning_ignored,
|
|
124
|
+
"agent": self.agent,
|
|
125
|
+
"feature_id": self.feature_id,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
def to_json(self) -> str:
|
|
129
|
+
"""Convert to JSON string."""
|
|
130
|
+
return json.dumps(self.to_dict())
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def from_dict(cls, data: dict) -> "ViolationRecord":
|
|
134
|
+
"""Create from dictionary, handling enum and datetime deserialization."""
|
|
135
|
+
data = data.copy()
|
|
136
|
+
data["timestamp"] = (
|
|
137
|
+
datetime.fromisoformat(data["timestamp"])
|
|
138
|
+
if isinstance(data["timestamp"], str)
|
|
139
|
+
else data["timestamp"]
|
|
140
|
+
)
|
|
141
|
+
data["violation_type"] = ViolationType(data["violation_type"])
|
|
142
|
+
return cls(**data)
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def from_json(cls, json_str: str) -> "ViolationRecord":
|
|
146
|
+
"""Create from JSON string."""
|
|
147
|
+
return cls.from_dict(json.loads(json_str))
|
|
148
|
+
|
|
149
|
+
def __str__(self) -> str:
|
|
150
|
+
"""Human-readable representation."""
|
|
151
|
+
return (
|
|
152
|
+
f"Violation({self.id}): {self.tool} for {self.violation_type}\n"
|
|
153
|
+
f" Context: {self.context_before}\n"
|
|
154
|
+
f" Waste: {self.waste_tokens} tokens (actual: {self.actual_cost_tokens}, "
|
|
155
|
+
f"optimal: {self.optimal_cost_tokens})\n"
|
|
156
|
+
f" Warning level: {self.warning_level}, Warned: {self.was_warned}"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def validate(self) -> tuple[bool, str]:
|
|
160
|
+
"""Validate violation record integrity.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Tuple of (is_valid, error_message)
|
|
164
|
+
"""
|
|
165
|
+
if not self.id:
|
|
166
|
+
return False, "id cannot be empty"
|
|
167
|
+
if not self.session_id:
|
|
168
|
+
return False, "session_id cannot be empty"
|
|
169
|
+
if not self.tool:
|
|
170
|
+
return False, "tool cannot be empty"
|
|
171
|
+
if self.actual_cost_tokens < 0:
|
|
172
|
+
return False, "actual_cost_tokens cannot be negative"
|
|
173
|
+
if self.optimal_cost_tokens < 0:
|
|
174
|
+
return False, "optimal_cost_tokens cannot be negative"
|
|
175
|
+
if self.waste_tokens != (self.actual_cost_tokens - self.optimal_cost_tokens):
|
|
176
|
+
return False, "waste_tokens must equal actual_cost - optimal_cost"
|
|
177
|
+
if not (1 <= self.warning_level <= 3):
|
|
178
|
+
return False, "warning_level must be 1, 2, or 3"
|
|
179
|
+
if self.was_warned and self.warning_ignored:
|
|
180
|
+
if self.warning_level < 2:
|
|
181
|
+
return False, "warning_ignored requires warning_level >= 2"
|
|
182
|
+
return True, ""
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@dataclass
|
|
186
|
+
class SessionViolationSummary:
|
|
187
|
+
"""Summary of violations for a single session.
|
|
188
|
+
|
|
189
|
+
Aggregates all violations that occurred during a session with metrics
|
|
190
|
+
for compliance rate, cost efficiency, and pattern analysis.
|
|
191
|
+
|
|
192
|
+
Attributes:
|
|
193
|
+
session_id: Session identifier
|
|
194
|
+
total_violations: Total violation count for session
|
|
195
|
+
violations_by_type: Count per violation type
|
|
196
|
+
total_waste_tokens: Sum of waste_tokens across all violations
|
|
197
|
+
circuit_breaker_triggered: Whether circuit breaker activated (>= 3 violations)
|
|
198
|
+
compliance_rate: Delegation compliance as float 0.0-1.0
|
|
199
|
+
violations: List of individual violation records
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
session_id: str
|
|
203
|
+
total_violations: int
|
|
204
|
+
violations_by_type: dict[ViolationType, int]
|
|
205
|
+
total_waste_tokens: int
|
|
206
|
+
circuit_breaker_triggered: bool
|
|
207
|
+
compliance_rate: float
|
|
208
|
+
violations: list[ViolationRecord] = field(default_factory=list)
|
|
209
|
+
|
|
210
|
+
def summary(self) -> str:
|
|
211
|
+
"""Return human-readable summary text.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Formatted summary with key metrics
|
|
215
|
+
"""
|
|
216
|
+
breaker_status = "YES 🚨" if self.circuit_breaker_triggered else "No"
|
|
217
|
+
|
|
218
|
+
violations_detail = ""
|
|
219
|
+
for vtype, count in self.violations_by_type.items():
|
|
220
|
+
violations_detail += f" • {vtype}: {count}\n"
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
f"Session {self.session_id}\n"
|
|
224
|
+
f"─────────────────────────────────────\n"
|
|
225
|
+
f"Total Violations: {self.total_violations}\n"
|
|
226
|
+
f"\nViolation Breakdown:\n{violations_detail}"
|
|
227
|
+
f"Total Waste: {self.total_waste_tokens} tokens\n"
|
|
228
|
+
f"Compliance Rate: {self.compliance_rate:.1%}\n"
|
|
229
|
+
f"Circuit Breaker: {breaker_status}"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
def to_dict(self) -> dict:
|
|
233
|
+
"""Convert to dictionary."""
|
|
234
|
+
return {
|
|
235
|
+
"session_id": self.session_id,
|
|
236
|
+
"total_violations": self.total_violations,
|
|
237
|
+
"violations_by_type": {
|
|
238
|
+
k.value: v for k, v in self.violations_by_type.items()
|
|
239
|
+
},
|
|
240
|
+
"total_waste_tokens": self.total_waste_tokens,
|
|
241
|
+
"circuit_breaker_triggered": self.circuit_breaker_triggered,
|
|
242
|
+
"compliance_rate": self.compliance_rate,
|
|
243
|
+
"violations": [v.to_dict() for v in self.violations],
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
def to_json(self) -> str:
|
|
247
|
+
"""Convert to JSON string."""
|
|
248
|
+
return json.dumps(self.to_dict())
|
|
249
|
+
|
|
250
|
+
@classmethod
|
|
251
|
+
def from_dict(cls, data: dict) -> "SessionViolationSummary":
|
|
252
|
+
"""Create from dictionary."""
|
|
253
|
+
data = data.copy()
|
|
254
|
+
data["violations_by_type"] = {
|
|
255
|
+
ViolationType(k): v for k, v in data.get("violations_by_type", {}).items()
|
|
256
|
+
}
|
|
257
|
+
data["violations"] = [
|
|
258
|
+
ViolationRecord.from_dict(v) for v in data.get("violations", [])
|
|
259
|
+
]
|
|
260
|
+
return cls(**data)
|
|
261
|
+
|
|
262
|
+
@classmethod
|
|
263
|
+
def from_json(cls, json_str: str) -> "SessionViolationSummary":
|
|
264
|
+
"""Create from JSON string."""
|
|
265
|
+
return cls.from_dict(json.loads(json_str))
|
|
266
|
+
|
|
267
|
+
def __str__(self) -> str:
|
|
268
|
+
"""Return summary representation."""
|
|
269
|
+
return (
|
|
270
|
+
f"SessionViolationSummary({self.session_id}): "
|
|
271
|
+
f"{self.total_violations} violations, "
|
|
272
|
+
f"{self.compliance_rate:.0%} compliant, "
|
|
273
|
+
f"waste: {self.total_waste_tokens} tokens"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
def validate(self) -> tuple[bool, str]:
|
|
277
|
+
"""Validate summary integrity.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
Tuple of (is_valid, error_message)
|
|
281
|
+
"""
|
|
282
|
+
if not self.session_id:
|
|
283
|
+
return False, "session_id cannot be empty"
|
|
284
|
+
if self.total_violations < 0:
|
|
285
|
+
return False, "total_violations cannot be negative"
|
|
286
|
+
if not (0.0 <= self.compliance_rate <= 1.0):
|
|
287
|
+
return False, "compliance_rate must be between 0.0 and 1.0"
|
|
288
|
+
if self.total_waste_tokens < 0:
|
|
289
|
+
return False, "total_waste_tokens cannot be negative"
|
|
290
|
+
if self.circuit_breaker_triggered and self.total_violations < 3:
|
|
291
|
+
return False, "circuit_breaker_triggered requires >= 3 violations"
|
|
292
|
+
|
|
293
|
+
# Validate count sum matches total
|
|
294
|
+
count_sum = sum(self.violations_by_type.values())
|
|
295
|
+
if count_sum != self.total_violations:
|
|
296
|
+
return (
|
|
297
|
+
False,
|
|
298
|
+
f"violations_by_type sum ({count_sum}) != total_violations "
|
|
299
|
+
f"({self.total_violations})",
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
return True, ""
|
|
303
|
+
|
|
304
|
+
@property
|
|
305
|
+
def count(self) -> int:
|
|
306
|
+
"""Total violation count."""
|
|
307
|
+
return self.total_violations
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@dataclass
|
|
311
|
+
class TokenCost:
|
|
312
|
+
"""Token cost breakdown for an operation or session.
|
|
313
|
+
|
|
314
|
+
Provides granular token accounting for cost analysis and efficiency
|
|
315
|
+
calculation.
|
|
316
|
+
|
|
317
|
+
Attributes:
|
|
318
|
+
total_tokens: Total tokens consumed
|
|
319
|
+
orchestrator_tokens: Tokens in orchestrator/main agent context
|
|
320
|
+
subagent_tokens: Tokens consumed by delegated subagents
|
|
321
|
+
estimated_savings: Estimated tokens saved via delegation
|
|
322
|
+
"""
|
|
323
|
+
|
|
324
|
+
total_tokens: int
|
|
325
|
+
orchestrator_tokens: int
|
|
326
|
+
subagent_tokens: int
|
|
327
|
+
estimated_savings: int = 0
|
|
328
|
+
|
|
329
|
+
def to_dict(self) -> dict:
|
|
330
|
+
"""Convert to dictionary."""
|
|
331
|
+
return asdict(self)
|
|
332
|
+
|
|
333
|
+
def to_json(self) -> str:
|
|
334
|
+
"""Convert to JSON string."""
|
|
335
|
+
return json.dumps(self.to_dict())
|
|
336
|
+
|
|
337
|
+
@classmethod
|
|
338
|
+
def from_dict(cls, data: dict) -> "TokenCost":
|
|
339
|
+
"""Create from dictionary."""
|
|
340
|
+
return cls(**data)
|
|
341
|
+
|
|
342
|
+
@classmethod
|
|
343
|
+
def from_json(cls, json_str: str) -> "TokenCost":
|
|
344
|
+
"""Create from JSON string."""
|
|
345
|
+
return cls.from_dict(json.loads(json_str))
|
|
346
|
+
|
|
347
|
+
def __str__(self) -> str:
|
|
348
|
+
"""Human-readable representation."""
|
|
349
|
+
return (
|
|
350
|
+
f"TokenCost: {self.total_tokens} total\n"
|
|
351
|
+
f" Orchestrator: {self.orchestrator_tokens}\n"
|
|
352
|
+
f" Subagents: {self.subagent_tokens}\n"
|
|
353
|
+
f" Estimated savings: {self.estimated_savings}"
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
def validate(self) -> tuple[bool, str]:
|
|
357
|
+
"""Validate token cost integrity.
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
Tuple of (is_valid, error_message)
|
|
361
|
+
"""
|
|
362
|
+
if self.total_tokens < 0:
|
|
363
|
+
return False, "total_tokens cannot be negative"
|
|
364
|
+
if self.orchestrator_tokens < 0:
|
|
365
|
+
return False, "orchestrator_tokens cannot be negative"
|
|
366
|
+
if self.subagent_tokens < 0:
|
|
367
|
+
return False, "subagent_tokens cannot be negative"
|
|
368
|
+
if self.estimated_savings < 0:
|
|
369
|
+
return False, "estimated_savings cannot be negative"
|
|
370
|
+
if (self.orchestrator_tokens + self.subagent_tokens) > self.total_tokens:
|
|
371
|
+
return (
|
|
372
|
+
False,
|
|
373
|
+
"orchestrator_tokens + subagent_tokens cannot exceed total_tokens",
|
|
374
|
+
)
|
|
375
|
+
return True, ""
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
@dataclass
|
|
379
|
+
class CostPrediction:
|
|
380
|
+
"""Prediction of cost impact for an operation.
|
|
381
|
+
|
|
382
|
+
Used by PreToolUse hook to estimate token cost of direct execution vs
|
|
383
|
+
optimal delegation approach.
|
|
384
|
+
|
|
385
|
+
Attributes:
|
|
386
|
+
should_delegate: Whether operation should be delegated
|
|
387
|
+
optimal_cost: Predicted cost if delegated
|
|
388
|
+
waste_percentage: Estimated waste as percentage
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
should_delegate: bool
|
|
392
|
+
optimal_cost: int
|
|
393
|
+
waste_percentage: float
|
|
394
|
+
|
|
395
|
+
def to_dict(self) -> dict:
|
|
396
|
+
"""Convert to dictionary."""
|
|
397
|
+
return asdict(self)
|
|
398
|
+
|
|
399
|
+
def to_json(self) -> str:
|
|
400
|
+
"""Convert to JSON string."""
|
|
401
|
+
return json.dumps(self.to_dict())
|
|
402
|
+
|
|
403
|
+
@classmethod
|
|
404
|
+
def from_dict(cls, data: dict) -> "CostPrediction":
|
|
405
|
+
"""Create from dictionary."""
|
|
406
|
+
return cls(**data)
|
|
407
|
+
|
|
408
|
+
@classmethod
|
|
409
|
+
def from_json(cls, json_str: str) -> "CostPrediction":
|
|
410
|
+
"""Create from JSON string."""
|
|
411
|
+
return cls.from_dict(json.loads(json_str))
|
|
412
|
+
|
|
413
|
+
def __str__(self) -> str:
|
|
414
|
+
"""Human-readable representation."""
|
|
415
|
+
return (
|
|
416
|
+
f"CostPrediction: Should delegate: {self.should_delegate}\n"
|
|
417
|
+
f" Optimal cost: {self.optimal_cost} tokens\n"
|
|
418
|
+
f" Waste if direct: {self.waste_percentage:.1f}%"
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
def validate(self) -> tuple[bool, str]:
|
|
422
|
+
"""Validate cost prediction integrity.
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
Tuple of (is_valid, error_message)
|
|
426
|
+
"""
|
|
427
|
+
if self.optimal_cost < 0:
|
|
428
|
+
return False, "optimal_cost cannot be negative"
|
|
429
|
+
if not (0.0 <= self.waste_percentage <= 100.0):
|
|
430
|
+
return False, "waste_percentage must be between 0 and 100"
|
|
431
|
+
return True, ""
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
@dataclass
|
|
435
|
+
class OperationClassification:
|
|
436
|
+
"""Classification of a tool operation for delegation decisions.
|
|
437
|
+
|
|
438
|
+
Used by PreToolUse hook to classify operations and determine if delegation
|
|
439
|
+
is required. Combines tool category with pattern analysis.
|
|
440
|
+
|
|
441
|
+
Attributes:
|
|
442
|
+
tool: Tool name (Read, Edit, Bash, etc.)
|
|
443
|
+
category: Operation category (exploration, implementation, etc.)
|
|
444
|
+
should_delegate: Whether operation requires delegation
|
|
445
|
+
reason: Explanation for classification
|
|
446
|
+
is_exploration_sequence: Whether this is part of multi-operation sequence
|
|
447
|
+
suggested_delegation: Recommended delegation target
|
|
448
|
+
predicted_cost: Predicted tokens for direct execution
|
|
449
|
+
optimal_cost: Tokens if delegated
|
|
450
|
+
waste_percentage: Waste as percentage
|
|
451
|
+
"""
|
|
452
|
+
|
|
453
|
+
tool: str
|
|
454
|
+
category: str
|
|
455
|
+
should_delegate: bool
|
|
456
|
+
reason: str
|
|
457
|
+
is_exploration_sequence: bool
|
|
458
|
+
suggested_delegation: str
|
|
459
|
+
|
|
460
|
+
predicted_cost: int = 0
|
|
461
|
+
optimal_cost: int = 0
|
|
462
|
+
waste_percentage: float = 0.0
|
|
463
|
+
|
|
464
|
+
VALID_CATEGORIES = {
|
|
465
|
+
"exploration",
|
|
466
|
+
"implementation",
|
|
467
|
+
"testing",
|
|
468
|
+
"git",
|
|
469
|
+
"allowed",
|
|
470
|
+
"edge_case",
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
def to_dict(self) -> dict:
|
|
474
|
+
"""Convert to dictionary."""
|
|
475
|
+
return asdict(self)
|
|
476
|
+
|
|
477
|
+
def to_json(self) -> str:
|
|
478
|
+
"""Convert to JSON string."""
|
|
479
|
+
return json.dumps(self.to_dict())
|
|
480
|
+
|
|
481
|
+
@classmethod
|
|
482
|
+
def from_dict(cls, data: dict) -> "OperationClassification":
|
|
483
|
+
"""Create from dictionary."""
|
|
484
|
+
return cls(**data)
|
|
485
|
+
|
|
486
|
+
@classmethod
|
|
487
|
+
def from_json(cls, json_str: str) -> "OperationClassification":
|
|
488
|
+
"""Create from JSON string."""
|
|
489
|
+
return cls.from_dict(json.loads(json_str))
|
|
490
|
+
|
|
491
|
+
def __str__(self) -> str:
|
|
492
|
+
"""Human-readable representation."""
|
|
493
|
+
return (
|
|
494
|
+
f"OperationClassification: {self.tool} ({self.category})\n"
|
|
495
|
+
f" Should delegate: {self.should_delegate}\n"
|
|
496
|
+
f" Reason: {self.reason}\n"
|
|
497
|
+
f" Suggested: {self.suggested_delegation}\n"
|
|
498
|
+
f" Cost: {self.predicted_cost} → {self.optimal_cost} "
|
|
499
|
+
f"({self.waste_percentage:.1f}% waste)"
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
def validate(self) -> tuple[bool, str]:
|
|
503
|
+
"""Validate operation classification integrity.
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
Tuple of (is_valid, error_message)
|
|
507
|
+
"""
|
|
508
|
+
if not self.tool:
|
|
509
|
+
return False, "tool cannot be empty"
|
|
510
|
+
if self.category not in self.VALID_CATEGORIES:
|
|
511
|
+
return False, f"category must be one of {self.VALID_CATEGORIES}"
|
|
512
|
+
if not self.reason:
|
|
513
|
+
return False, "reason cannot be empty"
|
|
514
|
+
if not self.suggested_delegation:
|
|
515
|
+
return False, "suggested_delegation cannot be empty"
|
|
516
|
+
if self.predicted_cost < 0:
|
|
517
|
+
return False, "predicted_cost cannot be negative"
|
|
518
|
+
if self.optimal_cost < 0:
|
|
519
|
+
return False, "optimal_cost cannot be negative"
|
|
520
|
+
if not (0.0 <= self.waste_percentage <= 100.0):
|
|
521
|
+
return False, "waste_percentage must be between 0 and 100"
|
|
522
|
+
return True, ""
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
@dataclass
|
|
526
|
+
class PatternRecord:
|
|
527
|
+
"""Record of a detected behavioral pattern.
|
|
528
|
+
|
|
529
|
+
Tracks identified patterns (both good patterns and anti-patterns) for
|
|
530
|
+
learning and customizing guidance messages.
|
|
531
|
+
|
|
532
|
+
Attributes:
|
|
533
|
+
id: Unique pattern ID
|
|
534
|
+
pattern_type: "anti-pattern" or "good-pattern"
|
|
535
|
+
name: Human-readable pattern name
|
|
536
|
+
description: What the pattern represents
|
|
537
|
+
trigger_conditions: List of conditions that activate this pattern
|
|
538
|
+
example_sequence: Example tool sequence that triggers pattern
|
|
539
|
+
occurrence_count: How many times detected
|
|
540
|
+
sessions_affected: Sessions where pattern was detected
|
|
541
|
+
correct_approach: Recommended fix (for anti-patterns)
|
|
542
|
+
delegation_suggestion: What to delegate to instead
|
|
543
|
+
"""
|
|
544
|
+
|
|
545
|
+
id: str
|
|
546
|
+
pattern_type: str
|
|
547
|
+
name: str
|
|
548
|
+
description: str
|
|
549
|
+
trigger_conditions: list[str]
|
|
550
|
+
example_sequence: list[str]
|
|
551
|
+
|
|
552
|
+
occurrence_count: int = 0
|
|
553
|
+
sessions_affected: list[str] = field(default_factory=list)
|
|
554
|
+
|
|
555
|
+
correct_approach: str | None = None
|
|
556
|
+
delegation_suggestion: str | None = None
|
|
557
|
+
|
|
558
|
+
def to_dict(self) -> dict:
|
|
559
|
+
"""Convert to dictionary."""
|
|
560
|
+
return asdict(self)
|
|
561
|
+
|
|
562
|
+
def to_json(self) -> str:
|
|
563
|
+
"""Convert to JSON string."""
|
|
564
|
+
return json.dumps(self.to_dict())
|
|
565
|
+
|
|
566
|
+
@classmethod
|
|
567
|
+
def from_dict(cls, data: dict) -> "PatternRecord":
|
|
568
|
+
"""Create from dictionary."""
|
|
569
|
+
return cls(**data)
|
|
570
|
+
|
|
571
|
+
@classmethod
|
|
572
|
+
def from_json(cls, json_str: str) -> "PatternRecord":
|
|
573
|
+
"""Create from JSON string."""
|
|
574
|
+
return cls.from_dict(json.loads(json_str))
|
|
575
|
+
|
|
576
|
+
def __str__(self) -> str:
|
|
577
|
+
"""Human-readable representation."""
|
|
578
|
+
return (
|
|
579
|
+
f"PatternRecord({self.id}): {self.name} ({self.pattern_type})\n"
|
|
580
|
+
f" Description: {self.description}\n"
|
|
581
|
+
f" Occurrences: {self.occurrence_count} in {len(self.sessions_affected)} "
|
|
582
|
+
f"sessions"
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
def validate(self) -> tuple[bool, str]:
|
|
586
|
+
"""Validate pattern record integrity.
|
|
587
|
+
|
|
588
|
+
Returns:
|
|
589
|
+
Tuple of (is_valid, error_message)
|
|
590
|
+
"""
|
|
591
|
+
if not self.id:
|
|
592
|
+
return False, "id cannot be empty"
|
|
593
|
+
if self.pattern_type not in ("anti-pattern", "good-pattern"):
|
|
594
|
+
return False, "pattern_type must be 'anti-pattern' or 'good-pattern'"
|
|
595
|
+
if not self.name:
|
|
596
|
+
return False, "name cannot be empty"
|
|
597
|
+
if not self.description:
|
|
598
|
+
return False, "description cannot be empty"
|
|
599
|
+
if not self.trigger_conditions:
|
|
600
|
+
return False, "trigger_conditions cannot be empty"
|
|
601
|
+
if not self.example_sequence:
|
|
602
|
+
return False, "example_sequence cannot be empty"
|
|
603
|
+
if self.occurrence_count < 0:
|
|
604
|
+
return False, "occurrence_count cannot be negative"
|
|
605
|
+
if self.pattern_type == "anti-pattern" and not self.correct_approach:
|
|
606
|
+
return False, "anti-patterns must have correct_approach"
|
|
607
|
+
return True, ""
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
@dataclass
|
|
611
|
+
class AutonomyLevel:
|
|
612
|
+
"""Recommendation for agent autonomy level.
|
|
613
|
+
|
|
614
|
+
Suggests appropriate autonomy settings based on demonstrated behavior and
|
|
615
|
+
violation history. Adapts guidance intensity to agent competence.
|
|
616
|
+
|
|
617
|
+
Levels:
|
|
618
|
+
- "observer": Minimal guidance, only watch
|
|
619
|
+
- "consultant": Moderate guidance, suggest alternatives
|
|
620
|
+
- "collaborator": Strong guidance, detailed explanations
|
|
621
|
+
- "operator": Maximal guidance, mandatory acknowledgments
|
|
622
|
+
|
|
623
|
+
Attributes:
|
|
624
|
+
level: One of "observer", "consultant", "collaborator", "operator"
|
|
625
|
+
messaging_intensity: "minimal", "moderate", "high", or "maximal"
|
|
626
|
+
enforcement_mode: "guidance" (no blocking) or "strict" (with acknowledgment)
|
|
627
|
+
reason: Explanation of why this level is recommended
|
|
628
|
+
based_on_violations: Count of violations influencing recommendation
|
|
629
|
+
based_on_patterns: List of patterns influencing recommendation
|
|
630
|
+
"""
|
|
631
|
+
|
|
632
|
+
level: str
|
|
633
|
+
messaging_intensity: str
|
|
634
|
+
enforcement_mode: str
|
|
635
|
+
|
|
636
|
+
reason: str
|
|
637
|
+
based_on_violations: int = 0
|
|
638
|
+
based_on_patterns: list[str] = field(default_factory=list)
|
|
639
|
+
|
|
640
|
+
VALID_LEVELS = {"observer", "consultant", "collaborator", "operator"}
|
|
641
|
+
VALID_INTENSITIES = {"minimal", "moderate", "high", "maximal"}
|
|
642
|
+
VALID_MODES = {"guidance", "strict"}
|
|
643
|
+
|
|
644
|
+
def to_dict(self) -> dict:
|
|
645
|
+
"""Convert to dictionary."""
|
|
646
|
+
return asdict(self)
|
|
647
|
+
|
|
648
|
+
def to_json(self) -> str:
|
|
649
|
+
"""Convert to JSON string."""
|
|
650
|
+
return json.dumps(self.to_dict())
|
|
651
|
+
|
|
652
|
+
@classmethod
|
|
653
|
+
def from_dict(cls, data: dict) -> "AutonomyLevel":
|
|
654
|
+
"""Create from dictionary."""
|
|
655
|
+
return cls(**data)
|
|
656
|
+
|
|
657
|
+
@classmethod
|
|
658
|
+
def from_json(cls, json_str: str) -> "AutonomyLevel":
|
|
659
|
+
"""Create from JSON string."""
|
|
660
|
+
return cls.from_dict(json.loads(json_str))
|
|
661
|
+
|
|
662
|
+
def __str__(self) -> str:
|
|
663
|
+
"""Human-readable representation."""
|
|
664
|
+
return (
|
|
665
|
+
f"AutonomyLevel: {self.level}\n"
|
|
666
|
+
f" Messaging: {self.messaging_intensity}\n"
|
|
667
|
+
f" Enforcement: {self.enforcement_mode}\n"
|
|
668
|
+
f" Reason: {self.reason}"
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
def validate(self) -> tuple[bool, str]:
|
|
672
|
+
"""Validate autonomy level configuration.
|
|
673
|
+
|
|
674
|
+
Returns:
|
|
675
|
+
Tuple of (is_valid, error_message)
|
|
676
|
+
"""
|
|
677
|
+
if self.level not in self.VALID_LEVELS:
|
|
678
|
+
return False, f"level must be one of {self.VALID_LEVELS}"
|
|
679
|
+
if self.messaging_intensity not in self.VALID_INTENSITIES:
|
|
680
|
+
return False, (
|
|
681
|
+
f"messaging_intensity must be one of {self.VALID_INTENSITIES}"
|
|
682
|
+
)
|
|
683
|
+
if self.enforcement_mode not in self.VALID_MODES:
|
|
684
|
+
return False, f"enforcement_mode must be one of {self.VALID_MODES}"
|
|
685
|
+
if not self.reason:
|
|
686
|
+
return False, "reason cannot be empty"
|
|
687
|
+
if self.based_on_violations < 0:
|
|
688
|
+
return False, "based_on_violations cannot be negative"
|
|
689
|
+
return True, ""
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
@dataclass
|
|
693
|
+
class CostMetrics:
|
|
694
|
+
"""Comprehensive cost metrics for a session or operation.
|
|
695
|
+
|
|
696
|
+
Aggregates token costs with efficiency scoring and waste analysis.
|
|
697
|
+
|
|
698
|
+
Attributes:
|
|
699
|
+
total_tokens: Total tokens consumed
|
|
700
|
+
orchestrator_tokens: Tokens in main orchestrator context
|
|
701
|
+
subagent_tokens: Tokens in delegated subagent contexts
|
|
702
|
+
waste_tokens: Tokens wasted on suboptimal decisions
|
|
703
|
+
optimal_tokens: What it would have cost with optimal delegation
|
|
704
|
+
efficiency_score: 0-100 efficiency rating
|
|
705
|
+
waste_percentage: Waste as percentage of total
|
|
706
|
+
"""
|
|
707
|
+
|
|
708
|
+
total_tokens: int
|
|
709
|
+
orchestrator_tokens: int
|
|
710
|
+
subagent_tokens: int
|
|
711
|
+
|
|
712
|
+
waste_tokens: int
|
|
713
|
+
optimal_tokens: int
|
|
714
|
+
|
|
715
|
+
efficiency_score: float = 0.0
|
|
716
|
+
waste_percentage: float = 0.0
|
|
717
|
+
|
|
718
|
+
def to_dict(self) -> dict:
|
|
719
|
+
"""Convert to dictionary."""
|
|
720
|
+
return asdict(self)
|
|
721
|
+
|
|
722
|
+
def to_json(self) -> str:
|
|
723
|
+
"""Convert to JSON string."""
|
|
724
|
+
return json.dumps(self.to_dict())
|
|
725
|
+
|
|
726
|
+
@classmethod
|
|
727
|
+
def from_dict(cls, data: dict) -> "CostMetrics":
|
|
728
|
+
"""Create from dictionary."""
|
|
729
|
+
return cls(**data)
|
|
730
|
+
|
|
731
|
+
@classmethod
|
|
732
|
+
def from_json(cls, json_str: str) -> "CostMetrics":
|
|
733
|
+
"""Create from JSON string."""
|
|
734
|
+
return cls.from_dict(json.loads(json_str))
|
|
735
|
+
|
|
736
|
+
def __str__(self) -> str:
|
|
737
|
+
"""Human-readable representation."""
|
|
738
|
+
return (
|
|
739
|
+
f"CostMetrics: {self.total_tokens} tokens\n"
|
|
740
|
+
f" Orchestrator: {self.orchestrator_tokens}\n"
|
|
741
|
+
f" Subagents: {self.subagent_tokens}\n"
|
|
742
|
+
f" Waste: {self.waste_tokens} tokens ({self.waste_percentage:.1f}%)\n"
|
|
743
|
+
f" Efficiency: {self.efficiency_score}/100"
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
def validate(self) -> tuple[bool, str]:
|
|
747
|
+
"""Validate cost metrics integrity.
|
|
748
|
+
|
|
749
|
+
Returns:
|
|
750
|
+
Tuple of (is_valid, error_message)
|
|
751
|
+
"""
|
|
752
|
+
if self.total_tokens < 0:
|
|
753
|
+
return False, "total_tokens cannot be negative"
|
|
754
|
+
if self.orchestrator_tokens < 0:
|
|
755
|
+
return False, "orchestrator_tokens cannot be negative"
|
|
756
|
+
if self.subagent_tokens < 0:
|
|
757
|
+
return False, "subagent_tokens cannot be negative"
|
|
758
|
+
if self.waste_tokens < 0:
|
|
759
|
+
return False, "waste_tokens cannot be negative"
|
|
760
|
+
if self.optimal_tokens < 0:
|
|
761
|
+
return False, "optimal_tokens cannot be negative"
|
|
762
|
+
if not (0.0 <= self.efficiency_score <= 100.0):
|
|
763
|
+
return False, "efficiency_score must be between 0 and 100"
|
|
764
|
+
if not (0.0 <= self.waste_percentage <= 100.0):
|
|
765
|
+
return False, "waste_percentage must be between 0 and 100"
|
|
766
|
+
if (self.orchestrator_tokens + self.subagent_tokens) > self.total_tokens:
|
|
767
|
+
return (
|
|
768
|
+
False,
|
|
769
|
+
"orchestrator + subagent tokens cannot exceed total",
|
|
770
|
+
)
|
|
771
|
+
return True, ""
|