htmlgraph 0.26.25__py3-none-any.whl → 0.27.0__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 +23 -1
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_registry.py +2 -1
- htmlgraph/analytics/cli.py +3 -3
- htmlgraph/analytics/cost_analyzer.py +5 -1
- htmlgraph/analytics/cross_session.py +13 -9
- htmlgraph/analytics/dependency.py +10 -6
- htmlgraph/analytics/work_type.py +15 -11
- htmlgraph/analytics_index.py +2 -1
- htmlgraph/api/main.py +58 -28
- htmlgraph/attribute_index.py +2 -1
- htmlgraph/builders/base.py +2 -1
- htmlgraph/builders/bug.py +2 -1
- htmlgraph/builders/chore.py +2 -1
- htmlgraph/builders/epic.py +2 -1
- htmlgraph/builders/feature.py +2 -1
- htmlgraph/builders/insight.py +2 -1
- htmlgraph/builders/metric.py +2 -1
- htmlgraph/builders/pattern.py +2 -1
- htmlgraph/builders/phase.py +2 -1
- htmlgraph/builders/spike.py +2 -1
- htmlgraph/builders/track.py +2 -1
- htmlgraph/cli/analytics.py +2 -1
- htmlgraph/cli/base.py +2 -1
- htmlgraph/cli/core.py +2 -1
- htmlgraph/cli/main.py +2 -1
- htmlgraph/cli/models.py +2 -1
- htmlgraph/cli/templates/cost_dashboard.py +2 -1
- htmlgraph/cli/work/__init__.py +2 -1
- htmlgraph/cli/work/browse.py +2 -1
- htmlgraph/cli/work/features.py +2 -1
- htmlgraph/cli/work/orchestration.py +2 -1
- htmlgraph/cli/work/report.py +2 -1
- htmlgraph/cli/work/sessions.py +2 -1
- htmlgraph/cli/work/snapshot.py +2 -1
- htmlgraph/cli/work/tracks.py +2 -1
- htmlgraph/collections/base.py +10 -5
- 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 +12 -7
- htmlgraph/collections/spike.py +6 -1
- htmlgraph/collections/task_delegation.py +7 -2
- htmlgraph/collections/todo.py +2 -1
- htmlgraph/collections/traces.py +15 -10
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/dependency_models.py +2 -1
- htmlgraph/edge_index.py +2 -1
- htmlgraph/event_log.py +81 -66
- htmlgraph/event_migration.py +2 -1
- htmlgraph/file_watcher.py +12 -8
- htmlgraph/find_api.py +2 -1
- htmlgraph/git_events.py +6 -2
- htmlgraph/hooks/cigs_pretool_enforcer.py +5 -1
- htmlgraph/hooks/drift_handler.py +3 -3
- htmlgraph/hooks/event_tracker.py +40 -61
- htmlgraph/hooks/installer.py +5 -1
- htmlgraph/hooks/orchestrator.py +4 -0
- htmlgraph/hooks/orchestrator_reflector.py +4 -0
- htmlgraph/hooks/post_tool_use_failure.py +7 -3
- htmlgraph/hooks/posttooluse.py +4 -0
- htmlgraph/hooks/prompt_analyzer.py +5 -5
- htmlgraph/hooks/session_handler.py +2 -1
- htmlgraph/hooks/session_summary.py +6 -2
- htmlgraph/hooks/validator.py +8 -4
- htmlgraph/ids.py +2 -1
- htmlgraph/learning.py +2 -1
- htmlgraph/mcp_server.py +2 -1
- htmlgraph/operations/analytics.py +2 -1
- htmlgraph/operations/bootstrap.py +2 -1
- htmlgraph/operations/events.py +2 -1
- htmlgraph/operations/fastapi_server.py +2 -1
- htmlgraph/operations/hooks.py +2 -1
- htmlgraph/operations/initialization.py +2 -1
- htmlgraph/operations/server.py +2 -1
- htmlgraph/orchestration/claude_launcher.py +23 -20
- htmlgraph/orchestration/command_builder.py +2 -1
- htmlgraph/orchestration/headless_spawner.py +6 -2
- htmlgraph/orchestration/model_selection.py +7 -3
- htmlgraph/orchestration/plugin_manager.py +24 -19
- htmlgraph/orchestration/spawners/claude.py +5 -2
- htmlgraph/orchestration/spawners/codex.py +12 -19
- htmlgraph/orchestration/spawners/copilot.py +13 -18
- htmlgraph/orchestration/spawners/gemini.py +12 -19
- htmlgraph/orchestration/subprocess_runner.py +6 -3
- htmlgraph/orchestration/task_coordination.py +16 -8
- htmlgraph/orchestrator.py +2 -1
- htmlgraph/parallel.py +2 -1
- htmlgraph/query_builder.py +2 -1
- htmlgraph/reflection.py +2 -1
- htmlgraph/refs.py +2 -1
- htmlgraph/repo_hash.py +2 -1
- 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/server.py +21 -17
- htmlgraph/session_warning.py +2 -1
- htmlgraph/sessions/handoff.py +4 -3
- htmlgraph/system_prompts.py +2 -1
- htmlgraph/track_builder.py +2 -1
- htmlgraph/transcript.py +2 -1
- htmlgraph/watch.py +2 -1
- htmlgraph/work_type_utils.py +2 -1
- {htmlgraph-0.26.25.dist-info → htmlgraph-0.27.0.dist-info}/METADATA +15 -1
- {htmlgraph-0.26.25.dist-info → htmlgraph-0.27.0.dist-info}/RECORD +146 -114
- htmlgraph/sdk.py +0 -3500
- {htmlgraph-0.26.25.data → htmlgraph-0.27.0.data}/data/htmlgraph/dashboard.html +0 -0
- {htmlgraph-0.26.25.data → htmlgraph-0.27.0.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.26.25.data → htmlgraph-0.27.0.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.26.25.data → htmlgraph-0.27.0.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.26.25.data → htmlgraph-0.27.0.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.26.25.dist-info → htmlgraph-0.27.0.dist-info}/WHEEL +0 -0
- {htmlgraph-0.26.25.dist-info → htmlgraph-0.27.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""Smart planning workflow integration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from htmlgraph.models import Node
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def start_planning_spike(
|
|
12
|
+
sdk: Any,
|
|
13
|
+
title: str,
|
|
14
|
+
context: str = "",
|
|
15
|
+
timebox_hours: float = 4.0,
|
|
16
|
+
auto_start: bool = True,
|
|
17
|
+
) -> Node:
|
|
18
|
+
"""
|
|
19
|
+
Create a planning spike to research and design before implementation.
|
|
20
|
+
|
|
21
|
+
This is for timeboxed investigation before creating a full track.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
sdk: SDK instance
|
|
25
|
+
title: Spike title (e.g., "Plan User Authentication System")
|
|
26
|
+
context: Background information
|
|
27
|
+
timebox_hours: Time limit for spike (default: 4 hours)
|
|
28
|
+
auto_start: Automatically start the spike (default: True)
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Created spike Node
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
>>> sdk = SDK(agent="claude")
|
|
35
|
+
>>> spike = sdk.start_planning_spike(
|
|
36
|
+
... "Plan Real-time Notifications",
|
|
37
|
+
... context="Users need live updates. Research options.",
|
|
38
|
+
... timebox_hours=3.0
|
|
39
|
+
... )
|
|
40
|
+
"""
|
|
41
|
+
from htmlgraph.ids import generate_id
|
|
42
|
+
from htmlgraph.models import Spike, SpikeType, Step
|
|
43
|
+
|
|
44
|
+
# Create spike directly (SpikeBuilder doesn't exist yet)
|
|
45
|
+
spike_id = generate_id(node_type="spike", title=title)
|
|
46
|
+
spike = Spike(
|
|
47
|
+
id=spike_id,
|
|
48
|
+
title=title,
|
|
49
|
+
type="spike",
|
|
50
|
+
status="in-progress" if auto_start and sdk._agent_id else "todo",
|
|
51
|
+
spike_type=SpikeType.ARCHITECTURAL,
|
|
52
|
+
timebox_hours=int(timebox_hours),
|
|
53
|
+
agent_assigned=sdk._agent_id if auto_start and sdk._agent_id else None,
|
|
54
|
+
steps=[
|
|
55
|
+
Step(description="Research existing solutions and patterns"),
|
|
56
|
+
Step(description="Define requirements and constraints"),
|
|
57
|
+
Step(description="Design high-level architecture"),
|
|
58
|
+
Step(description="Identify dependencies and risks"),
|
|
59
|
+
Step(description="Create implementation plan"),
|
|
60
|
+
],
|
|
61
|
+
content=f"<p>{context}</p>" if context else "",
|
|
62
|
+
edges={},
|
|
63
|
+
properties={},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
sdk._graph.add(spike)
|
|
67
|
+
return spike
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def create_track_from_plan(
|
|
71
|
+
sdk: Any,
|
|
72
|
+
title: str,
|
|
73
|
+
description: str,
|
|
74
|
+
spike_id: str | None = None,
|
|
75
|
+
priority: str = "high",
|
|
76
|
+
requirements: list[str | tuple[str, str]] | None = None,
|
|
77
|
+
phases: list[tuple[str, list[str]]] | None = None,
|
|
78
|
+
) -> dict[str, Any]:
|
|
79
|
+
"""
|
|
80
|
+
Create a track with spec and plan from planning results.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
sdk: SDK instance
|
|
84
|
+
title: Track title
|
|
85
|
+
description: Track description
|
|
86
|
+
spike_id: Optional spike ID that led to this track
|
|
87
|
+
priority: Track priority (default: "high")
|
|
88
|
+
requirements: List of requirements (strings or (req, priority) tuples)
|
|
89
|
+
phases: List of (phase_name, tasks) tuples for the plan
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Dict with track, spec, and plan details
|
|
93
|
+
|
|
94
|
+
Example:
|
|
95
|
+
>>> sdk = SDK(agent="claude")
|
|
96
|
+
>>> track_info = sdk.create_track_from_plan(
|
|
97
|
+
... title="User Authentication System",
|
|
98
|
+
... description="OAuth 2.0 with JWT tokens",
|
|
99
|
+
... requirements=[
|
|
100
|
+
... ("OAuth 2.0 integration", "must-have"),
|
|
101
|
+
... ("JWT token management", "must-have"),
|
|
102
|
+
... "Password reset flow"
|
|
103
|
+
... ],
|
|
104
|
+
... phases=[
|
|
105
|
+
... ("Phase 1: OAuth", ["Setup providers (2h)", "Callback (2h)"]),
|
|
106
|
+
... ("Phase 2: JWT", ["Token signing (2h)", "Refresh (1.5h)"])
|
|
107
|
+
... ]
|
|
108
|
+
... )
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
builder = (
|
|
112
|
+
sdk.tracks.builder().title(title).description(description).priority(priority)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Add reference to planning spike if provided
|
|
116
|
+
if spike_id:
|
|
117
|
+
# Access internal data for track builder
|
|
118
|
+
data: dict[str, Any] = builder._data # type: ignore[attr-defined]
|
|
119
|
+
data["properties"]["planning_spike"] = spike_id
|
|
120
|
+
|
|
121
|
+
# Add spec if requirements provided
|
|
122
|
+
if requirements:
|
|
123
|
+
# Convert simple strings to (requirement, "must-have") tuples
|
|
124
|
+
req_list = []
|
|
125
|
+
for req in requirements:
|
|
126
|
+
if isinstance(req, str):
|
|
127
|
+
req_list.append((req, "must-have"))
|
|
128
|
+
else:
|
|
129
|
+
req_list.append(req)
|
|
130
|
+
|
|
131
|
+
builder.with_spec(
|
|
132
|
+
overview=description,
|
|
133
|
+
context=f"Track created from planning spike: {spike_id}"
|
|
134
|
+
if spike_id
|
|
135
|
+
else "",
|
|
136
|
+
requirements=req_list,
|
|
137
|
+
acceptance_criteria=[],
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Add plan if phases provided
|
|
141
|
+
if phases:
|
|
142
|
+
builder.with_plan_phases(phases)
|
|
143
|
+
|
|
144
|
+
track = builder.create()
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
"track_id": track.id,
|
|
148
|
+
"title": track.title,
|
|
149
|
+
"has_spec": bool(requirements),
|
|
150
|
+
"has_plan": bool(phases),
|
|
151
|
+
"spike_id": spike_id,
|
|
152
|
+
"priority": priority,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def smart_plan(
|
|
157
|
+
sdk: Any,
|
|
158
|
+
description: str,
|
|
159
|
+
create_spike: bool = True,
|
|
160
|
+
timebox_hours: float = 4.0,
|
|
161
|
+
research_completed: bool = False,
|
|
162
|
+
research_findings: dict[str, Any] | None = None,
|
|
163
|
+
) -> dict[str, Any]:
|
|
164
|
+
"""
|
|
165
|
+
Smart planning workflow: analyzes project context and creates spike or track.
|
|
166
|
+
|
|
167
|
+
This is the main entry point for planning new work. It:
|
|
168
|
+
1. Checks current project state
|
|
169
|
+
2. Provides context from strategic analytics
|
|
170
|
+
3. Creates a planning spike or track as appropriate
|
|
171
|
+
|
|
172
|
+
**IMPORTANT: Research Phase Required**
|
|
173
|
+
For complex features, you should complete research BEFORE planning:
|
|
174
|
+
1. Use /htmlgraph:research or WebSearch to gather best practices
|
|
175
|
+
2. Document findings (libraries, patterns, anti-patterns)
|
|
176
|
+
3. Pass research_completed=True and research_findings to this method
|
|
177
|
+
4. This ensures planning is informed by industry best practices
|
|
178
|
+
|
|
179
|
+
Research-first workflow:
|
|
180
|
+
1. /htmlgraph:research "{topic}" → Gather external knowledge
|
|
181
|
+
2. sdk.smart_plan(..., research_completed=True) → Plan with context
|
|
182
|
+
3. Complete spike steps → Design solution
|
|
183
|
+
4. Create track from plan → Structure implementation
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
sdk: SDK instance
|
|
187
|
+
description: What you want to plan (e.g., "User authentication system")
|
|
188
|
+
create_spike: Create a spike for research (default: True)
|
|
189
|
+
timebox_hours: If creating spike, time limit (default: 4 hours)
|
|
190
|
+
research_completed: Whether research was performed (default: False)
|
|
191
|
+
research_findings: Structured research findings (optional)
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Dict with planning context and created spike/track info
|
|
195
|
+
|
|
196
|
+
Example:
|
|
197
|
+
>>> sdk = SDK(agent="claude")
|
|
198
|
+
>>> # WITH research (recommended for complex work)
|
|
199
|
+
>>> research = {
|
|
200
|
+
... "topic": "OAuth 2.0 best practices",
|
|
201
|
+
... "sources_count": 5,
|
|
202
|
+
... "recommended_library": "authlib",
|
|
203
|
+
... "key_insights": ["Use PKCE", "Implement token rotation"]
|
|
204
|
+
... }
|
|
205
|
+
>>> plan = sdk.smart_plan(
|
|
206
|
+
... "User authentication system",
|
|
207
|
+
... create_spike=True,
|
|
208
|
+
... research_completed=True,
|
|
209
|
+
... research_findings=research
|
|
210
|
+
... )
|
|
211
|
+
>>> logger.info(f"Created: {plan['spike_id']}")
|
|
212
|
+
>>> logger.info(f"Research informed: {plan['research_informed']}")
|
|
213
|
+
"""
|
|
214
|
+
# Get project context from strategic analytics
|
|
215
|
+
from htmlgraph.sdk.planning.bottlenecks import assess_risks, find_bottlenecks
|
|
216
|
+
from htmlgraph.sdk.planning.parallel import get_parallel_work
|
|
217
|
+
|
|
218
|
+
bottlenecks = find_bottlenecks(sdk, top_n=3)
|
|
219
|
+
risks = assess_risks(sdk)
|
|
220
|
+
parallel = get_parallel_work(sdk, max_agents=5)
|
|
221
|
+
|
|
222
|
+
context = {
|
|
223
|
+
"bottlenecks_count": len(bottlenecks),
|
|
224
|
+
"high_risk_count": risks["high_risk_count"],
|
|
225
|
+
"parallel_capacity": parallel["max_parallelism"],
|
|
226
|
+
"description": description,
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
# Build context string with research info
|
|
230
|
+
context_str = f"Project context:\n- {len(bottlenecks)} bottlenecks\n- {risks['high_risk_count']} high-risk items\n- {parallel['max_parallelism']} parallel capacity"
|
|
231
|
+
|
|
232
|
+
if research_completed and research_findings:
|
|
233
|
+
context_str += f"\n\nResearch completed:\n- Topic: {research_findings.get('topic', description)}"
|
|
234
|
+
if "sources_count" in research_findings:
|
|
235
|
+
context_str += f"\n- Sources: {research_findings['sources_count']}"
|
|
236
|
+
if "recommended_library" in research_findings:
|
|
237
|
+
context_str += (
|
|
238
|
+
f"\n- Recommended: {research_findings['recommended_library']}"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Validation: warn if complex work planned without research
|
|
242
|
+
is_complex = any(
|
|
243
|
+
[
|
|
244
|
+
"auth" in description.lower(),
|
|
245
|
+
"security" in description.lower(),
|
|
246
|
+
"real-time" in description.lower(),
|
|
247
|
+
"websocket" in description.lower(),
|
|
248
|
+
"oauth" in description.lower(),
|
|
249
|
+
"performance" in description.lower(),
|
|
250
|
+
"integration" in description.lower(),
|
|
251
|
+
]
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
warnings = []
|
|
255
|
+
if is_complex and not research_completed:
|
|
256
|
+
warnings.append(
|
|
257
|
+
"⚠️ Complex feature detected without research. "
|
|
258
|
+
"Consider using /htmlgraph:research first to gather best practices."
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
if create_spike:
|
|
262
|
+
spike = start_planning_spike(
|
|
263
|
+
sdk,
|
|
264
|
+
title=f"Plan: {description}",
|
|
265
|
+
context=context_str,
|
|
266
|
+
timebox_hours=timebox_hours,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Store research metadata in spike properties if provided
|
|
270
|
+
if research_completed and research_findings:
|
|
271
|
+
spike.properties["research_completed"] = True
|
|
272
|
+
spike.properties["research_findings"] = research_findings
|
|
273
|
+
sdk._graph.update(spike)
|
|
274
|
+
|
|
275
|
+
result = {
|
|
276
|
+
"type": "spike",
|
|
277
|
+
"spike_id": spike.id,
|
|
278
|
+
"title": spike.title,
|
|
279
|
+
"status": spike.status,
|
|
280
|
+
"timebox_hours": timebox_hours,
|
|
281
|
+
"project_context": context,
|
|
282
|
+
"research_informed": research_completed,
|
|
283
|
+
"next_steps": [
|
|
284
|
+
"Research and design the solution"
|
|
285
|
+
if not research_completed
|
|
286
|
+
else "Design solution using research findings",
|
|
287
|
+
"Complete spike steps",
|
|
288
|
+
"Use SDK.create_track_from_plan() to create track",
|
|
289
|
+
],
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if warnings:
|
|
293
|
+
result["warnings"] = warnings
|
|
294
|
+
|
|
295
|
+
return result
|
|
296
|
+
else:
|
|
297
|
+
# Direct track creation (for when you already know what to do)
|
|
298
|
+
track_info = create_track_from_plan(
|
|
299
|
+
sdk,
|
|
300
|
+
title=description,
|
|
301
|
+
description=f"Planned with context: {context}",
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
result = {
|
|
305
|
+
"type": "track",
|
|
306
|
+
**track_info,
|
|
307
|
+
"project_context": context,
|
|
308
|
+
"research_informed": research_completed,
|
|
309
|
+
"next_steps": [
|
|
310
|
+
"Create features from track plan",
|
|
311
|
+
"Link features to track",
|
|
312
|
+
"Start implementation",
|
|
313
|
+
],
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if warnings:
|
|
317
|
+
result["warnings"] = warnings
|
|
318
|
+
|
|
319
|
+
return result
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session management submodule for HtmlGraph SDK.
|
|
3
|
+
|
|
4
|
+
Provides session lifecycle operations, handoff context, and continuity.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from htmlgraph.sdk.session.continuity import SessionContinuityMixin
|
|
10
|
+
from htmlgraph.sdk.session.handoff import SessionHandoffMixin
|
|
11
|
+
from htmlgraph.sdk.session.info import SessionInfoMixin
|
|
12
|
+
from htmlgraph.sdk.session.manager import SessionManagerMixin
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"SessionManagerMixin",
|
|
16
|
+
"SessionHandoffMixin",
|
|
17
|
+
"SessionContinuityMixin",
|
|
18
|
+
"SessionInfoMixin",
|
|
19
|
+
]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session continuity and resume operations for SDK.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from htmlgraph.session_manager import SessionManager
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SessionContinuityMixin:
|
|
14
|
+
"""
|
|
15
|
+
Provides session continuity operations for resuming work.
|
|
16
|
+
|
|
17
|
+
Attributes accessed by mixins:
|
|
18
|
+
session_manager: SessionManager instance
|
|
19
|
+
_agent_id: Agent identifier
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
session_manager: SessionManager
|
|
23
|
+
|
|
24
|
+
def continue_from_last(
|
|
25
|
+
self,
|
|
26
|
+
agent: str | None = None,
|
|
27
|
+
auto_create_session: bool = True,
|
|
28
|
+
) -> tuple[Any, Any]:
|
|
29
|
+
"""
|
|
30
|
+
Continue work from the last completed session.
|
|
31
|
+
|
|
32
|
+
Loads context from previous session including handoff notes,
|
|
33
|
+
recommended files, blockers, and recent commits.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
agent: Filter by agent (None = current SDK agent)
|
|
37
|
+
auto_create_session: Create new session if True
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Tuple of (new_session, resume_info) or (None, None)
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
>>> sdk = SDK(agent="claude")
|
|
44
|
+
>>> session, resume = sdk.continue_from_last()
|
|
45
|
+
>>> if resume:
|
|
46
|
+
... logger.info("%s", resume.summary)
|
|
47
|
+
... logger.info("%s", resume.next_focus)
|
|
48
|
+
... for file in resume.recommended_files:
|
|
49
|
+
... logger.info(f" - {file}")
|
|
50
|
+
"""
|
|
51
|
+
if not agent:
|
|
52
|
+
agent = self._agent_id # type: ignore[attr-defined]
|
|
53
|
+
|
|
54
|
+
return self.session_manager.continue_from_last(
|
|
55
|
+
agent=agent,
|
|
56
|
+
auto_create_session=auto_create_session,
|
|
57
|
+
)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session handoff context management for SDK.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from htmlgraph.session_manager import SessionManager
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SessionHandoffMixin:
|
|
14
|
+
"""
|
|
15
|
+
Provides session handoff operations for cross-session continuity.
|
|
16
|
+
|
|
17
|
+
Attributes accessed by mixins:
|
|
18
|
+
session_manager: SessionManager instance
|
|
19
|
+
_agent_id: Agent identifier
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
session_manager: SessionManager
|
|
23
|
+
|
|
24
|
+
def set_session_handoff(
|
|
25
|
+
self,
|
|
26
|
+
handoff_notes: str | None = None,
|
|
27
|
+
recommended_next: str | None = None,
|
|
28
|
+
blockers: list[str] | None = None,
|
|
29
|
+
session_id: str | None = None,
|
|
30
|
+
) -> Any:
|
|
31
|
+
"""
|
|
32
|
+
Set handoff context on a session.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
handoff_notes: Notes for next session/agent
|
|
36
|
+
recommended_next: Suggested next steps
|
|
37
|
+
blockers: List of blockers
|
|
38
|
+
session_id: Specific session ID (defaults to active session)
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Updated Session or None if not found
|
|
42
|
+
"""
|
|
43
|
+
if not session_id:
|
|
44
|
+
if self._agent_id: # type: ignore[attr-defined]
|
|
45
|
+
active = self.session_manager.get_active_session_for_agent(
|
|
46
|
+
self._agent_id # type: ignore[attr-defined]
|
|
47
|
+
)
|
|
48
|
+
else:
|
|
49
|
+
active = self.session_manager.get_active_session()
|
|
50
|
+
if not active:
|
|
51
|
+
return None
|
|
52
|
+
session_id = active.id
|
|
53
|
+
|
|
54
|
+
return self.session_manager.set_session_handoff(
|
|
55
|
+
session_id=session_id,
|
|
56
|
+
handoff_notes=handoff_notes,
|
|
57
|
+
recommended_next=recommended_next,
|
|
58
|
+
blockers=blockers,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def end_session_with_handoff(
|
|
62
|
+
self,
|
|
63
|
+
session_id: str | None = None,
|
|
64
|
+
summary: str | None = None,
|
|
65
|
+
next_focus: str | None = None,
|
|
66
|
+
blockers: list[str] | None = None,
|
|
67
|
+
keep_context: list[str] | None = None,
|
|
68
|
+
auto_recommend_context: bool = True,
|
|
69
|
+
) -> Any:
|
|
70
|
+
"""
|
|
71
|
+
End session with handoff information for next session.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
session_id: Session to end (None = active session)
|
|
75
|
+
summary: What was accomplished
|
|
76
|
+
next_focus: What should be done next
|
|
77
|
+
blockers: List of blockers
|
|
78
|
+
keep_context: List of files to keep context for
|
|
79
|
+
auto_recommend_context: Auto-recommend files from git
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Updated Session or None
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
>>> sdk.end_session_with_handoff(
|
|
86
|
+
... summary="Completed OAuth integration",
|
|
87
|
+
... next_focus="Implement JWT token refresh",
|
|
88
|
+
... blockers=["Waiting for security review"],
|
|
89
|
+
... keep_context=["src/auth/oauth.py"]
|
|
90
|
+
... )
|
|
91
|
+
"""
|
|
92
|
+
if not session_id:
|
|
93
|
+
if self._agent_id: # type: ignore[attr-defined]
|
|
94
|
+
active = self.session_manager.get_active_session_for_agent(
|
|
95
|
+
self._agent_id # type: ignore[attr-defined]
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
active = self.session_manager.get_active_session()
|
|
99
|
+
if not active:
|
|
100
|
+
return None
|
|
101
|
+
session_id = active.id
|
|
102
|
+
|
|
103
|
+
return self.session_manager.end_session_with_handoff(
|
|
104
|
+
session_id=session_id,
|
|
105
|
+
summary=summary,
|
|
106
|
+
next_focus=next_focus,
|
|
107
|
+
blockers=blockers,
|
|
108
|
+
keep_context=keep_context,
|
|
109
|
+
auto_recommend_context=auto_recommend_context,
|
|
110
|
+
)
|