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.
Files changed (29) hide show
  1. htmlgraph/__init__.py +5 -1
  2. htmlgraph/cigs/__init__.py +77 -0
  3. htmlgraph/cigs/autonomy.py +385 -0
  4. htmlgraph/cigs/cost.py +475 -0
  5. htmlgraph/cigs/messages_basic.py +472 -0
  6. htmlgraph/cigs/messaging.py +365 -0
  7. htmlgraph/cigs/models.py +771 -0
  8. htmlgraph/cigs/pattern_storage.py +427 -0
  9. htmlgraph/cigs/patterns.py +503 -0
  10. htmlgraph/cigs/posttool_analyzer.py +234 -0
  11. htmlgraph/cigs/tracker.py +317 -0
  12. htmlgraph/cli.py +413 -11
  13. htmlgraph/hooks/cigs_pretool_enforcer.py +350 -0
  14. htmlgraph/hooks/posttooluse.py +50 -2
  15. htmlgraph/hooks/task_enforcer.py +60 -4
  16. htmlgraph/models.py +14 -1
  17. htmlgraph/orchestration/headless_spawner.py +519 -21
  18. htmlgraph/orchestrator-system-prompt-optimized.txt +259 -53
  19. htmlgraph/reflection.py +442 -0
  20. htmlgraph/sdk.py +26 -9
  21. {htmlgraph-0.23.5.dist-info → htmlgraph-0.24.1.dist-info}/METADATA +2 -1
  22. {htmlgraph-0.23.5.dist-info → htmlgraph-0.24.1.dist-info}/RECORD +29 -17
  23. {htmlgraph-0.23.5.data → htmlgraph-0.24.1.data}/data/htmlgraph/dashboard.html +0 -0
  24. {htmlgraph-0.23.5.data → htmlgraph-0.24.1.data}/data/htmlgraph/styles.css +0 -0
  25. {htmlgraph-0.23.5.data → htmlgraph-0.24.1.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  26. {htmlgraph-0.23.5.data → htmlgraph-0.24.1.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  27. {htmlgraph-0.23.5.data → htmlgraph-0.24.1.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  28. {htmlgraph-0.23.5.dist-info → htmlgraph-0.24.1.dist-info}/WHEEL +0 -0
  29. {htmlgraph-0.23.5.dist-info → htmlgraph-0.24.1.dist-info}/entry_points.txt +0 -0
@@ -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__(self, directory: Path | str | None = None, agent: str | None = None):
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 active session for current agent)
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
- # Find active session if not specified
569
+ # Determine target session: explicit > parent > active
562
570
  if not session_id:
563
- active = self.session_manager.get_active_session(agent=self._agent_id)
564
- if not active:
565
- raise ValueError(
566
- "No active session. Start one with sdk.start_session()"
567
- )
568
- session_id = active.id
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.23.5
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=mtIyVp_jFrQp7Vq9fQotsdbgzaXHZl8m31141-Surps,4979
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=CZC631eM4toy2l7fP63EaORRATlUa5sDmJgU98bFjKU,201518
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=vigJmC_MkKcHH362Yoauomy7fOQ0Xrn2XNASgCDEKK8,82362
27
- htmlgraph/orchestrator-system-prompt-optimized.txt,sha256=8UptB4-jmyz0OrjRm5VlJqBxzk3oXNJEgUQChuTWhR8,2885
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=jhKHAU7hFsKFawQQOYjwnM4FJ69HC9lOwCDRh2Qt9ho,100400
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=imrRl29qsywRQW0-ruBbJq-FGYKCkvgbjSvis3U5j50,11261
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=H7Gug843BTR5H4YfAgW8-b7rjICuAHLP9cw1iNfcHew,4510
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=VfMX1Ubf7N8AFF1poSGyT_yyoUPIYmzlxYJJuyWqP98,19446
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.23.5.data/data/htmlgraph/dashboard.html,sha256=rkZYjSnPbUuAm35QMpCNWemenYqQTdkkumCX2hhe8Dc,173537
138
- htmlgraph-0.23.5.data/data/htmlgraph/styles.css,sha256=oDUSC8jG-V-hKojOBO9J88hxAeY2wJrBYTq0uCwX_Y4,7135
139
- htmlgraph-0.23.5.data/data/htmlgraph/templates/AGENTS.md.template,sha256=f96h7V6ygwj-v-fanVI48eYMxR6t_se4bet1H4ZsDpI,7642
140
- htmlgraph-0.23.5.data/data/htmlgraph/templates/CLAUDE.md.template,sha256=h1kG2hTX2XYig2KszsHBfzrwa_4Cfcq2Pj4SwqzeDlM,1984
141
- htmlgraph-0.23.5.data/data/htmlgraph/templates/GEMINI.md.template,sha256=gAGzE53Avki87BM_otqy5HdcYCoLsHgqaKjVzNzPMX8,1622
142
- htmlgraph-0.23.5.dist-info/METADATA,sha256=n09K5uXhSh8VZGdMVKavkhUlZUf2ioh7Yk5Knkit4fw,7827
143
- htmlgraph-0.23.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
144
- htmlgraph-0.23.5.dist-info/entry_points.txt,sha256=EaUbjA_bbDwEO_XDLEGMeK8aQP-ZnHiUTkLshyKDyB8,98
145
- htmlgraph-0.23.5.dist-info/RECORD,,
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,,