htmlgraph 0.20.1__py3-none-any.whl → 0.27.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +51 -1
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +26 -10
- htmlgraph/agent_registry.py +2 -1
- htmlgraph/analytics/__init__.py +8 -1
- htmlgraph/analytics/cli.py +86 -20
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +10 -6
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +67 -27
- htmlgraph/analytics_index.py +53 -20
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +1366 -0
- htmlgraph/api/templates/dashboard.html +794 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +1100 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +578 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +2 -1
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/base.py +57 -2
- htmlgraph/builders/bug.py +19 -3
- htmlgraph/builders/chore.py +19 -3
- htmlgraph/builders/epic.py +19 -3
- htmlgraph/builders/feature.py +27 -3
- htmlgraph/builders/insight.py +2 -1
- htmlgraph/builders/metric.py +2 -1
- htmlgraph/builders/pattern.py +2 -1
- htmlgraph/builders/phase.py +19 -3
- htmlgraph/builders/spike.py +29 -3
- htmlgraph/builders/track.py +42 -1
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +2 -0
- htmlgraph/collections/base.py +197 -14
- htmlgraph/collections/bug.py +2 -1
- htmlgraph/collections/chore.py +2 -1
- htmlgraph/collections/epic.py +2 -1
- htmlgraph/collections/feature.py +2 -1
- htmlgraph/collections/insight.py +2 -1
- htmlgraph/collections/metric.py +2 -1
- htmlgraph/collections/pattern.py +2 -1
- htmlgraph/collections/phase.py +2 -1
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +13 -2
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +14 -1
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/converter.py +116 -7
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2246 -248
- htmlgraph/dashboard.html.backup +6592 -0
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +2 -1
- htmlgraph/deploy.py +26 -27
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +2 -1
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +86 -37
- htmlgraph/event_migration.py +2 -1
- htmlgraph/file_watcher.py +12 -8
- htmlgraph/find_api.py +2 -1
- htmlgraph/git_events.py +67 -9
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/hooks/.htmlgraph/agents.json +72 -0
- htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
- htmlgraph/hooks/__init__.py +8 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +790 -99
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/installer.py +5 -1
- htmlgraph/hooks/orchestrator.py +327 -76
- htmlgraph/hooks/orchestrator_reflector.py +31 -4
- htmlgraph/hooks/post_tool_use_failure.py +32 -7
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +92 -19
- htmlgraph/hooks/pretooluse.py +527 -7
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +99 -4
- htmlgraph/hooks/validator.py +212 -91
- htmlgraph/ids.py +2 -1
- htmlgraph/learning.py +125 -100
- htmlgraph/mcp_server.py +2 -1
- htmlgraph/models.py +217 -18
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +2 -1
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +115 -4
- htmlgraph/parallel.py +2 -1
- htmlgraph/parser.py +86 -6
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +2 -1
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +295 -107
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +285 -3
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +2 -1
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +33 -1
- htmlgraph/track_manager.py +38 -0
- htmlgraph/transcript.py +18 -5
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +2 -1
- htmlgraph/work_type_utils.py +2 -1
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -4839
- htmlgraph/sdk.py +0 -2359
- htmlgraph-0.20.1.dist-info/RECORD +0 -118
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
htmlgraph/reflection.py
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Computational Reflection Module.
|
|
5
|
+
|
|
6
|
+
Pre-computes actionable context from session history for injection into
|
|
7
|
+
orchestrator prompts. Addresses the LLM limitation where models can only
|
|
8
|
+
effectively track 5-10 variables in working memory.
|
|
9
|
+
|
|
10
|
+
Design Principles:
|
|
11
|
+
1. COMPUTE, don't prompt - Do the synthesis work here, not in prompts
|
|
12
|
+
2. LIMIT to 5 items - Respect LLM working memory constraints
|
|
13
|
+
3. PRIORITIZE by recency and relevance - Most actionable items first
|
|
14
|
+
4. CONNECT the dots - Surface relationships the model would miss
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
from htmlgraph.reflection import ComputationalReflection
|
|
18
|
+
|
|
19
|
+
reflection = ComputationalReflection(sdk)
|
|
20
|
+
context = reflection.get_actionable_context()
|
|
21
|
+
# Returns: {
|
|
22
|
+
# "summary": "3 blockers, 1 recent failure, avoid Read-Read-Read pattern",
|
|
23
|
+
# "items": [...], # Max 5 items
|
|
24
|
+
# "injected_at": "2025-01-04T12:00:00"
|
|
25
|
+
# }
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
from dataclasses import dataclass
|
|
30
|
+
from datetime import datetime, timedelta
|
|
31
|
+
from typing import TYPE_CHECKING, Any
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from htmlgraph.sdk import SDK
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class ReflectionItem:
|
|
39
|
+
"""A single actionable reflection item."""
|
|
40
|
+
|
|
41
|
+
category: str # "blocker", "failure", "anti_pattern", "spike", "recommendation"
|
|
42
|
+
priority: int # 1-5, higher = more important
|
|
43
|
+
title: str # Brief title
|
|
44
|
+
detail: str # One-line actionable detail
|
|
45
|
+
source_id: str | None = None # ID of source item (feature, spike, etc.)
|
|
46
|
+
|
|
47
|
+
def to_dict(self) -> dict[str, Any]:
|
|
48
|
+
"""Convert to dictionary for JSON serialization."""
|
|
49
|
+
return {
|
|
50
|
+
"category": self.category,
|
|
51
|
+
"priority": self.priority,
|
|
52
|
+
"title": self.title,
|
|
53
|
+
"detail": self.detail,
|
|
54
|
+
"source_id": self.source_id,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ComputationalReflection:
|
|
59
|
+
"""
|
|
60
|
+
Computes actionable context from HtmlGraph history.
|
|
61
|
+
|
|
62
|
+
This class addresses the core problem: LLMs can retrieve data but
|
|
63
|
+
struggle to synthesize insights from complex graph structures.
|
|
64
|
+
We do the synthesis here and inject computed results.
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
>>> sdk = SDK(agent="claude")
|
|
68
|
+
>>> reflection = ComputationalReflection(sdk)
|
|
69
|
+
>>> context = reflection.get_actionable_context()
|
|
70
|
+
>>> print(context["summary"])
|
|
71
|
+
"2 blockers | Avoid: Edit-Edit-Edit | Related: spk-abc123"
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
MAX_ITEMS = 5 # LLM working memory limit
|
|
75
|
+
LOOKBACK_HOURS = 48 # How far back to look for patterns
|
|
76
|
+
|
|
77
|
+
def __init__(self, sdk: SDK):
|
|
78
|
+
self.sdk = sdk
|
|
79
|
+
self._cache: dict[str, Any] = {}
|
|
80
|
+
self._cache_time: datetime | None = None
|
|
81
|
+
self._cache_ttl = timedelta(minutes=5)
|
|
82
|
+
|
|
83
|
+
def get_actionable_context(
|
|
84
|
+
self,
|
|
85
|
+
current_feature_id: str | None = None,
|
|
86
|
+
current_track: str | None = None,
|
|
87
|
+
) -> dict[str, Any]:
|
|
88
|
+
"""
|
|
89
|
+
Get pre-computed actionable context for injection.
|
|
90
|
+
|
|
91
|
+
This is the main entry point. Returns a structured dict
|
|
92
|
+
suitable for injection into SessionStart or PreToolUse hooks.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
current_feature_id: ID of feature being worked on (if any)
|
|
96
|
+
current_track: Track name for filtering relevant history
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Dict with summary string and list of max 5 items
|
|
100
|
+
"""
|
|
101
|
+
# Check cache
|
|
102
|
+
if self._cache_time and datetime.now() - self._cache_time < self._cache_ttl:
|
|
103
|
+
return self._cache
|
|
104
|
+
|
|
105
|
+
items: list[ReflectionItem] = []
|
|
106
|
+
|
|
107
|
+
# 1. Get blocking items (highest priority)
|
|
108
|
+
items.extend(self._get_blockers(current_feature_id))
|
|
109
|
+
|
|
110
|
+
# 2. Get recent failures
|
|
111
|
+
items.extend(self._get_recent_failures(current_track))
|
|
112
|
+
|
|
113
|
+
# 3. Get anti-patterns to avoid
|
|
114
|
+
items.extend(self._get_anti_patterns())
|
|
115
|
+
|
|
116
|
+
# 4. Get related spikes (investigations)
|
|
117
|
+
items.extend(self._get_related_spikes(current_feature_id, current_track))
|
|
118
|
+
|
|
119
|
+
# 5. Get strategic recommendations
|
|
120
|
+
items.extend(self._get_recommendations())
|
|
121
|
+
|
|
122
|
+
# Sort by priority (highest first) and limit to MAX_ITEMS
|
|
123
|
+
items.sort(key=lambda x: x.priority, reverse=True)
|
|
124
|
+
items = items[: self.MAX_ITEMS]
|
|
125
|
+
|
|
126
|
+
# Build summary string
|
|
127
|
+
summary = self._build_summary(items)
|
|
128
|
+
|
|
129
|
+
result = {
|
|
130
|
+
"summary": summary,
|
|
131
|
+
"items": [item.to_dict() for item in items],
|
|
132
|
+
"injected_at": datetime.now().isoformat(),
|
|
133
|
+
"item_count": len(items),
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# Cache result
|
|
137
|
+
self._cache = result
|
|
138
|
+
self._cache_time = datetime.now()
|
|
139
|
+
|
|
140
|
+
return result
|
|
141
|
+
|
|
142
|
+
def _get_blockers(self, feature_id: str | None) -> list[ReflectionItem]:
|
|
143
|
+
"""Get items blocking current work."""
|
|
144
|
+
items = []
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
# Use SDK's find_bottlenecks
|
|
148
|
+
bottlenecks = self.sdk.find_bottlenecks(top_n=3)
|
|
149
|
+
|
|
150
|
+
for bn in bottlenecks[:2]: # Max 2 blockers
|
|
151
|
+
items.append(
|
|
152
|
+
ReflectionItem(
|
|
153
|
+
category="blocker",
|
|
154
|
+
priority=5, # Highest priority
|
|
155
|
+
title=f"Blocker: {bn.get('title', 'Unknown')[:40]}",
|
|
156
|
+
detail=f"Blocks {bn.get('blocks_count', 0)} items. Resolve first.",
|
|
157
|
+
source_id=bn.get("id"),
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
except Exception:
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
# Also check for features marked as blocking
|
|
164
|
+
try:
|
|
165
|
+
blocked = self.sdk.features.where(status="blocked")
|
|
166
|
+
for feat in blocked[:1]: # Max 1 blocked feature
|
|
167
|
+
items.append(
|
|
168
|
+
ReflectionItem(
|
|
169
|
+
category="blocker",
|
|
170
|
+
priority=4,
|
|
171
|
+
title=f"Blocked: {feat.title[:40]}",
|
|
172
|
+
detail="Feature is blocked. Check dependencies.",
|
|
173
|
+
source_id=feat.id,
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
except Exception:
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
return items
|
|
180
|
+
|
|
181
|
+
def _get_recent_failures(self, track: str | None) -> list[ReflectionItem]:
|
|
182
|
+
"""Get recent failures from session history."""
|
|
183
|
+
items = []
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
# Get recent sessions
|
|
187
|
+
sessions = self.sdk.sessions.all()
|
|
188
|
+
cutoff = datetime.now() - timedelta(hours=self.LOOKBACK_HOURS)
|
|
189
|
+
|
|
190
|
+
recent_sessions = [
|
|
191
|
+
s
|
|
192
|
+
for s in sessions
|
|
193
|
+
if hasattr(s, "started_at") and s.started_at and s.started_at > cutoff
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
# Look for error patterns in session activity
|
|
197
|
+
for session in recent_sessions[-3:]: # Last 3 sessions
|
|
198
|
+
if hasattr(session, "activity_log") and session.activity_log:
|
|
199
|
+
for activity in session.activity_log:
|
|
200
|
+
success = (
|
|
201
|
+
activity.success
|
|
202
|
+
if not isinstance(activity, dict)
|
|
203
|
+
else activity.get("success", True)
|
|
204
|
+
)
|
|
205
|
+
if not success:
|
|
206
|
+
tool = (
|
|
207
|
+
activity.tool
|
|
208
|
+
if not isinstance(activity, dict)
|
|
209
|
+
else activity.get("tool", "")
|
|
210
|
+
)
|
|
211
|
+
summary = (
|
|
212
|
+
activity.summary
|
|
213
|
+
if not isinstance(activity, dict)
|
|
214
|
+
else activity.get("summary", "")
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
items.append(
|
|
218
|
+
ReflectionItem(
|
|
219
|
+
category="failure",
|
|
220
|
+
priority=4,
|
|
221
|
+
title=f"Recent failure: {tool}",
|
|
222
|
+
detail=summary[:60]
|
|
223
|
+
if summary
|
|
224
|
+
else "Check session log",
|
|
225
|
+
source_id=session.id,
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
break # One failure per session max
|
|
229
|
+
except Exception:
|
|
230
|
+
pass
|
|
231
|
+
|
|
232
|
+
return items[:2] # Max 2 failures
|
|
233
|
+
|
|
234
|
+
def _get_anti_patterns(self) -> list[ReflectionItem]:
|
|
235
|
+
"""Get anti-patterns to avoid from recent sessions."""
|
|
236
|
+
items = []
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
# Import learning module for pattern analysis
|
|
240
|
+
from htmlgraph.learning import LearningPersistence
|
|
241
|
+
|
|
242
|
+
learning = LearningPersistence(self.sdk)
|
|
243
|
+
|
|
244
|
+
# Get active session for analysis
|
|
245
|
+
active_sessions = [
|
|
246
|
+
s for s in self.sdk.sessions.all() if s.status == "active"
|
|
247
|
+
]
|
|
248
|
+
|
|
249
|
+
if active_sessions:
|
|
250
|
+
# Analyze most recent active session
|
|
251
|
+
session = active_sessions[-1]
|
|
252
|
+
analysis = learning.analyze_for_orchestrator(session.id)
|
|
253
|
+
|
|
254
|
+
# Extract anti-patterns
|
|
255
|
+
for pattern in analysis.get("anti_patterns", [])[:1]:
|
|
256
|
+
seq = pattern.get("sequence", [])
|
|
257
|
+
desc = pattern.get("description", "")
|
|
258
|
+
items.append(
|
|
259
|
+
ReflectionItem(
|
|
260
|
+
category="anti_pattern",
|
|
261
|
+
priority=3,
|
|
262
|
+
title=f"Avoid: {' → '.join(seq)}",
|
|
263
|
+
detail=desc[:60]
|
|
264
|
+
if desc
|
|
265
|
+
else "Detected inefficient pattern",
|
|
266
|
+
source_id=session.id,
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
except Exception:
|
|
270
|
+
pass
|
|
271
|
+
|
|
272
|
+
return items[:1] # Max 1 anti-pattern
|
|
273
|
+
|
|
274
|
+
def _get_related_spikes(
|
|
275
|
+
self, feature_id: str | None, track: str | None
|
|
276
|
+
) -> list[ReflectionItem]:
|
|
277
|
+
"""Get related investigation spikes."""
|
|
278
|
+
items = []
|
|
279
|
+
|
|
280
|
+
try:
|
|
281
|
+
spikes = self.sdk.spikes.all()
|
|
282
|
+
|
|
283
|
+
# Find spikes with findings that might be relevant
|
|
284
|
+
relevant_spikes = []
|
|
285
|
+
|
|
286
|
+
for spike in spikes:
|
|
287
|
+
# Check if spike has findings
|
|
288
|
+
if not hasattr(spike, "findings") or not spike.findings:
|
|
289
|
+
continue
|
|
290
|
+
|
|
291
|
+
# Check if spike is related to current feature
|
|
292
|
+
if feature_id and hasattr(spike, "edges") and spike.edges:
|
|
293
|
+
for edge_type, edges in spike.edges.items():
|
|
294
|
+
for edge in edges:
|
|
295
|
+
if edge.target_id == feature_id:
|
|
296
|
+
relevant_spikes.append((spike, 5)) # High relevance
|
|
297
|
+
break
|
|
298
|
+
|
|
299
|
+
# Check if spike mentions the track
|
|
300
|
+
if track and track.lower() in (spike.title or "").lower():
|
|
301
|
+
relevant_spikes.append((spike, 3)) # Medium relevance
|
|
302
|
+
|
|
303
|
+
# Check for recent completed spikes with findings
|
|
304
|
+
if spike.status == "done" and spike.findings:
|
|
305
|
+
if hasattr(spike, "updated") and spike.updated:
|
|
306
|
+
cutoff = datetime.now() - timedelta(hours=24)
|
|
307
|
+
if spike.updated > cutoff:
|
|
308
|
+
relevant_spikes.append((spike, 2)) # Lower relevance
|
|
309
|
+
|
|
310
|
+
# Sort by relevance and take top
|
|
311
|
+
relevant_spikes.sort(key=lambda x: x[1], reverse=True)
|
|
312
|
+
|
|
313
|
+
for spike, relevance in relevant_spikes[:1]:
|
|
314
|
+
findings_preview = spike.findings[:60] if spike.findings else ""
|
|
315
|
+
items.append(
|
|
316
|
+
ReflectionItem(
|
|
317
|
+
category="spike",
|
|
318
|
+
priority=2,
|
|
319
|
+
title=f"Related: {spike.title[:35]}",
|
|
320
|
+
detail=findings_preview or "See spike for details",
|
|
321
|
+
source_id=spike.id,
|
|
322
|
+
)
|
|
323
|
+
)
|
|
324
|
+
except Exception:
|
|
325
|
+
pass
|
|
326
|
+
|
|
327
|
+
return items[:1] # Max 1 spike
|
|
328
|
+
|
|
329
|
+
def _get_recommendations(self) -> list[ReflectionItem]:
|
|
330
|
+
"""Get strategic recommendations."""
|
|
331
|
+
items = []
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
recs = self.sdk.recommend_next_work(agent_count=1)
|
|
335
|
+
|
|
336
|
+
if recs and len(recs) > 0:
|
|
337
|
+
rec = recs[0]
|
|
338
|
+
reasons = rec.get("reasons", [])
|
|
339
|
+
reason_str = reasons[0] if reasons else "Recommended next"
|
|
340
|
+
|
|
341
|
+
items.append(
|
|
342
|
+
ReflectionItem(
|
|
343
|
+
category="recommendation",
|
|
344
|
+
priority=2,
|
|
345
|
+
title=f"Next: {rec.get('title', 'Unknown')[:35]}",
|
|
346
|
+
detail=reason_str[:60],
|
|
347
|
+
source_id=rec.get("id"),
|
|
348
|
+
)
|
|
349
|
+
)
|
|
350
|
+
except Exception:
|
|
351
|
+
pass
|
|
352
|
+
|
|
353
|
+
return items[:1] # Max 1 recommendation
|
|
354
|
+
|
|
355
|
+
def _build_summary(self, items: list[ReflectionItem]) -> str:
|
|
356
|
+
"""Build a one-line summary from items."""
|
|
357
|
+
if not items:
|
|
358
|
+
return "No actionable context found."
|
|
359
|
+
|
|
360
|
+
parts = []
|
|
361
|
+
|
|
362
|
+
# Count by category
|
|
363
|
+
blockers = [i for i in items if i.category == "blocker"]
|
|
364
|
+
failures = [i for i in items if i.category == "failure"]
|
|
365
|
+
anti_patterns = [i for i in items if i.category == "anti_pattern"]
|
|
366
|
+
spikes = [i for i in items if i.category == "spike"]
|
|
367
|
+
|
|
368
|
+
if blockers:
|
|
369
|
+
parts.append(f"{len(blockers)} blocker{'s' if len(blockers) > 1 else ''}")
|
|
370
|
+
|
|
371
|
+
if failures:
|
|
372
|
+
parts.append(
|
|
373
|
+
f"{len(failures)} recent failure{'s' if len(failures) > 1 else ''}"
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
if anti_patterns:
|
|
377
|
+
pattern = anti_patterns[0]
|
|
378
|
+
parts.append(f"Avoid: {pattern.title.replace('Avoid: ', '')}")
|
|
379
|
+
|
|
380
|
+
if spikes:
|
|
381
|
+
spike = spikes[0]
|
|
382
|
+
parts.append(f"See: {spike.source_id}")
|
|
383
|
+
|
|
384
|
+
return " | ".join(parts) if parts else "Session context loaded."
|
|
385
|
+
|
|
386
|
+
def format_for_injection(self, context: dict[str, Any] | None = None) -> str:
|
|
387
|
+
"""
|
|
388
|
+
Format context for injection into hooks.
|
|
389
|
+
|
|
390
|
+
Returns a markdown-formatted string suitable for additionalContext.
|
|
391
|
+
"""
|
|
392
|
+
if context is None:
|
|
393
|
+
context = self.get_actionable_context()
|
|
394
|
+
|
|
395
|
+
if not context.get("items"):
|
|
396
|
+
return ""
|
|
397
|
+
|
|
398
|
+
lines = ["## Computed Reflections", ""]
|
|
399
|
+
lines.append(f"**Summary:** {context.get('summary', 'N/A')}")
|
|
400
|
+
lines.append("")
|
|
401
|
+
|
|
402
|
+
for item in context.get("items", []):
|
|
403
|
+
icon = {
|
|
404
|
+
"blocker": "🚫",
|
|
405
|
+
"failure": "❌",
|
|
406
|
+
"anti_pattern": "⚠️",
|
|
407
|
+
"spike": "🔍",
|
|
408
|
+
"recommendation": "💡",
|
|
409
|
+
}.get(item.get("category", ""), "•")
|
|
410
|
+
|
|
411
|
+
lines.append(f"{icon} **{item.get('title', 'Unknown')}**")
|
|
412
|
+
lines.append(f" {item.get('detail', '')}")
|
|
413
|
+
if item.get("source_id"):
|
|
414
|
+
lines.append(f" _Source: {item.get('source_id')}_")
|
|
415
|
+
lines.append("")
|
|
416
|
+
|
|
417
|
+
lines.append("---")
|
|
418
|
+
lines.append("")
|
|
419
|
+
|
|
420
|
+
return "\n".join(lines)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def get_reflection_context(
|
|
424
|
+
sdk: SDK,
|
|
425
|
+
feature_id: str | None = None,
|
|
426
|
+
track: str | None = None,
|
|
427
|
+
) -> str:
|
|
428
|
+
"""
|
|
429
|
+
Convenience function to get formatted reflection context.
|
|
430
|
+
|
|
431
|
+
This is the main entry point for hooks.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
sdk: HtmlGraph SDK instance
|
|
435
|
+
feature_id: Current feature ID (optional)
|
|
436
|
+
track: Current track name (optional)
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
Formatted string for injection into hook context
|
|
440
|
+
"""
|
|
441
|
+
reflection = ComputationalReflection(sdk)
|
|
442
|
+
context = reflection.get_actionable_context(feature_id, track)
|
|
443
|
+
return reflection.format_for_injection(context)
|