htmlgraph 0.23.5__py3-none-any.whl → 0.24.1__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 +5 -1
- htmlgraph/cigs/__init__.py +77 -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/tracker.py +317 -0
- htmlgraph/cli.py +413 -11
- htmlgraph/hooks/cigs_pretool_enforcer.py +350 -0
- htmlgraph/hooks/posttooluse.py +50 -2
- htmlgraph/hooks/task_enforcer.py +60 -4
- htmlgraph/models.py +14 -1
- htmlgraph/orchestration/headless_spawner.py +519 -21
- htmlgraph/orchestrator-system-prompt-optimized.txt +259 -53
- htmlgraph/reflection.py +442 -0
- htmlgraph/sdk.py +26 -9
- {htmlgraph-0.23.5.dist-info → htmlgraph-0.24.1.dist-info}/METADATA +2 -1
- {htmlgraph-0.23.5.dist-info → htmlgraph-0.24.1.dist-info}/RECORD +29 -17
- {htmlgraph-0.23.5.data → htmlgraph-0.24.1.data}/data/htmlgraph/dashboard.html +0 -0
- {htmlgraph-0.23.5.data → htmlgraph-0.24.1.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.23.5.data → htmlgraph-0.24.1.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.23.5.data → htmlgraph-0.24.1.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.23.5.data → htmlgraph-0.24.1.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.23.5.dist-info → htmlgraph-0.24.1.dist-info}/WHEEL +0 -0
- {htmlgraph-0.23.5.dist-info → htmlgraph-0.24.1.dist-info}/entry_points.txt +0 -0
htmlgraph/reflection.py
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Computational Reflection Module.
|
|
3
|
+
|
|
4
|
+
Pre-computes actionable context from session history for injection into
|
|
5
|
+
orchestrator prompts. Addresses the LLM limitation where models can only
|
|
6
|
+
effectively track 5-10 variables in working memory.
|
|
7
|
+
|
|
8
|
+
Design Principles:
|
|
9
|
+
1. COMPUTE, don't prompt - Do the synthesis work here, not in prompts
|
|
10
|
+
2. LIMIT to 5 items - Respect LLM working memory constraints
|
|
11
|
+
3. PRIORITIZE by recency and relevance - Most actionable items first
|
|
12
|
+
4. CONNECT the dots - Surface relationships the model would miss
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
from htmlgraph.reflection import ComputationalReflection
|
|
16
|
+
|
|
17
|
+
reflection = ComputationalReflection(sdk)
|
|
18
|
+
context = reflection.get_actionable_context()
|
|
19
|
+
# Returns: {
|
|
20
|
+
# "summary": "3 blockers, 1 recent failure, avoid Read-Read-Read pattern",
|
|
21
|
+
# "items": [...], # Max 5 items
|
|
22
|
+
# "injected_at": "2025-01-04T12:00:00"
|
|
23
|
+
# }
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from datetime import datetime, timedelta
|
|
30
|
+
from typing import TYPE_CHECKING, Any
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from htmlgraph.sdk import SDK
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ReflectionItem:
|
|
38
|
+
"""A single actionable reflection item."""
|
|
39
|
+
|
|
40
|
+
category: str # "blocker", "failure", "anti_pattern", "spike", "recommendation"
|
|
41
|
+
priority: int # 1-5, higher = more important
|
|
42
|
+
title: str # Brief title
|
|
43
|
+
detail: str # One-line actionable detail
|
|
44
|
+
source_id: str | None = None # ID of source item (feature, spike, etc.)
|
|
45
|
+
|
|
46
|
+
def to_dict(self) -> dict[str, Any]:
|
|
47
|
+
"""Convert to dictionary for JSON serialization."""
|
|
48
|
+
return {
|
|
49
|
+
"category": self.category,
|
|
50
|
+
"priority": self.priority,
|
|
51
|
+
"title": self.title,
|
|
52
|
+
"detail": self.detail,
|
|
53
|
+
"source_id": self.source_id,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ComputationalReflection:
|
|
58
|
+
"""
|
|
59
|
+
Computes actionable context from HtmlGraph history.
|
|
60
|
+
|
|
61
|
+
This class addresses the core problem: LLMs can retrieve data but
|
|
62
|
+
struggle to synthesize insights from complex graph structures.
|
|
63
|
+
We do the synthesis here and inject computed results.
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
>>> sdk = SDK(agent="claude")
|
|
67
|
+
>>> reflection = ComputationalReflection(sdk)
|
|
68
|
+
>>> context = reflection.get_actionable_context()
|
|
69
|
+
>>> print(context["summary"])
|
|
70
|
+
"2 blockers | Avoid: Edit-Edit-Edit | Related: spk-abc123"
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
MAX_ITEMS = 5 # LLM working memory limit
|
|
74
|
+
LOOKBACK_HOURS = 48 # How far back to look for patterns
|
|
75
|
+
|
|
76
|
+
def __init__(self, sdk: SDK):
|
|
77
|
+
self.sdk = sdk
|
|
78
|
+
self._cache: dict[str, Any] = {}
|
|
79
|
+
self._cache_time: datetime | None = None
|
|
80
|
+
self._cache_ttl = timedelta(minutes=5)
|
|
81
|
+
|
|
82
|
+
def get_actionable_context(
|
|
83
|
+
self,
|
|
84
|
+
current_feature_id: str | None = None,
|
|
85
|
+
current_track: str | None = None,
|
|
86
|
+
) -> dict[str, Any]:
|
|
87
|
+
"""
|
|
88
|
+
Get pre-computed actionable context for injection.
|
|
89
|
+
|
|
90
|
+
This is the main entry point. Returns a structured dict
|
|
91
|
+
suitable for injection into SessionStart or PreToolUse hooks.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
current_feature_id: ID of feature being worked on (if any)
|
|
95
|
+
current_track: Track name for filtering relevant history
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Dict with summary string and list of max 5 items
|
|
99
|
+
"""
|
|
100
|
+
# Check cache
|
|
101
|
+
if self._cache_time and datetime.now() - self._cache_time < self._cache_ttl:
|
|
102
|
+
return self._cache
|
|
103
|
+
|
|
104
|
+
items: list[ReflectionItem] = []
|
|
105
|
+
|
|
106
|
+
# 1. Get blocking items (highest priority)
|
|
107
|
+
items.extend(self._get_blockers(current_feature_id))
|
|
108
|
+
|
|
109
|
+
# 2. Get recent failures
|
|
110
|
+
items.extend(self._get_recent_failures(current_track))
|
|
111
|
+
|
|
112
|
+
# 3. Get anti-patterns to avoid
|
|
113
|
+
items.extend(self._get_anti_patterns())
|
|
114
|
+
|
|
115
|
+
# 4. Get related spikes (investigations)
|
|
116
|
+
items.extend(self._get_related_spikes(current_feature_id, current_track))
|
|
117
|
+
|
|
118
|
+
# 5. Get strategic recommendations
|
|
119
|
+
items.extend(self._get_recommendations())
|
|
120
|
+
|
|
121
|
+
# Sort by priority (highest first) and limit to MAX_ITEMS
|
|
122
|
+
items.sort(key=lambda x: x.priority, reverse=True)
|
|
123
|
+
items = items[: self.MAX_ITEMS]
|
|
124
|
+
|
|
125
|
+
# Build summary string
|
|
126
|
+
summary = self._build_summary(items)
|
|
127
|
+
|
|
128
|
+
result = {
|
|
129
|
+
"summary": summary,
|
|
130
|
+
"items": [item.to_dict() for item in items],
|
|
131
|
+
"injected_at": datetime.now().isoformat(),
|
|
132
|
+
"item_count": len(items),
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# Cache result
|
|
136
|
+
self._cache = result
|
|
137
|
+
self._cache_time = datetime.now()
|
|
138
|
+
|
|
139
|
+
return result
|
|
140
|
+
|
|
141
|
+
def _get_blockers(self, feature_id: str | None) -> list[ReflectionItem]:
|
|
142
|
+
"""Get items blocking current work."""
|
|
143
|
+
items = []
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
# Use SDK's find_bottlenecks
|
|
147
|
+
bottlenecks = self.sdk.find_bottlenecks(top_n=3)
|
|
148
|
+
|
|
149
|
+
for bn in bottlenecks[:2]: # Max 2 blockers
|
|
150
|
+
items.append(
|
|
151
|
+
ReflectionItem(
|
|
152
|
+
category="blocker",
|
|
153
|
+
priority=5, # Highest priority
|
|
154
|
+
title=f"Blocker: {bn.get('title', 'Unknown')[:40]}",
|
|
155
|
+
detail=f"Blocks {bn.get('blocks_count', 0)} items. Resolve first.",
|
|
156
|
+
source_id=bn.get("id"),
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
except Exception:
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
# Also check for features marked as blocking
|
|
163
|
+
try:
|
|
164
|
+
blocked = self.sdk.features.where(status="blocked")
|
|
165
|
+
for feat in blocked[:1]: # Max 1 blocked feature
|
|
166
|
+
items.append(
|
|
167
|
+
ReflectionItem(
|
|
168
|
+
category="blocker",
|
|
169
|
+
priority=4,
|
|
170
|
+
title=f"Blocked: {feat.title[:40]}",
|
|
171
|
+
detail="Feature is blocked. Check dependencies.",
|
|
172
|
+
source_id=feat.id,
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
except Exception:
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
return items
|
|
179
|
+
|
|
180
|
+
def _get_recent_failures(self, track: str | None) -> list[ReflectionItem]:
|
|
181
|
+
"""Get recent failures from session history."""
|
|
182
|
+
items = []
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
# Get recent sessions
|
|
186
|
+
sessions = self.sdk.sessions.all()
|
|
187
|
+
cutoff = datetime.now() - timedelta(hours=self.LOOKBACK_HOURS)
|
|
188
|
+
|
|
189
|
+
recent_sessions = [
|
|
190
|
+
s
|
|
191
|
+
for s in sessions
|
|
192
|
+
if hasattr(s, "started_at") and s.started_at and s.started_at > cutoff
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
# Look for error patterns in session activity
|
|
196
|
+
for session in recent_sessions[-3:]: # Last 3 sessions
|
|
197
|
+
if hasattr(session, "activity_log") and session.activity_log:
|
|
198
|
+
for activity in session.activity_log:
|
|
199
|
+
success = (
|
|
200
|
+
activity.success
|
|
201
|
+
if not isinstance(activity, dict)
|
|
202
|
+
else activity.get("success", True)
|
|
203
|
+
)
|
|
204
|
+
if not success:
|
|
205
|
+
tool = (
|
|
206
|
+
activity.tool
|
|
207
|
+
if not isinstance(activity, dict)
|
|
208
|
+
else activity.get("tool", "")
|
|
209
|
+
)
|
|
210
|
+
summary = (
|
|
211
|
+
activity.summary
|
|
212
|
+
if not isinstance(activity, dict)
|
|
213
|
+
else activity.get("summary", "")
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
items.append(
|
|
217
|
+
ReflectionItem(
|
|
218
|
+
category="failure",
|
|
219
|
+
priority=4,
|
|
220
|
+
title=f"Recent failure: {tool}",
|
|
221
|
+
detail=summary[:60]
|
|
222
|
+
if summary
|
|
223
|
+
else "Check session log",
|
|
224
|
+
source_id=session.id,
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
break # One failure per session max
|
|
228
|
+
except Exception:
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
return items[:2] # Max 2 failures
|
|
232
|
+
|
|
233
|
+
def _get_anti_patterns(self) -> list[ReflectionItem]:
|
|
234
|
+
"""Get anti-patterns to avoid from recent sessions."""
|
|
235
|
+
items = []
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
# Import learning module for pattern analysis
|
|
239
|
+
from htmlgraph.learning import LearningPersistence
|
|
240
|
+
|
|
241
|
+
learning = LearningPersistence(self.sdk)
|
|
242
|
+
|
|
243
|
+
# Get active session for analysis
|
|
244
|
+
active_sessions = [
|
|
245
|
+
s for s in self.sdk.sessions.all() if s.status == "active"
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
if active_sessions:
|
|
249
|
+
# Analyze most recent active session
|
|
250
|
+
session = active_sessions[-1]
|
|
251
|
+
analysis = learning.analyze_for_orchestrator(session.id)
|
|
252
|
+
|
|
253
|
+
# Extract anti-patterns
|
|
254
|
+
for pattern in analysis.get("anti_patterns", [])[:1]:
|
|
255
|
+
seq = pattern.get("sequence", [])
|
|
256
|
+
desc = pattern.get("description", "")
|
|
257
|
+
items.append(
|
|
258
|
+
ReflectionItem(
|
|
259
|
+
category="anti_pattern",
|
|
260
|
+
priority=3,
|
|
261
|
+
title=f"Avoid: {' → '.join(seq)}",
|
|
262
|
+
detail=desc[:60]
|
|
263
|
+
if desc
|
|
264
|
+
else "Detected inefficient pattern",
|
|
265
|
+
source_id=session.id,
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
except Exception:
|
|
269
|
+
pass
|
|
270
|
+
|
|
271
|
+
return items[:1] # Max 1 anti-pattern
|
|
272
|
+
|
|
273
|
+
def _get_related_spikes(
|
|
274
|
+
self, feature_id: str | None, track: str | None
|
|
275
|
+
) -> list[ReflectionItem]:
|
|
276
|
+
"""Get related investigation spikes."""
|
|
277
|
+
items = []
|
|
278
|
+
|
|
279
|
+
try:
|
|
280
|
+
spikes = self.sdk.spikes.all()
|
|
281
|
+
|
|
282
|
+
# Find spikes with findings that might be relevant
|
|
283
|
+
relevant_spikes = []
|
|
284
|
+
|
|
285
|
+
for spike in spikes:
|
|
286
|
+
# Check if spike has findings
|
|
287
|
+
if not hasattr(spike, "findings") or not spike.findings:
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
# Check if spike is related to current feature
|
|
291
|
+
if feature_id and hasattr(spike, "edges") and spike.edges:
|
|
292
|
+
for edge_type, edges in spike.edges.items():
|
|
293
|
+
for edge in edges:
|
|
294
|
+
if edge.target_id == feature_id:
|
|
295
|
+
relevant_spikes.append((spike, 5)) # High relevance
|
|
296
|
+
break
|
|
297
|
+
|
|
298
|
+
# Check if spike mentions the track
|
|
299
|
+
if track and track.lower() in (spike.title or "").lower():
|
|
300
|
+
relevant_spikes.append((spike, 3)) # Medium relevance
|
|
301
|
+
|
|
302
|
+
# Check for recent completed spikes with findings
|
|
303
|
+
if spike.status == "done" and spike.findings:
|
|
304
|
+
if hasattr(spike, "updated") and spike.updated:
|
|
305
|
+
cutoff = datetime.now() - timedelta(hours=24)
|
|
306
|
+
if spike.updated > cutoff:
|
|
307
|
+
relevant_spikes.append((spike, 2)) # Lower relevance
|
|
308
|
+
|
|
309
|
+
# Sort by relevance and take top
|
|
310
|
+
relevant_spikes.sort(key=lambda x: x[1], reverse=True)
|
|
311
|
+
|
|
312
|
+
for spike, relevance in relevant_spikes[:1]:
|
|
313
|
+
findings_preview = spike.findings[:60] if spike.findings else ""
|
|
314
|
+
items.append(
|
|
315
|
+
ReflectionItem(
|
|
316
|
+
category="spike",
|
|
317
|
+
priority=2,
|
|
318
|
+
title=f"Related: {spike.title[:35]}",
|
|
319
|
+
detail=findings_preview or "See spike for details",
|
|
320
|
+
source_id=spike.id,
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
except Exception:
|
|
324
|
+
pass
|
|
325
|
+
|
|
326
|
+
return items[:1] # Max 1 spike
|
|
327
|
+
|
|
328
|
+
def _get_recommendations(self) -> list[ReflectionItem]:
|
|
329
|
+
"""Get strategic recommendations."""
|
|
330
|
+
items = []
|
|
331
|
+
|
|
332
|
+
try:
|
|
333
|
+
recs = self.sdk.recommend_next_work(agent_count=1)
|
|
334
|
+
|
|
335
|
+
if recs and len(recs) > 0:
|
|
336
|
+
rec = recs[0]
|
|
337
|
+
reasons = rec.get("reasons", [])
|
|
338
|
+
reason_str = reasons[0] if reasons else "Recommended next"
|
|
339
|
+
|
|
340
|
+
items.append(
|
|
341
|
+
ReflectionItem(
|
|
342
|
+
category="recommendation",
|
|
343
|
+
priority=2,
|
|
344
|
+
title=f"Next: {rec.get('title', 'Unknown')[:35]}",
|
|
345
|
+
detail=reason_str[:60],
|
|
346
|
+
source_id=rec.get("id"),
|
|
347
|
+
)
|
|
348
|
+
)
|
|
349
|
+
except Exception:
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
return items[:1] # Max 1 recommendation
|
|
353
|
+
|
|
354
|
+
def _build_summary(self, items: list[ReflectionItem]) -> str:
|
|
355
|
+
"""Build a one-line summary from items."""
|
|
356
|
+
if not items:
|
|
357
|
+
return "No actionable context found."
|
|
358
|
+
|
|
359
|
+
parts = []
|
|
360
|
+
|
|
361
|
+
# Count by category
|
|
362
|
+
blockers = [i for i in items if i.category == "blocker"]
|
|
363
|
+
failures = [i for i in items if i.category == "failure"]
|
|
364
|
+
anti_patterns = [i for i in items if i.category == "anti_pattern"]
|
|
365
|
+
spikes = [i for i in items if i.category == "spike"]
|
|
366
|
+
|
|
367
|
+
if blockers:
|
|
368
|
+
parts.append(f"{len(blockers)} blocker{'s' if len(blockers) > 1 else ''}")
|
|
369
|
+
|
|
370
|
+
if failures:
|
|
371
|
+
parts.append(
|
|
372
|
+
f"{len(failures)} recent failure{'s' if len(failures) > 1 else ''}"
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
if anti_patterns:
|
|
376
|
+
pattern = anti_patterns[0]
|
|
377
|
+
parts.append(f"Avoid: {pattern.title.replace('Avoid: ', '')}")
|
|
378
|
+
|
|
379
|
+
if spikes:
|
|
380
|
+
spike = spikes[0]
|
|
381
|
+
parts.append(f"See: {spike.source_id}")
|
|
382
|
+
|
|
383
|
+
return " | ".join(parts) if parts else "Session context loaded."
|
|
384
|
+
|
|
385
|
+
def format_for_injection(self, context: dict[str, Any] | None = None) -> str:
|
|
386
|
+
"""
|
|
387
|
+
Format context for injection into hooks.
|
|
388
|
+
|
|
389
|
+
Returns a markdown-formatted string suitable for additionalContext.
|
|
390
|
+
"""
|
|
391
|
+
if context is None:
|
|
392
|
+
context = self.get_actionable_context()
|
|
393
|
+
|
|
394
|
+
if not context.get("items"):
|
|
395
|
+
return ""
|
|
396
|
+
|
|
397
|
+
lines = ["## Computed Reflections", ""]
|
|
398
|
+
lines.append(f"**Summary:** {context.get('summary', 'N/A')}")
|
|
399
|
+
lines.append("")
|
|
400
|
+
|
|
401
|
+
for item in context.get("items", []):
|
|
402
|
+
icon = {
|
|
403
|
+
"blocker": "🚫",
|
|
404
|
+
"failure": "❌",
|
|
405
|
+
"anti_pattern": "⚠️",
|
|
406
|
+
"spike": "🔍",
|
|
407
|
+
"recommendation": "💡",
|
|
408
|
+
}.get(item.get("category", ""), "•")
|
|
409
|
+
|
|
410
|
+
lines.append(f"{icon} **{item.get('title', 'Unknown')}**")
|
|
411
|
+
lines.append(f" {item.get('detail', '')}")
|
|
412
|
+
if item.get("source_id"):
|
|
413
|
+
lines.append(f" _Source: {item.get('source_id')}_")
|
|
414
|
+
lines.append("")
|
|
415
|
+
|
|
416
|
+
lines.append("---")
|
|
417
|
+
lines.append("")
|
|
418
|
+
|
|
419
|
+
return "\n".join(lines)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def get_reflection_context(
|
|
423
|
+
sdk: SDK,
|
|
424
|
+
feature_id: str | None = None,
|
|
425
|
+
track: str | None = None,
|
|
426
|
+
) -> str:
|
|
427
|
+
"""
|
|
428
|
+
Convenience function to get formatted reflection context.
|
|
429
|
+
|
|
430
|
+
This is the main entry point for hooks.
|
|
431
|
+
|
|
432
|
+
Args:
|
|
433
|
+
sdk: HtmlGraph SDK instance
|
|
434
|
+
feature_id: Current feature ID (optional)
|
|
435
|
+
track: Current track name (optional)
|
|
436
|
+
|
|
437
|
+
Returns:
|
|
438
|
+
Formatted string for injection into hook context
|
|
439
|
+
"""
|
|
440
|
+
reflection = ComputationalReflection(sdk)
|
|
441
|
+
context = reflection.get_actionable_context(feature_id, track)
|
|
442
|
+
return reflection.format_for_injection(context)
|
htmlgraph/sdk.py
CHANGED
|
@@ -39,6 +39,7 @@ Example:
|
|
|
39
39
|
|
|
40
40
|
from __future__ import annotations
|
|
41
41
|
|
|
42
|
+
import os
|
|
42
43
|
from pathlib import Path
|
|
43
44
|
from typing import Any
|
|
44
45
|
|
|
@@ -201,13 +202,19 @@ class SDK:
|
|
|
201
202
|
sdk.epics.assign(["epic-001"], agent="claude")
|
|
202
203
|
"""
|
|
203
204
|
|
|
204
|
-
def __init__(
|
|
205
|
+
def __init__(
|
|
206
|
+
self,
|
|
207
|
+
directory: Path | str | None = None,
|
|
208
|
+
agent: str | None = None,
|
|
209
|
+
parent_session: str | None = None,
|
|
210
|
+
):
|
|
205
211
|
"""
|
|
206
212
|
Initialize SDK.
|
|
207
213
|
|
|
208
214
|
Args:
|
|
209
215
|
directory: Path to .htmlgraph directory (auto-discovered if not provided)
|
|
210
216
|
agent: Agent identifier for operations
|
|
217
|
+
parent_session: Parent session ID to log activities to (for nested contexts)
|
|
211
218
|
"""
|
|
212
219
|
if directory is None:
|
|
213
220
|
directory = self._discover_htmlgraph()
|
|
@@ -217,6 +224,7 @@ class SDK:
|
|
|
217
224
|
|
|
218
225
|
self._directory = Path(directory)
|
|
219
226
|
self._agent_id = agent
|
|
227
|
+
self._parent_session = parent_session or os.getenv("HTMLGRAPH_PARENT_SESSION")
|
|
220
228
|
|
|
221
229
|
# Initialize underlying HtmlGraphs first (for backward compatibility and sharing)
|
|
222
230
|
# These are shared with SessionManager to avoid double-loading features
|
|
@@ -541,7 +549,7 @@ class SDK:
|
|
|
541
549
|
file_paths: Files involved in this activity
|
|
542
550
|
success: Whether the tool call succeeded
|
|
543
551
|
feature_id: Explicit feature ID (skips attribution if provided)
|
|
544
|
-
session_id: Session ID (defaults to
|
|
552
|
+
session_id: Session ID (defaults to parent session if available, then active session)
|
|
545
553
|
parent_activity_id: ID of parent activity (e.g., Skill/Task invocation)
|
|
546
554
|
payload: Optional rich payload data
|
|
547
555
|
|
|
@@ -558,14 +566,23 @@ class SDK:
|
|
|
558
566
|
... )
|
|
559
567
|
>>> print(f"Tracked: [{entry.tool}] {entry.summary}")
|
|
560
568
|
"""
|
|
561
|
-
#
|
|
569
|
+
# Determine target session: explicit > parent > active
|
|
562
570
|
if not session_id:
|
|
563
|
-
|
|
564
|
-
if
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
571
|
+
# Use parent session if available (for nested contexts)
|
|
572
|
+
if self._parent_session:
|
|
573
|
+
session_id = self._parent_session
|
|
574
|
+
else:
|
|
575
|
+
# Fall back to active session
|
|
576
|
+
active = self.session_manager.get_active_session(agent=self._agent_id)
|
|
577
|
+
if not active:
|
|
578
|
+
raise ValueError(
|
|
579
|
+
"No active session. Start one with sdk.start_session()"
|
|
580
|
+
)
|
|
581
|
+
session_id = active.id
|
|
582
|
+
|
|
583
|
+
# Get parent activity ID from environment if not provided
|
|
584
|
+
if not parent_activity_id:
|
|
585
|
+
parent_activity_id = os.getenv("HTMLGRAPH_PARENT_ACTIVITY")
|
|
569
586
|
|
|
570
587
|
return self.session_manager.track_activity(
|
|
571
588
|
session_id=session_id,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: htmlgraph
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.24.1
|
|
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
|
|
@@ -32,6 +32,7 @@ Requires-Dist: black>=23.0.0; extra == 'dev'
|
|
|
32
32
|
Requires-Dist: invoke>=2.2.0; extra == 'dev'
|
|
33
33
|
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
34
34
|
Requires-Dist: pdoc>=14.0.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
35
36
|
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
36
37
|
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
37
38
|
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
htmlgraph/__init__.py,sha256=
|
|
1
|
+
htmlgraph/__init__.py,sha256=2J36CrlL9ZJVYngE0zXFK2OGThAO0bY2tHtmntl5qL0,5185
|
|
2
2
|
htmlgraph/agent_detection.py,sha256=PAYo7rU3N_y1cGRd7Dwjh5Wgu-QZ7ENblX_yOzU-gJ0,2749
|
|
3
3
|
htmlgraph/agent_registry.py,sha256=Usa_35by7p5gtpvHO7K3AcGimnorw-FzgPVa3cWTQ58,9448
|
|
4
4
|
htmlgraph/agents.py,sha256=Yvu6x1nOfrW2WhRTAHiCuSpvqoVJXx1Mkzd59kwEczw,33466
|
|
5
5
|
htmlgraph/analytics_index.py,sha256=ba6Y4H_NNOCxI_Z4U7wSgBFFairf4IJT74WcM1PoZuI,30594
|
|
6
6
|
htmlgraph/attribute_index.py,sha256=cBZUV4YfGnhh6lF59aYPCdNrRr1hK__BzSKCueSDUhQ,6593
|
|
7
|
-
htmlgraph/cli.py,sha256
|
|
7
|
+
htmlgraph/cli.py,sha256=-v3VBsf0IZwNQ-7ASkp_1jJHDrsFQKIkHzbtU-K66Ts,216118
|
|
8
8
|
htmlgraph/context_analytics.py,sha256=CaLu0o2uSr6rlBM5YeaFZe7grgsy7_Hx10qdXuNcdao,11344
|
|
9
9
|
htmlgraph/converter.py,sha256=fhJF2h4YbrmL1wIa1jk7kGkT_CYGHeWp_5UzXitQ6HE,22747
|
|
10
10
|
htmlgraph/dashboard.html,sha256=rkZYjSnPbUuAm35QMpCNWemenYqQTdkkumCX2hhe8Dc,173537
|
|
@@ -23,8 +23,8 @@ htmlgraph/ids.py,sha256=ibEC8xW1ZHbAW6ImOKP2wLARgW7nzkxu8voce_hkljk,8389
|
|
|
23
23
|
htmlgraph/index.d.ts,sha256=7dvExfA16g1z5Kut8xyHnSUfZ6wiUUwWNy6R7WKiwas,6922
|
|
24
24
|
htmlgraph/learning.py,sha256=6SsRdz-xJGFPjp7YagpUDTZqqjNKp2wWihcnhwkHys0,28566
|
|
25
25
|
htmlgraph/mcp_server.py,sha256=AeJeGJEtX5Dqu5rfhKfT5kwF2Oe8V8xCaP8BgMEh86s,24033
|
|
26
|
-
htmlgraph/models.py,sha256=
|
|
27
|
-
htmlgraph/orchestrator-system-prompt-optimized.txt,sha256=
|
|
26
|
+
htmlgraph/models.py,sha256=Zs4aXDv9TRNqkFxib_TKgMpiygHak1mV1TAM0doZJqI,83045
|
|
27
|
+
htmlgraph/orchestrator-system-prompt-optimized.txt,sha256=O9GwHwitLyYMAbNuNsPVVG4UtHGC7uPnmIhUTfFj-PY,9671
|
|
28
28
|
htmlgraph/orchestrator.py,sha256=6mj70vroWjmNmdvQ7jqqRSA9O1rFUNMUYDWPzqkizLk,19697
|
|
29
29
|
htmlgraph/orchestrator_mode.py,sha256=F6LNZARqieQXUri3CRSq_lsqFbnVeGXJQPno1ZP47O4,9187
|
|
30
30
|
htmlgraph/orchestrator_validator.py,sha256=gd_KbHsRsNEIF7EElwcxbMYqOMlyeuYIZwClASp-L-E,4699
|
|
@@ -32,8 +32,9 @@ htmlgraph/parallel.py,sha256=BsyqGKWY_DkSRElBdvvAkWlL6stC9BPkyxjdPdggx_w,22418
|
|
|
32
32
|
htmlgraph/parser.py,sha256=mPcQ3WQdDCpflhjk8397TPbHnQcU3SzefNk-JE406UA,16570
|
|
33
33
|
htmlgraph/planning.py,sha256=iqPF9mCVQwOfJ4vuqcF2Y3-yhx9koJZw0cID7CknIug,35903
|
|
34
34
|
htmlgraph/query_builder.py,sha256=aNtJ05GpGl9yUSSrX0D6pX_AgqlrrH-CulI_oP11PUk,18092
|
|
35
|
+
htmlgraph/reflection.py,sha256=j5oclTrmRGEFgGCPHh93QSasEG2IMAFdFXRj7tP-S2c,15662
|
|
35
36
|
htmlgraph/routing.py,sha256=QYDY6bzYPmv6kocAXCqguB1cazN0i_xTo9EVCO3fO2Y,8803
|
|
36
|
-
htmlgraph/sdk.py,sha256=
|
|
37
|
+
htmlgraph/sdk.py,sha256=GgQOXDiqpNtO0PTf88_taxLhJK4dLOjeGR5ExZw_F-4,101109
|
|
37
38
|
htmlgraph/server.py,sha256=F94rNpGSlgCg3Ax6kvzi7WjWFeHr7QerLxgmVknP4gA,52479
|
|
38
39
|
htmlgraph/session_manager.py,sha256=8_H29kN6Btii1RfzNpifjjUVTMU0cEeTElFsDC6icLM,89430
|
|
39
40
|
htmlgraph/session_warning.py,sha256=leAYNp8pOkPFosIvNkY4Y3vjs1j76F3pGktUqQX9tI0,7877
|
|
@@ -70,6 +71,16 @@ htmlgraph/builders/pattern.py,sha256=QIKHJ3raOjwAWj1KipWA5o9wxbFCZLPsnoF1CkuFbnE
|
|
|
70
71
|
htmlgraph/builders/phase.py,sha256=pvdG_ZiswzdRCBM1pz7hOoCS9MD-lwaOgPqVcuLXucs,3611
|
|
71
72
|
htmlgraph/builders/spike.py,sha256=_LXMDEJCdCjJPbNB8CA0FDlFDN-pZbdzvQDFXdrldo0,4327
|
|
72
73
|
htmlgraph/builders/track.py,sha256=tdolGgYQl3PvoX6jHNCq3Vh6USrb7uN3NX555SoRHGs,23097
|
|
74
|
+
htmlgraph/cigs/__init__.py,sha256=FQa1Tx0fnp7U0RgM-4A_WuZvuFjLC_qCgRt9KdFG3nU,1935
|
|
75
|
+
htmlgraph/cigs/autonomy.py,sha256=kq35CbzI9UV9h0Bzy8t9YIJwmHHM9KD381RUocQIlNs,14075
|
|
76
|
+
htmlgraph/cigs/cost.py,sha256=DcXsIoJt_IlGVSD0Unk8f9lJUwoEXiBZATFHOM2SdjY,17502
|
|
77
|
+
htmlgraph/cigs/messages_basic.py,sha256=QWaOmV6BLLkutrQGfX7B-RIEEy0dwGlUj1BuCKz2Shg,16422
|
|
78
|
+
htmlgraph/cigs/messaging.py,sha256=jx6Y9fvuFHkoP_g2C4ff54ymfJBsVPAjORq5XORHslA,12965
|
|
79
|
+
htmlgraph/cigs/models.py,sha256=QoCpaRsw8DzNzA9oPBcqr76ZMghxKtCJCVtL1zB5U6E,27643
|
|
80
|
+
htmlgraph/cigs/pattern_storage.py,sha256=7ZjLc62xEk5TwLUkd88MW02d_iLNkDffX3CFudR95mg,14201
|
|
81
|
+
htmlgraph/cigs/patterns.py,sha256=qshVy2jey3_MJaizYIagi_qWbSnShyLyHdB100vzjn8,18456
|
|
82
|
+
htmlgraph/cigs/posttool_analyzer.py,sha256=hVUXXVuvFhq-smc2JAst-gMlQWI7gXCXH9f91kBcSRs,8134
|
|
83
|
+
htmlgraph/cigs/tracker.py,sha256=OcyXY7tGxbRCtlLX8Pg2bVtCtzPJtvh1VOtt4jbL9SI,10502
|
|
73
84
|
htmlgraph/collections/__init__.py,sha256=qHT1UvHD-jCmNI4t1fWprWLw-5jE7NabN6kngboVRFY,1056
|
|
74
85
|
htmlgraph/collections/base.py,sha256=yK-1Kdh1wq3Ng7PkEzKX2UXlbqQl8257-9WIOfVGSmI,24721
|
|
75
86
|
htmlgraph/collections/bug.py,sha256=bDbZy_sdiIn7AK0b8CXs88mUEr1ZkOGVezG0aKwh0pw,1331
|
|
@@ -101,6 +112,7 @@ htmlgraph/extensions/gemini/hooks/scripts/post-tool.sh,sha256=z8lUt5qoyLylwCVqNT
|
|
|
101
112
|
htmlgraph/extensions/gemini/hooks/scripts/session-end.sh,sha256=yQzSKfxBzuMS-U_xcS_sbISrjQwnyM-E-KMu1LeRYAI,1095
|
|
102
113
|
htmlgraph/extensions/gemini/hooks/scripts/session-start.sh,sha256=thpCbfE8WD8huFU7mUMas-gWAk6BsHtYLaKlIHusNRg,2670
|
|
103
114
|
htmlgraph/hooks/__init__.py,sha256=jL2HyCoFWQQ8l-4-EAlypDxPalNE3JBfDyELYWAg-g0,865
|
|
115
|
+
htmlgraph/hooks/cigs_pretool_enforcer.py,sha256=q0v8KwRI0oLdfUZup95LSXJO-olokNwSY1qqTu4vWnM,12702
|
|
104
116
|
htmlgraph/hooks/event_tracker.py,sha256=KQcIWbhNJser6Tip87oUAPQJgUAAKESKE5ARQasLtCM,23301
|
|
105
117
|
htmlgraph/hooks/hooks-config.example.json,sha256=tXpk-U-FZzGOoNJK2uiDMbIHCYEHA794J-El0fBwkqg,197
|
|
106
118
|
htmlgraph/hooks/installer.py,sha256=nOctCFDEV7BEh7ZzxNY-apu1KZG0SHPMq74UPIOChqY,11756
|
|
@@ -110,11 +122,11 @@ htmlgraph/hooks/post-checkout.sh,sha256=Hsr5hqD54jisGbtqf7-Z-G_b6XNGcee_CZRYaKYz
|
|
|
110
122
|
htmlgraph/hooks/post-commit.sh,sha256=if65jNGZnEWsZPq_iYDNYunrZ1cmjPUEUbh6_4vfpOE,511
|
|
111
123
|
htmlgraph/hooks/post-merge.sh,sha256=gq-EeFLhDUVp-J2jyWMBVFcB0pdmH54Wu1SW_Gn-s2I,541
|
|
112
124
|
htmlgraph/hooks/post_tool_use_failure.py,sha256=DHkJtuAOg5KSLfFZ1O-kePwaqmtNkbGQSEn4NplzvD8,8381
|
|
113
|
-
htmlgraph/hooks/posttooluse.py,sha256=
|
|
125
|
+
htmlgraph/hooks/posttooluse.py,sha256=3usICpqlIdLKsijYY61BP2QezsB45MZ6evwFSobC9Yo,12822
|
|
114
126
|
htmlgraph/hooks/pre-commit.sh,sha256=gTpbnHIBFxpAl7-REhXoS0NI4Pmlqo9pQEMEngTAU_A,3865
|
|
115
127
|
htmlgraph/hooks/pre-push.sh,sha256=rNnkG8YmDtyk7OuJHOcbOYQR3MYFneaG6_w2X-Hl8Hs,660
|
|
116
128
|
htmlgraph/hooks/pretooluse.py,sha256=Q6wtU_IBIjCd22j7MvZrdd959TdAHx8OovIOnqkwCm0,9606
|
|
117
|
-
htmlgraph/hooks/task_enforcer.py,sha256=
|
|
129
|
+
htmlgraph/hooks/task_enforcer.py,sha256=NFyU1TiUoVZBl91AWiZwZc3JzugWxcfGbaFw2nuGHnA,6587
|
|
118
130
|
htmlgraph/hooks/task_validator.py,sha256=GwEoqL2lptPWQqckkfl0N-Auc7TtHiyRlOf6p7HcoIo,5438
|
|
119
131
|
htmlgraph/hooks/validator.py,sha256=cwul8fIGS-_hFKskrYC6Hjh0F9yP4_3ErAsyp_SL43c,17417
|
|
120
132
|
htmlgraph/operations/README.md,sha256=5h0P-aZ3po3h_7mUpuh7jJwOL-NEvtlBlPCbqzvLrIE,1730
|
|
@@ -124,7 +136,7 @@ htmlgraph/operations/events.py,sha256=vSuLj8IWtdfPMvoK6yDmDM3kh_SDOWU8Ofu5u4DF_T
|
|
|
124
136
|
htmlgraph/operations/hooks.py,sha256=_jRHbjrWNM1fjukEr0TBu6wn90PVfpXtJjicSITqP0A,10037
|
|
125
137
|
htmlgraph/operations/server.py,sha256=cxdSYZU10pNgiNfYk2T84D_40CAFjA9wSVsRfk9Ipm8,8746
|
|
126
138
|
htmlgraph/orchestration/__init__.py,sha256=a9NWy-zWG9T7f7V37ibahniF_enn-31gM4veEJkM8cw,844
|
|
127
|
-
htmlgraph/orchestration/headless_spawner.py,sha256=
|
|
139
|
+
htmlgraph/orchestration/headless_spawner.py,sha256=8B_y6NojAVwwaav25izAiKGApel61gAeVzDEcm_xP9k,40244
|
|
128
140
|
htmlgraph/orchestration/model_selection.py,sha256=2IArHkde5rKD56aj5Jw9I9Z5iLV0yOgw3QCmNF996DE,11311
|
|
129
141
|
htmlgraph/orchestration/task_coordination.py,sha256=7_oQ4AlHOv14hs6RvLsatJzF-F5gkIbv1EOrmeGPhiw,9699
|
|
130
142
|
htmlgraph/scripts/__init__.py,sha256=a_ef7jnypTH3fzjutKYtUquJospdbA6IOR4o9dgv8Gk,48
|
|
@@ -134,12 +146,12 @@ htmlgraph/services/claiming.py,sha256=HcrltEJKN72mxuD7fGuXWeh1U0vwhjMvhZcFc02Eiy
|
|
|
134
146
|
htmlgraph/templates/AGENTS.md.template,sha256=f96h7V6ygwj-v-fanVI48eYMxR6t_se4bet1H4ZsDpI,7642
|
|
135
147
|
htmlgraph/templates/CLAUDE.md.template,sha256=h1kG2hTX2XYig2KszsHBfzrwa_4Cfcq2Pj4SwqzeDlM,1984
|
|
136
148
|
htmlgraph/templates/GEMINI.md.template,sha256=gAGzE53Avki87BM_otqy5HdcYCoLsHgqaKjVzNzPMX8,1622
|
|
137
|
-
htmlgraph-0.
|
|
138
|
-
htmlgraph-0.
|
|
139
|
-
htmlgraph-0.
|
|
140
|
-
htmlgraph-0.
|
|
141
|
-
htmlgraph-0.
|
|
142
|
-
htmlgraph-0.
|
|
143
|
-
htmlgraph-0.
|
|
144
|
-
htmlgraph-0.
|
|
145
|
-
htmlgraph-0.
|
|
149
|
+
htmlgraph-0.24.1.data/data/htmlgraph/dashboard.html,sha256=rkZYjSnPbUuAm35QMpCNWemenYqQTdkkumCX2hhe8Dc,173537
|
|
150
|
+
htmlgraph-0.24.1.data/data/htmlgraph/styles.css,sha256=oDUSC8jG-V-hKojOBO9J88hxAeY2wJrBYTq0uCwX_Y4,7135
|
|
151
|
+
htmlgraph-0.24.1.data/data/htmlgraph/templates/AGENTS.md.template,sha256=f96h7V6ygwj-v-fanVI48eYMxR6t_se4bet1H4ZsDpI,7642
|
|
152
|
+
htmlgraph-0.24.1.data/data/htmlgraph/templates/CLAUDE.md.template,sha256=h1kG2hTX2XYig2KszsHBfzrwa_4Cfcq2Pj4SwqzeDlM,1984
|
|
153
|
+
htmlgraph-0.24.1.data/data/htmlgraph/templates/GEMINI.md.template,sha256=gAGzE53Avki87BM_otqy5HdcYCoLsHgqaKjVzNzPMX8,1622
|
|
154
|
+
htmlgraph-0.24.1.dist-info/METADATA,sha256=a0UZKmR2Kho_tfbBzgFiAnC4CmhkrmE3wkhRCQOLMcw,7881
|
|
155
|
+
htmlgraph-0.24.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
156
|
+
htmlgraph-0.24.1.dist-info/entry_points.txt,sha256=EaUbjA_bbDwEO_XDLEGMeK8aQP-ZnHiUTkLshyKDyB8,98
|
|
157
|
+
htmlgraph-0.24.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|