htmlgraph 0.23.1__py3-none-any.whl → 0.23.3__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/__init__.py CHANGED
@@ -84,7 +84,7 @@ from htmlgraph.types import (
84
84
  )
85
85
  from htmlgraph.work_type_utils import infer_work_type, infer_work_type_from_id
86
86
 
87
- __version__ = "0.23.1"
87
+ __version__ = "0.23.3"
88
88
  __all__ = [
89
89
  # Exceptions
90
90
  "HtmlGraphError",
htmlgraph/converter.py CHANGED
@@ -555,12 +555,14 @@ def html_to_session(filepath: Path | str) -> Session:
555
555
  pattern_type = tr.attrs.get("data-pattern-type", "neutral")
556
556
 
557
557
  # Extract sequence from first <td class="sequence">
558
- seq_td = tr.query_one("td.sequence")
558
+ seq_tds = tr.query("td.sequence")
559
+ seq_td = seq_tds[0] if seq_tds else None
559
560
  sequence_str = seq_td.to_text().strip() if seq_td else ""
560
561
  sequence = [s.strip() for s in sequence_str.split("→")] if sequence_str else []
561
562
 
562
563
  # Extract count from third <td>
563
- count_td = tr.query_all("td")[2] if len(tr.query_all("td")) > 2 else None
564
+ tds = tr.query("td")
565
+ count_td = tds[2] if len(tds) > 2 else None
564
566
  count_str = count_td.to_text().strip() if count_td else "0"
565
567
  try:
566
568
  count = int(count_str)
@@ -568,7 +570,7 @@ def html_to_session(filepath: Path | str) -> Session:
568
570
  count = 0
569
571
 
570
572
  # Extract timestamps from fourth <td>
571
- time_td = tr.query_all("td")[3] if len(tr.query_all("td")) > 3 else None
573
+ time_td = tds[3] if len(tds) > 3 else None
572
574
  time_str = time_td.to_text().strip() if time_td else ""
573
575
  times = time_str.split(" / ")
574
576
  first_detected = times[0].strip() if len(times) > 0 else ""
htmlgraph/event_log.py CHANGED
@@ -38,6 +38,22 @@ class EventRecord:
38
38
  session_status: str | None = None
39
39
  file_paths: list[str] | None = None
40
40
  payload: dict[str, Any] | None = None
41
+ # Phase 1: Enhanced Event Data Schema for multi-AI delegation tracking
42
+ delegated_to_ai: str | None = (
43
+ None # "gemini", "codex", "copilot", "claude", or None
44
+ )
45
+ task_id: str | None = None # Unique task ID for parallel tracking
46
+ task_status: str | None = (
47
+ None # "pending", "running", "completed", "failed", "timeout"
48
+ )
49
+ model_selected: str | None = None # Specific model (e.g., "gemini-2.0-flash")
50
+ complexity_level: str | None = None # "low", "medium", "high", "very-high"
51
+ budget_mode: str | None = None # "free", "balanced", "performance"
52
+ execution_duration_seconds: float | None = None # How long delegation took
53
+ tokens_estimated: int | None = None # Estimated token usage
54
+ tokens_actual: int | None = None # Actual token usage
55
+ cost_usd: float | None = None # Calculated cost
56
+ task_findings: str | None = None # Results from delegated task
41
57
 
42
58
  def to_json(self) -> dict[str, Any]:
43
59
  return {
@@ -56,6 +72,18 @@ class EventRecord:
56
72
  "session_status": self.session_status,
57
73
  "file_paths": self.file_paths or [],
58
74
  "payload": self.payload,
75
+ # Delegation fields
76
+ "delegated_to_ai": self.delegated_to_ai,
77
+ "task_id": self.task_id,
78
+ "task_status": self.task_status,
79
+ "model_selected": self.model_selected,
80
+ "complexity_level": self.complexity_level,
81
+ "budget_mode": self.budget_mode,
82
+ "execution_duration_seconds": self.execution_duration_seconds,
83
+ "tokens_estimated": self.tokens_estimated,
84
+ "tokens_actual": self.tokens_actual,
85
+ "cost_usd": self.cost_usd,
86
+ "task_findings": self.task_findings,
59
87
  }
60
88
 
61
89
 
@@ -107,6 +107,23 @@ def is_allowed_orchestrator_operation(tool: str, params: dict) -> tuple[bool, st
107
107
  - reason_if_not: Explanation if blocked (empty if allowed)
108
108
  - category: Operation category for logging
109
109
  """
110
+ # Get enforcement level from manager
111
+ try:
112
+ cwd = Path.cwd()
113
+ graph_dir = cwd / ".htmlgraph"
114
+ if not graph_dir.exists():
115
+ for parent in [cwd.parent, cwd.parent.parent, cwd.parent.parent.parent]:
116
+ candidate = parent / ".htmlgraph"
117
+ if candidate.exists():
118
+ graph_dir = candidate
119
+ break
120
+ manager = OrchestratorModeManager(graph_dir)
121
+ enforcement_level = (
122
+ manager.get_enforcement_level() if manager.is_enabled() else "guidance"
123
+ )
124
+ except Exception:
125
+ enforcement_level = "guidance"
126
+
110
127
  # Use OrchestratorValidator for comprehensive validation
111
128
  validator = OrchestratorValidator()
112
129
  result, reason = validator.validate_tool_use(tool, params)
@@ -121,6 +138,10 @@ def is_allowed_orchestrator_operation(tool: str, params: dict) -> tuple[bool, st
121
138
  if tool in ["Task", "AskUserQuestion", "TodoWrite"]:
122
139
  return True, "", "orchestrator-core"
123
140
 
141
+ # FIX #2: Block Skills in strict mode (must be invoked via Task delegation)
142
+ if tool == "Skill" and enforcement_level == "strict":
143
+ return False, "Skills must be invoked via Task delegation", "skill-blocked"
144
+
124
145
  # Category 2: SDK Operations - Always allowed
125
146
  if tool == "Bash":
126
147
  command = params.get("command", "")
@@ -141,11 +162,51 @@ def is_allowed_orchestrator_operation(tool: str, params: dict) -> tuple[bool, st
141
162
  if "from htmlgraph import" in command or "import htmlgraph" in command:
142
163
  return True, "", "sdk-inline"
143
164
 
165
+ # FIX #3: Check if bash command is in allowed whitelist (strict mode only)
166
+ # If we've gotten here, it's not a whitelisted command above
167
+ # Block non-whitelisted bash commands in strict mode
168
+ if enforcement_level == "strict":
169
+ # Check if it's a blocked test/build pattern (handled below)
170
+ blocked_patterns = [
171
+ r"^npm (run|test|build)",
172
+ r"^pytest",
173
+ r"^uv run pytest",
174
+ r"^python -m pytest",
175
+ r"^cargo (build|test)",
176
+ r"^mvn (compile|test|package)",
177
+ r"^make (test|build)",
178
+ ]
179
+ is_blocked_pattern = any(
180
+ re.match(pattern, command) for pattern in blocked_patterns
181
+ )
182
+
183
+ if not is_blocked_pattern:
184
+ # Not a specifically blocked pattern, but also not whitelisted
185
+ # In strict mode, we should delegate
186
+ return (
187
+ False,
188
+ f"Bash command not in allowed list. Delegate to subagent.\n\n"
189
+ f"Command: {command[:100]}",
190
+ "bash-blocked",
191
+ )
192
+
144
193
  # Category 3: Quick Lookups - Single operations only
145
194
  if tool in ["Read", "Grep", "Glob"]:
146
195
  # Check tool history to see if this is a single lookup or part of a sequence
147
196
  history = load_tool_history()
148
197
 
198
+ # FIX #4: Check for mixed exploration pattern
199
+ exploration_count = sum(
200
+ 1 for h in history[-5:] if h["tool"] in ["Read", "Grep", "Glob"]
201
+ )
202
+ if exploration_count >= 3 and enforcement_level == "strict":
203
+ return (
204
+ False,
205
+ "Multiple exploration calls detected. Delegate to Explorer agent.\n\n"
206
+ "Use Task tool with explorer subagent.",
207
+ "exploration-blocked",
208
+ )
209
+
149
210
  # Look at last 3 tool calls
150
211
  recent_same_tool = sum(1 for h in history[-3:] if h["tool"] == tool)
151
212
 
@@ -181,7 +242,7 @@ def is_allowed_orchestrator_operation(tool: str, params: dict) -> tuple[bool, st
181
242
  command = params.get("command", "")
182
243
 
183
244
  # Block compilation, testing, building (should be in subagent)
184
- blocked_patterns = [
245
+ test_build_patterns: list[tuple[str, str]] = [
185
246
  (r"^npm (run|test|build)", "npm test/build"),
186
247
  (r"^pytest", "pytest"),
187
248
  (r"^uv run pytest", "pytest"),
@@ -191,7 +252,7 @@ def is_allowed_orchestrator_operation(tool: str, params: dict) -> tuple[bool, st
191
252
  (r"^make (test|build)", "make test/build"),
192
253
  ]
193
254
 
194
- for pattern, name in blocked_patterns:
255
+ for pattern, name in test_build_patterns:
195
256
  if re.match(pattern, command):
196
257
  return (
197
258
  False,
@@ -200,8 +261,11 @@ def is_allowed_orchestrator_operation(tool: str, params: dict) -> tuple[bool, st
200
261
  "test-build-blocked",
201
262
  )
202
263
 
203
- # Default: Allow with guidance
204
- return True, "Allowed but consider if delegation would be better", "allowed-default"
264
+ # FIX #1: Remove "allowed-default" escape hatch in strict mode
265
+ if enforcement_level == "strict":
266
+ return False, "Not in allowed whitelist", "strict-blocked"
267
+ else:
268
+ return True, "Allowed in guidance mode", "guidance-allowed"
205
269
 
206
270
 
207
271
  def create_task_suggestion(tool: str, params: dict) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: htmlgraph
3
- Version: 0.23.1
3
+ Version: 0.23.3
4
4
  Summary: HTML is All You Need - Graph database on web standards
5
5
  Project-URL: Homepage, https://github.com/Shakes-tzd/htmlgraph
6
6
  Project-URL: Documentation, https://github.com/Shakes-tzd/htmlgraph#readme
@@ -1,4 +1,4 @@
1
- htmlgraph/__init__.py,sha256=nWM6HtPl0ASV6Y-Flmw0VxM2eGiuoxt1Lt-8Y2B_Gx4,4979
1
+ htmlgraph/__init__.py,sha256=Qf68lwdWeeqCQKique3dLRlcp1q7q4zfbycqJoIhmAI,4979
2
2
  htmlgraph/agent_detection.py,sha256=MG1kx9S-ey_Wi84hJTwwgw6VDvYEbUpygmDjGPhHcUA,3805
3
3
  htmlgraph/agent_registry.py,sha256=Usa_35by7p5gtpvHO7K3AcGimnorw-FzgPVa3cWTQ58,9448
4
4
  htmlgraph/agents.py,sha256=Yvu6x1nOfrW2WhRTAHiCuSpvqoVJXx1Mkzd59kwEczw,33466
@@ -6,13 +6,13 @@ htmlgraph/analytics_index.py,sha256=ba6Y4H_NNOCxI_Z4U7wSgBFFairf4IJT74WcM1PoZuI,
6
6
  htmlgraph/attribute_index.py,sha256=cBZUV4YfGnhh6lF59aYPCdNrRr1hK__BzSKCueSDUhQ,6593
7
7
  htmlgraph/cli.py,sha256=-h-_7b5xRuxyLpIYHIeNbJabe3hY3_yTql_jWxfbjTU,197621
8
8
  htmlgraph/context_analytics.py,sha256=CaLu0o2uSr6rlBM5YeaFZe7grgsy7_Hx10qdXuNcdao,11344
9
- htmlgraph/converter.py,sha256=OfcydZcJqvr2jpMxvAD4wcq8o4NXC7w4X4QzdDiYq8k,22277
9
+ htmlgraph/converter.py,sha256=arP0HDANwx-fbENaMSEwM2BNVPhlYhBYwIYlOl4q5lM,22292
10
10
  htmlgraph/dashboard.html,sha256=rkZYjSnPbUuAm35QMpCNWemenYqQTdkkumCX2hhe8Dc,173537
11
11
  htmlgraph/dependency_models.py,sha256=eKpBz9y_pTE5E8baESqHyGUDj5-uXokVd2Bx3ZogAyM,4313
12
12
  htmlgraph/deploy.py,sha256=kM_IMa3PmKpQf4YVH57aL9uV5IfpVJgaj-IFsgAKIbY,17771
13
13
  htmlgraph/deployment_models.py,sha256=8pjvfe0YmExuYg65D4jO4kET52HaG8WhcArWXOJpfCQ,16064
14
14
  htmlgraph/edge_index.py,sha256=epeUiTj1Hm-NQj8WCWjSUypG1e8Z2hJulNT9drHstcM,14937
15
- htmlgraph/event_log.py,sha256=BjLmN73ohqwhTUQrlTd4E53eoitbMExmFCVRGNjlNY0,7866
15
+ htmlgraph/event_log.py,sha256=zJUefAaz97LvoVA-pi8uVMcCqA_pyS_saVczbioQf-Q,9372
16
16
  htmlgraph/event_migration.py,sha256=7vocfwy-E6_D8_Aeql630FrlBXppIYp1iLmZ_cw_ras,2512
17
17
  htmlgraph/exceptions.py,sha256=o_BPNQXtv9zaejbiItDVBK7kcGvYbh4Icj5NXmDSLeU,1592
18
18
  htmlgraph/file_watcher.py,sha256=qwwmDkWnKLPp8a_48VrtJZo6HcoYPRz1KMAD39yCV7U,6296
@@ -104,7 +104,7 @@ htmlgraph/hooks/__init__.py,sha256=jL2HyCoFWQQ8l-4-EAlypDxPalNE3JBfDyELYWAg-g0,8
104
104
  htmlgraph/hooks/event_tracker.py,sha256=KQcIWbhNJser6Tip87oUAPQJgUAAKESKE5ARQasLtCM,23301
105
105
  htmlgraph/hooks/hooks-config.example.json,sha256=tXpk-U-FZzGOoNJK2uiDMbIHCYEHA794J-El0fBwkqg,197
106
106
  htmlgraph/hooks/installer.py,sha256=nOctCFDEV7BEh7ZzxNY-apu1KZG0SHPMq74UPIOChqY,11756
107
- htmlgraph/hooks/orchestrator.py,sha256=55xjmfg680e9PKcMkBmpbAmSXkZa33EQcZXlwPjDn50,17105
107
+ htmlgraph/hooks/orchestrator.py,sha256=rKRx5EYED-BFQrnAJ_roIjpP8GOs3RKDpulDouA29K0,19781
108
108
  htmlgraph/hooks/orchestrator_reflector.py,sha256=j3kZge33m42CEUVYiufiz7mf7Qm4DimnsRZKjbpZStA,5154
109
109
  htmlgraph/hooks/post-checkout.sh,sha256=Hsr5hqD54jisGbtqf7-Z-G_b6XNGcee_CZRYaKYzWzU,615
110
110
  htmlgraph/hooks/post-commit.sh,sha256=if65jNGZnEWsZPq_iYDNYunrZ1cmjPUEUbh6_4vfpOE,511
@@ -134,12 +134,12 @@ htmlgraph/services/claiming.py,sha256=HcrltEJKN72mxuD7fGuXWeh1U0vwhjMvhZcFc02Eiy
134
134
  htmlgraph/templates/AGENTS.md.template,sha256=f96h7V6ygwj-v-fanVI48eYMxR6t_se4bet1H4ZsDpI,7642
135
135
  htmlgraph/templates/CLAUDE.md.template,sha256=h1kG2hTX2XYig2KszsHBfzrwa_4Cfcq2Pj4SwqzeDlM,1984
136
136
  htmlgraph/templates/GEMINI.md.template,sha256=gAGzE53Avki87BM_otqy5HdcYCoLsHgqaKjVzNzPMX8,1622
137
- htmlgraph-0.23.1.data/data/htmlgraph/dashboard.html,sha256=rkZYjSnPbUuAm35QMpCNWemenYqQTdkkumCX2hhe8Dc,173537
138
- htmlgraph-0.23.1.data/data/htmlgraph/styles.css,sha256=oDUSC8jG-V-hKojOBO9J88hxAeY2wJrBYTq0uCwX_Y4,7135
139
- htmlgraph-0.23.1.data/data/htmlgraph/templates/AGENTS.md.template,sha256=f96h7V6ygwj-v-fanVI48eYMxR6t_se4bet1H4ZsDpI,7642
140
- htmlgraph-0.23.1.data/data/htmlgraph/templates/CLAUDE.md.template,sha256=h1kG2hTX2XYig2KszsHBfzrwa_4Cfcq2Pj4SwqzeDlM,1984
141
- htmlgraph-0.23.1.data/data/htmlgraph/templates/GEMINI.md.template,sha256=gAGzE53Avki87BM_otqy5HdcYCoLsHgqaKjVzNzPMX8,1622
142
- htmlgraph-0.23.1.dist-info/METADATA,sha256=ZYzuVrkX-RqywaYq_QkiI3ILuYFEq5nUo_WnhgK2ZB4,7753
143
- htmlgraph-0.23.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
144
- htmlgraph-0.23.1.dist-info/entry_points.txt,sha256=EaUbjA_bbDwEO_XDLEGMeK8aQP-ZnHiUTkLshyKDyB8,98
145
- htmlgraph-0.23.1.dist-info/RECORD,,
137
+ htmlgraph-0.23.3.data/data/htmlgraph/dashboard.html,sha256=rkZYjSnPbUuAm35QMpCNWemenYqQTdkkumCX2hhe8Dc,173537
138
+ htmlgraph-0.23.3.data/data/htmlgraph/styles.css,sha256=oDUSC8jG-V-hKojOBO9J88hxAeY2wJrBYTq0uCwX_Y4,7135
139
+ htmlgraph-0.23.3.data/data/htmlgraph/templates/AGENTS.md.template,sha256=f96h7V6ygwj-v-fanVI48eYMxR6t_se4bet1H4ZsDpI,7642
140
+ htmlgraph-0.23.3.data/data/htmlgraph/templates/CLAUDE.md.template,sha256=h1kG2hTX2XYig2KszsHBfzrwa_4Cfcq2Pj4SwqzeDlM,1984
141
+ htmlgraph-0.23.3.data/data/htmlgraph/templates/GEMINI.md.template,sha256=gAGzE53Avki87BM_otqy5HdcYCoLsHgqaKjVzNzPMX8,1622
142
+ htmlgraph-0.23.3.dist-info/METADATA,sha256=L2COdnXtlHpf-FZWIgZauw1ySW_SnodPONdnw5bHlCw,7753
143
+ htmlgraph-0.23.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
144
+ htmlgraph-0.23.3.dist-info/entry_points.txt,sha256=EaUbjA_bbDwEO_XDLEGMeK8aQP-ZnHiUTkLshyKDyB8,98
145
+ htmlgraph-0.23.3.dist-info/RECORD,,