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,317 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ViolationTracker for CIGS (Computational Imperative Guidance System)
|
|
3
|
+
|
|
4
|
+
Tracks delegation violations in JSONL format, providing thread-safe access
|
|
5
|
+
to violation records and session metrics.
|
|
6
|
+
|
|
7
|
+
Reference: .htmlgraph/spikes/computational-imperative-guidance-system-design.md (Part 3)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import threading
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from uuid import uuid4
|
|
16
|
+
|
|
17
|
+
from htmlgraph.cigs.models import (
|
|
18
|
+
OperationClassification,
|
|
19
|
+
SessionViolationSummary,
|
|
20
|
+
TokenCost,
|
|
21
|
+
ViolationRecord,
|
|
22
|
+
ViolationType,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ViolationTracker:
|
|
27
|
+
"""
|
|
28
|
+
Thread-safe tracker for delegation violations.
|
|
29
|
+
|
|
30
|
+
Stores violations in JSONL format at `.htmlgraph/cigs/violations.jsonl`
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, graph_dir: Path | None = None):
|
|
34
|
+
"""
|
|
35
|
+
Initialize ViolationTracker.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
graph_dir: Root directory for HtmlGraph (defaults to .htmlgraph)
|
|
39
|
+
"""
|
|
40
|
+
if graph_dir is None:
|
|
41
|
+
graph_dir = Path.cwd() / ".htmlgraph"
|
|
42
|
+
|
|
43
|
+
self.graph_dir = Path(graph_dir)
|
|
44
|
+
self.cigs_dir = self.graph_dir / "cigs"
|
|
45
|
+
self.violations_file = self.cigs_dir / "violations.jsonl"
|
|
46
|
+
|
|
47
|
+
# Create directory if needed
|
|
48
|
+
self.cigs_dir.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
|
|
50
|
+
# Thread-safe access
|
|
51
|
+
self._lock = threading.RLock()
|
|
52
|
+
|
|
53
|
+
# Current session ID (set externally or detect from environment)
|
|
54
|
+
self._session_id: str | None = self._detect_session_id()
|
|
55
|
+
|
|
56
|
+
def _detect_session_id(self) -> str | None:
|
|
57
|
+
"""Detect current session ID from environment or HtmlGraph session manager."""
|
|
58
|
+
# First check environment variable
|
|
59
|
+
if "HTMLGRAPH_SESSION_ID" in os.environ:
|
|
60
|
+
return os.environ["HTMLGRAPH_SESSION_ID"]
|
|
61
|
+
|
|
62
|
+
# Try to get from session manager if available
|
|
63
|
+
try:
|
|
64
|
+
from htmlgraph.session_manager import SessionManager
|
|
65
|
+
|
|
66
|
+
sm = SessionManager(self.graph_dir)
|
|
67
|
+
current = sm.get_active_session()
|
|
68
|
+
if current:
|
|
69
|
+
return str(current.id)
|
|
70
|
+
except Exception:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
def record_violation(
|
|
76
|
+
self,
|
|
77
|
+
tool: str,
|
|
78
|
+
params: dict,
|
|
79
|
+
classification: OperationClassification,
|
|
80
|
+
predicted_waste: int,
|
|
81
|
+
) -> str:
|
|
82
|
+
"""
|
|
83
|
+
Record a violation.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
tool: Tool name (Read, Grep, Edit, etc.)
|
|
87
|
+
params: Tool parameters passed
|
|
88
|
+
classification: OperationClassification with context
|
|
89
|
+
predicted_waste: Predicted wasted tokens
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Violation ID
|
|
93
|
+
"""
|
|
94
|
+
with self._lock:
|
|
95
|
+
violation_id = f"viol-{uuid4().hex[:12]}"
|
|
96
|
+
timestamp = datetime.utcnow()
|
|
97
|
+
|
|
98
|
+
# Get current session
|
|
99
|
+
session_id = self._session_id or "unknown"
|
|
100
|
+
|
|
101
|
+
record = ViolationRecord(
|
|
102
|
+
id=violation_id,
|
|
103
|
+
session_id=session_id,
|
|
104
|
+
timestamp=timestamp,
|
|
105
|
+
tool=tool,
|
|
106
|
+
tool_params=params,
|
|
107
|
+
violation_type=self._classify_violation(tool, classification),
|
|
108
|
+
context_before=None,
|
|
109
|
+
should_have_delegated_to=classification.suggested_delegation,
|
|
110
|
+
actual_cost_tokens=classification.predicted_cost,
|
|
111
|
+
optimal_cost_tokens=classification.optimal_cost,
|
|
112
|
+
waste_tokens=predicted_waste,
|
|
113
|
+
warning_level=1,
|
|
114
|
+
was_warned=False,
|
|
115
|
+
warning_ignored=False,
|
|
116
|
+
agent="claude-code",
|
|
117
|
+
feature_id=None,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Append to JSONL file
|
|
121
|
+
self._append_violation(record)
|
|
122
|
+
|
|
123
|
+
return violation_id
|
|
124
|
+
|
|
125
|
+
def _classify_violation(
|
|
126
|
+
self, tool: str, classification: OperationClassification
|
|
127
|
+
) -> ViolationType:
|
|
128
|
+
"""Classify the violation type based on tool and context."""
|
|
129
|
+
if classification.is_exploration_sequence:
|
|
130
|
+
return ViolationType.EXPLORATION_SEQUENCE
|
|
131
|
+
|
|
132
|
+
# Map tools to violation types
|
|
133
|
+
if tool in ("Read", "Grep", "Glob"):
|
|
134
|
+
return ViolationType.DIRECT_EXPLORATION
|
|
135
|
+
elif tool in ("Edit", "Write", "NotebookEdit"):
|
|
136
|
+
return ViolationType.DIRECT_IMPLEMENTATION
|
|
137
|
+
elif tool == "Bash" and "git" in str(classification.reason).lower():
|
|
138
|
+
return ViolationType.DIRECT_GIT
|
|
139
|
+
elif tool == "Bash":
|
|
140
|
+
return ViolationType.DIRECT_TESTING
|
|
141
|
+
|
|
142
|
+
return ViolationType.DIRECT_EXPLORATION
|
|
143
|
+
|
|
144
|
+
def _append_violation(self, record: ViolationRecord) -> None:
|
|
145
|
+
"""Append violation record to JSONL file (thread-safe)."""
|
|
146
|
+
with self._lock:
|
|
147
|
+
try:
|
|
148
|
+
with open(self.violations_file, "a") as f:
|
|
149
|
+
f.write(json.dumps(record.to_dict()) + "\n")
|
|
150
|
+
except Exception as e:
|
|
151
|
+
# Log but don't crash on storage errors
|
|
152
|
+
print(f"Warning: Failed to record violation: {e}")
|
|
153
|
+
|
|
154
|
+
def get_session_violations(
|
|
155
|
+
self, session_id: str | None = None
|
|
156
|
+
) -> SessionViolationSummary:
|
|
157
|
+
"""
|
|
158
|
+
Get violations for current or specific session.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
session_id: Session ID (defaults to current session)
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
SessionViolationSummary with aggregated metrics
|
|
165
|
+
"""
|
|
166
|
+
if session_id is None:
|
|
167
|
+
session_id = self._session_id or "unknown"
|
|
168
|
+
|
|
169
|
+
with self._lock:
|
|
170
|
+
violations = self._load_violations()
|
|
171
|
+
|
|
172
|
+
# Filter to session
|
|
173
|
+
session_violations = [v for v in violations if v.session_id == session_id]
|
|
174
|
+
|
|
175
|
+
# Aggregate by type
|
|
176
|
+
violations_by_type = {}
|
|
177
|
+
for vtype in ViolationType:
|
|
178
|
+
count = sum(1 for v in session_violations if v.violation_type == vtype)
|
|
179
|
+
if count > 0:
|
|
180
|
+
violations_by_type[vtype] = count
|
|
181
|
+
|
|
182
|
+
# Calculate totals
|
|
183
|
+
total_violations = len(session_violations)
|
|
184
|
+
total_waste = sum(v.waste_tokens for v in session_violations)
|
|
185
|
+
circuit_breaker = total_violations >= 3
|
|
186
|
+
|
|
187
|
+
# Compliance rate: 1.0 = no violations, 0.0 = many violations
|
|
188
|
+
# For simplicity: (max_violations - actual) / max_violations
|
|
189
|
+
# where max_violations = 5 (violation rate saturates)
|
|
190
|
+
compliance_rate = max(0.0, 1.0 - (total_violations / 5.0))
|
|
191
|
+
|
|
192
|
+
return SessionViolationSummary(
|
|
193
|
+
session_id=session_id,
|
|
194
|
+
total_violations=total_violations,
|
|
195
|
+
violations_by_type=violations_by_type,
|
|
196
|
+
total_waste_tokens=total_waste,
|
|
197
|
+
circuit_breaker_triggered=circuit_breaker,
|
|
198
|
+
compliance_rate=compliance_rate,
|
|
199
|
+
violations=session_violations,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
def get_recent_violations(self, sessions: int = 5) -> list[ViolationRecord]:
|
|
203
|
+
"""
|
|
204
|
+
Get violations from last N sessions.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
sessions: Number of sessions to include
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
List of violation records from recent sessions
|
|
211
|
+
"""
|
|
212
|
+
with self._lock:
|
|
213
|
+
violations = self._load_violations()
|
|
214
|
+
|
|
215
|
+
# Group by session and get N most recent
|
|
216
|
+
session_ids = set(v.session_id for v in violations)
|
|
217
|
+
recent_sessions = sorted(session_ids)[-sessions:]
|
|
218
|
+
|
|
219
|
+
return [v for v in violations if v.session_id in recent_sessions]
|
|
220
|
+
|
|
221
|
+
def record_actual_cost(self, tool: str, cost: TokenCost) -> None:
|
|
222
|
+
"""
|
|
223
|
+
Update violation with actual cost after execution.
|
|
224
|
+
|
|
225
|
+
This updates the most recent violation for the given tool.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
tool: Tool name
|
|
229
|
+
cost: TokenCost with actual metrics
|
|
230
|
+
"""
|
|
231
|
+
with self._lock:
|
|
232
|
+
violations = self._load_violations()
|
|
233
|
+
|
|
234
|
+
if not violations:
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
# Find most recent violation for this tool (in current session)
|
|
238
|
+
session_id = self._session_id or "unknown"
|
|
239
|
+
matching = [
|
|
240
|
+
v for v in violations if v.tool == tool and v.session_id == session_id
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
if not matching:
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
# Update most recent
|
|
247
|
+
latest = matching[-1]
|
|
248
|
+
latest.actual_cost_tokens = cost.total_tokens
|
|
249
|
+
latest.waste_tokens = cost.total_tokens - latest.optimal_cost_tokens
|
|
250
|
+
|
|
251
|
+
# Rewrite file (simple approach - replace entire file)
|
|
252
|
+
self._write_violations(violations)
|
|
253
|
+
|
|
254
|
+
def _write_violations(self, violations: list[ViolationRecord]) -> None:
|
|
255
|
+
"""Write violations back to JSONL file."""
|
|
256
|
+
with self._lock:
|
|
257
|
+
try:
|
|
258
|
+
with open(self.violations_file, "w") as f:
|
|
259
|
+
for v in violations:
|
|
260
|
+
f.write(json.dumps(v.to_dict()) + "\n")
|
|
261
|
+
except Exception as e:
|
|
262
|
+
print(f"Warning: Failed to write violations: {e}")
|
|
263
|
+
|
|
264
|
+
def _load_violations(self) -> list[ViolationRecord]:
|
|
265
|
+
"""Load all violations from JSONL file."""
|
|
266
|
+
violations: list[ViolationRecord] = []
|
|
267
|
+
|
|
268
|
+
if not self.violations_file.exists():
|
|
269
|
+
return violations
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
with open(self.violations_file) as f:
|
|
273
|
+
for line in f:
|
|
274
|
+
if not line.strip():
|
|
275
|
+
continue
|
|
276
|
+
try:
|
|
277
|
+
data = json.loads(line)
|
|
278
|
+
violations.append(ViolationRecord.from_dict(data))
|
|
279
|
+
except (json.JSONDecodeError, ValueError) as e:
|
|
280
|
+
print(f"Warning: Failed to parse violation record: {e}")
|
|
281
|
+
continue
|
|
282
|
+
except Exception as e:
|
|
283
|
+
print(f"Warning: Failed to load violations: {e}")
|
|
284
|
+
|
|
285
|
+
return violations
|
|
286
|
+
|
|
287
|
+
def get_session_waste(self) -> int:
|
|
288
|
+
"""
|
|
289
|
+
Get total wasted tokens for current session.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
Total waste tokens
|
|
293
|
+
"""
|
|
294
|
+
summary = self.get_session_violations()
|
|
295
|
+
return summary.total_waste_tokens
|
|
296
|
+
|
|
297
|
+
def set_session_id(self, session_id: str) -> None:
|
|
298
|
+
"""
|
|
299
|
+
Set the current session ID.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
session_id: Session ID to use for subsequent violations
|
|
303
|
+
"""
|
|
304
|
+
self._session_id = session_id
|
|
305
|
+
|
|
306
|
+
def clear_session_file(self) -> None:
|
|
307
|
+
"""
|
|
308
|
+
Clear the violations file (useful for testing).
|
|
309
|
+
|
|
310
|
+
WARNING: This deletes all violation records!
|
|
311
|
+
"""
|
|
312
|
+
with self._lock:
|
|
313
|
+
try:
|
|
314
|
+
if self.violations_file.exists():
|
|
315
|
+
self.violations_file.unlink()
|
|
316
|
+
except Exception as e:
|
|
317
|
+
print(f"Warning: Failed to clear violations file: {e}")
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0",
|
|
3
|
+
"updated": "2026-01-11T21:41:53.445581",
|
|
4
|
+
"agents": {
|
|
5
|
+
"claude": {
|
|
6
|
+
"id": "claude",
|
|
7
|
+
"name": "Claude",
|
|
8
|
+
"capabilities": [
|
|
9
|
+
"python",
|
|
10
|
+
"javascript",
|
|
11
|
+
"typescript",
|
|
12
|
+
"html",
|
|
13
|
+
"css",
|
|
14
|
+
"code-review",
|
|
15
|
+
"testing",
|
|
16
|
+
"documentation",
|
|
17
|
+
"debugging",
|
|
18
|
+
"refactoring",
|
|
19
|
+
"architecture",
|
|
20
|
+
"api-design"
|
|
21
|
+
],
|
|
22
|
+
"max_parallel_tasks": 3,
|
|
23
|
+
"preferred_complexity": [
|
|
24
|
+
"low",
|
|
25
|
+
"medium",
|
|
26
|
+
"high",
|
|
27
|
+
"very-high"
|
|
28
|
+
],
|
|
29
|
+
"active": true,
|
|
30
|
+
"metadata": {}
|
|
31
|
+
},
|
|
32
|
+
"gemini": {
|
|
33
|
+
"id": "gemini",
|
|
34
|
+
"name": "Gemini",
|
|
35
|
+
"capabilities": [
|
|
36
|
+
"python",
|
|
37
|
+
"data-analysis",
|
|
38
|
+
"documentation",
|
|
39
|
+
"testing",
|
|
40
|
+
"code-review",
|
|
41
|
+
"javascript"
|
|
42
|
+
],
|
|
43
|
+
"max_parallel_tasks": 2,
|
|
44
|
+
"preferred_complexity": [
|
|
45
|
+
"low",
|
|
46
|
+
"medium",
|
|
47
|
+
"high"
|
|
48
|
+
],
|
|
49
|
+
"active": true,
|
|
50
|
+
"metadata": {}
|
|
51
|
+
},
|
|
52
|
+
"codex": {
|
|
53
|
+
"id": "codex",
|
|
54
|
+
"name": "Codex",
|
|
55
|
+
"capabilities": [
|
|
56
|
+
"python",
|
|
57
|
+
"javascript",
|
|
58
|
+
"debugging",
|
|
59
|
+
"testing",
|
|
60
|
+
"code-generation",
|
|
61
|
+
"documentation"
|
|
62
|
+
],
|
|
63
|
+
"max_parallel_tasks": 2,
|
|
64
|
+
"preferred_complexity": [
|
|
65
|
+
"low",
|
|
66
|
+
"medium"
|
|
67
|
+
],
|
|
68
|
+
"active": true,
|
|
69
|
+
"metadata": {}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""HtmlGraph CLI - Modular command-line interface.
|
|
2
|
+
|
|
3
|
+
Architecture:
|
|
4
|
+
- base.py: Base classes, formatters, error handling
|
|
5
|
+
- constants.py: Single source of truth for all configuration
|
|
6
|
+
- main.py: Entry point, argument parsing
|
|
7
|
+
- core.py: Infrastructure commands (serve, init, status, etc.)
|
|
8
|
+
- work.py: Work management (features, sessions, tracks, etc.)
|
|
9
|
+
- analytics.py: Reporting and analytics commands
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
from htmlgraph.cli.main import main
|
|
13
|
+
main()
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from htmlgraph.cli.base import (
|
|
17
|
+
BaseCommand,
|
|
18
|
+
CommandError,
|
|
19
|
+
CommandResult,
|
|
20
|
+
JsonFormatter,
|
|
21
|
+
TextFormatter,
|
|
22
|
+
get_formatter,
|
|
23
|
+
)
|
|
24
|
+
from htmlgraph.cli.main import main
|
|
25
|
+
from htmlgraph.cli.work import (
|
|
26
|
+
cmd_orchestrator_reset_violations,
|
|
27
|
+
cmd_orchestrator_set_level,
|
|
28
|
+
cmd_orchestrator_status,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"main",
|
|
33
|
+
"BaseCommand",
|
|
34
|
+
"CommandError",
|
|
35
|
+
"CommandResult",
|
|
36
|
+
"JsonFormatter",
|
|
37
|
+
"TextFormatter",
|
|
38
|
+
"get_formatter",
|
|
39
|
+
"cmd_orchestrator_reset_violations",
|
|
40
|
+
"cmd_orchestrator_set_level",
|
|
41
|
+
"cmd_orchestrator_status",
|
|
42
|
+
]
|