htmlgraph 0.9.3__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 +173 -17
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +127 -0
- htmlgraph/agent_registry.py +45 -30
- htmlgraph/agents.py +160 -107
- htmlgraph/analytics/__init__.py +9 -2
- htmlgraph/analytics/cli.py +190 -51
- 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 +192 -100
- 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 +190 -14
- htmlgraph/analytics_index.py +135 -51
- 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 +208 -0
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/__init__.py +14 -0
- htmlgraph/builders/base.py +118 -29
- htmlgraph/builders/bug.py +150 -0
- htmlgraph/builders/chore.py +119 -0
- htmlgraph/builders/epic.py +150 -0
- htmlgraph/builders/feature.py +31 -6
- htmlgraph/builders/insight.py +195 -0
- htmlgraph/builders/metric.py +217 -0
- htmlgraph/builders/pattern.py +202 -0
- htmlgraph/builders/phase.py +162 -0
- htmlgraph/builders/spike.py +52 -19
- htmlgraph/builders/track.py +148 -72
- 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 +18 -0
- htmlgraph/collections/base.py +415 -98
- htmlgraph/collections/bug.py +53 -0
- htmlgraph/collections/chore.py +53 -0
- htmlgraph/collections/epic.py +53 -0
- htmlgraph/collections/feature.py +12 -26
- htmlgraph/collections/insight.py +100 -0
- htmlgraph/collections/metric.py +92 -0
- htmlgraph/collections/pattern.py +97 -0
- htmlgraph/collections/phase.py +53 -0
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +56 -16
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +511 -0
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +344 -0
- htmlgraph/converter.py +216 -28
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2406 -307
- 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 +19 -2
- htmlgraph/deploy.py +142 -125
- htmlgraph/deployment_models.py +474 -0
- 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 +182 -27
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +100 -52
- htmlgraph/event_migration.py +13 -4
- htmlgraph/exceptions.py +49 -0
- htmlgraph/file_watcher.py +101 -28
- htmlgraph/find_api.py +75 -63
- htmlgraph/git_events.py +145 -63
- htmlgraph/graph.py +1122 -106
- 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 +45 -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 +1314 -0
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/hooks-config.example.json +12 -0
- htmlgraph/hooks/installer.py +343 -0
- htmlgraph/hooks/orchestrator.py +674 -0
- htmlgraph/hooks/orchestrator_reflector.py +223 -0
- htmlgraph/hooks/post-checkout.sh +28 -0
- htmlgraph/hooks/post-commit.sh +24 -0
- htmlgraph/hooks/post-merge.sh +26 -0
- htmlgraph/hooks/post_tool_use_failure.py +273 -0
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +408 -0
- htmlgraph/hooks/pre-commit.sh +94 -0
- htmlgraph/hooks/pre-push.sh +28 -0
- htmlgraph/hooks/pretooluse.py +819 -0
- 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 +255 -0
- htmlgraph/hooks/task_validator.py +177 -0
- htmlgraph/hooks/validator.py +628 -0
- htmlgraph/ids.py +41 -27
- htmlgraph/index.d.ts +286 -0
- htmlgraph/learning.py +767 -0
- htmlgraph/mcp_server.py +69 -23
- htmlgraph/models.py +1586 -87
- 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/task_coordination.py +343 -0
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +669 -0
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +328 -0
- htmlgraph/orchestrator_validator.py +133 -0
- htmlgraph/parallel.py +646 -0
- htmlgraph/parser.py +160 -35
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/planning.py +147 -52
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +109 -72
- 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/routing.py +8 -19
- htmlgraph/scripts/deploy.py +1 -2
- 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 +685 -180
- htmlgraph/services/__init__.py +10 -0
- htmlgraph/services/claiming.py +199 -0
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +1392 -175
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +201 -0
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/setup.py +34 -17
- htmlgraph/spike_index.py +143 -0
- htmlgraph/sync_docs.py +12 -15
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph/templates/GEMINI.md.template +87 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +146 -15
- htmlgraph/track_manager.py +69 -21
- htmlgraph/transcript.py +890 -0
- htmlgraph/transcript_analytics.py +699 -0
- htmlgraph/types.py +323 -0
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +8 -5
- htmlgraph/work_type_utils.py +3 -2
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
- htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -2688
- htmlgraph/sdk.py +0 -709
- htmlgraph-0.9.3.dist-info/RECORD +0 -61
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Parallel work detection and orchestration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_parallel_work(sdk: Any, max_agents: int = 5) -> dict[str, Any]:
|
|
12
|
+
"""
|
|
13
|
+
Find tasks that can be worked on simultaneously.
|
|
14
|
+
|
|
15
|
+
Note: Prefer using sdk.dep_analytics.find_parallelizable_work() directly.
|
|
16
|
+
This method exists for backward compatibility.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
sdk: SDK instance
|
|
20
|
+
max_agents: Maximum number of parallel agents to plan for
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Dict with parallelization opportunities
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
>>> sdk = SDK(agent="claude")
|
|
27
|
+
>>> # Preferred approach
|
|
28
|
+
>>> report = sdk.dep_analytics.find_parallelizable_work(status="todo")
|
|
29
|
+
>>> # Or via SDK (backward compatibility)
|
|
30
|
+
>>> parallel = sdk.get_parallel_work(max_agents=3)
|
|
31
|
+
>>> logger.info(f"Can work on {parallel['max_parallelism']} tasks at once")
|
|
32
|
+
>>> logger.info(f"Ready now: {parallel['ready_now']}")
|
|
33
|
+
"""
|
|
34
|
+
report = sdk.dep_analytics.find_parallelizable_work(status="todo")
|
|
35
|
+
|
|
36
|
+
ready_now = report.dependency_levels[0].nodes if report.dependency_levels else []
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
"max_parallelism": report.max_parallelism,
|
|
40
|
+
"ready_now": ready_now[:max_agents],
|
|
41
|
+
"total_ready": len(ready_now),
|
|
42
|
+
"level_count": len(report.dependency_levels),
|
|
43
|
+
"next_level": report.dependency_levels[1].nodes
|
|
44
|
+
if len(report.dependency_levels) > 1
|
|
45
|
+
else [],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def plan_parallel_work(
|
|
50
|
+
sdk: Any,
|
|
51
|
+
max_agents: int = 5,
|
|
52
|
+
shared_files: list[str] | None = None,
|
|
53
|
+
) -> dict[str, Any]:
|
|
54
|
+
"""
|
|
55
|
+
Plan and prepare parallel work execution.
|
|
56
|
+
|
|
57
|
+
This integrates with smart_plan to enable parallel agent dispatch.
|
|
58
|
+
Uses the 6-phase ParallelWorkflow:
|
|
59
|
+
1. Pre-flight analysis (dependencies, risks)
|
|
60
|
+
2. Context preparation (shared file caching)
|
|
61
|
+
3. Prompt generation (for Task tool)
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
sdk: SDK instance
|
|
65
|
+
max_agents: Maximum parallel agents (default: 5)
|
|
66
|
+
shared_files: Files to pre-cache for all agents
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Dict with parallel execution plan:
|
|
70
|
+
- can_parallelize: Whether parallelization is recommended
|
|
71
|
+
- analysis: Pre-flight analysis results
|
|
72
|
+
- prompts: Ready-to-use Task tool prompts
|
|
73
|
+
- recommendations: Optimization suggestions
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
>>> sdk = SDK(agent="orchestrator")
|
|
77
|
+
>>> plan = sdk.plan_parallel_work(max_agents=3)
|
|
78
|
+
>>> if plan["can_parallelize"]:
|
|
79
|
+
... # Use prompts with Task tool
|
|
80
|
+
... for p in plan["prompts"]:
|
|
81
|
+
... Task(prompt=p["prompt"], description=p["description"])
|
|
82
|
+
"""
|
|
83
|
+
from htmlgraph.parallel import ParallelWorkflow
|
|
84
|
+
|
|
85
|
+
workflow = ParallelWorkflow(sdk)
|
|
86
|
+
|
|
87
|
+
# Phase 1: Pre-flight analysis
|
|
88
|
+
analysis = workflow.analyze(max_agents=max_agents)
|
|
89
|
+
|
|
90
|
+
result = {
|
|
91
|
+
"can_parallelize": analysis.can_parallelize,
|
|
92
|
+
"max_parallelism": analysis.max_parallelism,
|
|
93
|
+
"ready_tasks": analysis.ready_tasks,
|
|
94
|
+
"blocked_tasks": analysis.blocked_tasks,
|
|
95
|
+
"speedup_factor": analysis.speedup_factor,
|
|
96
|
+
"recommendation": analysis.recommendation,
|
|
97
|
+
"warnings": analysis.warnings,
|
|
98
|
+
"prompts": [],
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if not analysis.can_parallelize:
|
|
102
|
+
result["reason"] = analysis.recommendation
|
|
103
|
+
return result
|
|
104
|
+
|
|
105
|
+
# Phase 2 & 3: Prepare tasks and generate prompts
|
|
106
|
+
tasks = workflow.prepare_tasks(
|
|
107
|
+
analysis.ready_tasks[:max_agents],
|
|
108
|
+
shared_files=shared_files,
|
|
109
|
+
)
|
|
110
|
+
prompts = workflow.generate_prompts(tasks)
|
|
111
|
+
|
|
112
|
+
result["prompts"] = prompts
|
|
113
|
+
result["task_count"] = len(prompts)
|
|
114
|
+
|
|
115
|
+
# Add efficiency guidelines
|
|
116
|
+
result["guidelines"] = {
|
|
117
|
+
"dispatch": "Send ALL Task calls in a SINGLE message for true parallelism",
|
|
118
|
+
"patterns": [
|
|
119
|
+
"Grep → Read (search before reading)",
|
|
120
|
+
"Read → Edit → Bash (read, modify, test)",
|
|
121
|
+
"Glob → Read (find files first)",
|
|
122
|
+
],
|
|
123
|
+
"avoid": [
|
|
124
|
+
"Sequential Task calls (loses parallelism)",
|
|
125
|
+
"Read → Read → Read (cache instead)",
|
|
126
|
+
"Edit → Edit → Edit (batch edits)",
|
|
127
|
+
],
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return result
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def aggregate_parallel_results(
|
|
134
|
+
sdk: Any,
|
|
135
|
+
agent_ids: list[str],
|
|
136
|
+
) -> dict[str, Any]:
|
|
137
|
+
"""
|
|
138
|
+
Aggregate results from parallel agent execution.
|
|
139
|
+
|
|
140
|
+
Call this after parallel agents complete to:
|
|
141
|
+
- Collect health metrics
|
|
142
|
+
- Detect anti-patterns
|
|
143
|
+
- Identify conflicts
|
|
144
|
+
- Generate recommendations
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
sdk: SDK instance
|
|
148
|
+
agent_ids: List of agent/transcript IDs to analyze
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Dict with aggregated results and validation
|
|
152
|
+
|
|
153
|
+
Example:
|
|
154
|
+
>>> # After parallel work completes
|
|
155
|
+
>>> results = sdk.aggregate_parallel_results([
|
|
156
|
+
... "agent-abc123",
|
|
157
|
+
... "agent-def456",
|
|
158
|
+
... "agent-ghi789",
|
|
159
|
+
... ])
|
|
160
|
+
>>> logger.info(f"Health: {results['avg_health_score']:.0%}")
|
|
161
|
+
>>> logger.info(f"Conflicts: {results['conflicts']}")
|
|
162
|
+
"""
|
|
163
|
+
from htmlgraph.parallel import ParallelWorkflow
|
|
164
|
+
|
|
165
|
+
workflow = ParallelWorkflow(sdk)
|
|
166
|
+
|
|
167
|
+
# Phase 5: Aggregate
|
|
168
|
+
aggregate = workflow.aggregate(agent_ids)
|
|
169
|
+
|
|
170
|
+
# Phase 6: Validate
|
|
171
|
+
validation = workflow.validate(aggregate)
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
"total_agents": aggregate.total_agents,
|
|
175
|
+
"successful": aggregate.successful,
|
|
176
|
+
"failed": aggregate.failed,
|
|
177
|
+
"total_duration_seconds": aggregate.total_duration_seconds,
|
|
178
|
+
"parallel_speedup": aggregate.parallel_speedup,
|
|
179
|
+
"avg_health_score": aggregate.avg_health_score,
|
|
180
|
+
"total_anti_patterns": aggregate.total_anti_patterns,
|
|
181
|
+
"files_modified": aggregate.files_modified,
|
|
182
|
+
"conflicts": aggregate.conflicts,
|
|
183
|
+
"recommendations": aggregate.recommendations,
|
|
184
|
+
"validation": validation,
|
|
185
|
+
"all_passed": all(validation.values()),
|
|
186
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""Work queue management."""
|
|
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 get_work_queue(
|
|
12
|
+
sdk: Any, agent_id: str | None = None, limit: int = 10, min_score: float = 0.0
|
|
13
|
+
) -> list[dict[str, Any]]:
|
|
14
|
+
"""
|
|
15
|
+
Get prioritized work queue showing recommended work, active work, and dependencies.
|
|
16
|
+
|
|
17
|
+
This method provides a comprehensive view of:
|
|
18
|
+
1. Recommended next work (using smart analytics)
|
|
19
|
+
2. Active work by all agents
|
|
20
|
+
3. Blocked items and what's blocking them
|
|
21
|
+
4. Priority-based scoring
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
sdk: SDK instance
|
|
25
|
+
agent_id: Agent to get queue for (defaults to SDK agent)
|
|
26
|
+
limit: Maximum number of items to return (default: 10)
|
|
27
|
+
min_score: Minimum score threshold (default: 0.0)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
List of work queue items with scoring and metadata:
|
|
31
|
+
- task_id: Work item ID
|
|
32
|
+
- title: Work item title
|
|
33
|
+
- status: Current status
|
|
34
|
+
- priority: Priority level
|
|
35
|
+
- score: Routing score
|
|
36
|
+
- complexity: Complexity level (if set)
|
|
37
|
+
- effort: Estimated effort (if set)
|
|
38
|
+
- blocks_count: Number of tasks this blocks (if any)
|
|
39
|
+
- blocked_by: List of blocking task IDs (if blocked)
|
|
40
|
+
- agent_assigned: Current assignee (if any)
|
|
41
|
+
- type: Work item type (feature, bug, spike, etc.)
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
>>> sdk = SDK(agent="claude")
|
|
45
|
+
>>> queue = sdk.get_work_queue(limit=5)
|
|
46
|
+
>>> for item in queue:
|
|
47
|
+
... logger.info(f"{item['score']:.1f} - {item['title']}")
|
|
48
|
+
... if item.get('blocked_by'):
|
|
49
|
+
... logger.info(f" ⚠️ Blocked by: {', '.join(item['blocked_by'])}")
|
|
50
|
+
"""
|
|
51
|
+
from htmlgraph.routing import AgentCapabilityRegistry, CapabilityMatcher
|
|
52
|
+
|
|
53
|
+
agent = agent_id or sdk._agent_id or "cli"
|
|
54
|
+
|
|
55
|
+
# Get all work item types
|
|
56
|
+
all_work = []
|
|
57
|
+
for collection_name in ["features", "bugs", "spikes", "chores", "epics"]:
|
|
58
|
+
collection = getattr(sdk, collection_name, None)
|
|
59
|
+
if collection:
|
|
60
|
+
# Get todo and blocked items
|
|
61
|
+
for item in collection.where(status="todo"):
|
|
62
|
+
all_work.append(item)
|
|
63
|
+
for item in collection.where(status="blocked"):
|
|
64
|
+
all_work.append(item)
|
|
65
|
+
|
|
66
|
+
if not all_work:
|
|
67
|
+
return []
|
|
68
|
+
|
|
69
|
+
# Get recommendations from analytics (uses strategic scoring)
|
|
70
|
+
from htmlgraph.sdk.planning.recommendations import recommend_next_work
|
|
71
|
+
|
|
72
|
+
recommendations = recommend_next_work(sdk, agent_count=limit * 2)
|
|
73
|
+
rec_scores = {rec["id"]: rec["score"] for rec in recommendations}
|
|
74
|
+
|
|
75
|
+
# Build routing registry
|
|
76
|
+
registry = AgentCapabilityRegistry()
|
|
77
|
+
|
|
78
|
+
# Register current agent
|
|
79
|
+
registry.register_agent(agent, capabilities=[], wip_limit=5)
|
|
80
|
+
|
|
81
|
+
# Get current WIP count for agent
|
|
82
|
+
wip_count = len(sdk.features.where(status="in-progress", agent_assigned=agent))
|
|
83
|
+
registry.set_wip(agent, wip_count)
|
|
84
|
+
|
|
85
|
+
# Score each work item
|
|
86
|
+
queue_items = []
|
|
87
|
+
for item in all_work:
|
|
88
|
+
# Use strategic score if available, otherwise use routing score
|
|
89
|
+
if item.id in rec_scores:
|
|
90
|
+
score = rec_scores[item.id]
|
|
91
|
+
else:
|
|
92
|
+
# Fallback to routing score
|
|
93
|
+
agent_profile = registry.get_agent(agent)
|
|
94
|
+
if agent_profile:
|
|
95
|
+
score = CapabilityMatcher.score_agent_task_fit(agent_profile, item)
|
|
96
|
+
else:
|
|
97
|
+
score = 0.0
|
|
98
|
+
|
|
99
|
+
# Apply minimum score filter
|
|
100
|
+
if score < min_score:
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
# Build queue item
|
|
104
|
+
queue_item = {
|
|
105
|
+
"task_id": item.id,
|
|
106
|
+
"title": item.title,
|
|
107
|
+
"status": item.status,
|
|
108
|
+
"priority": item.priority,
|
|
109
|
+
"score": score,
|
|
110
|
+
"type": item.type,
|
|
111
|
+
"complexity": getattr(item, "complexity", None),
|
|
112
|
+
"effort": getattr(item, "estimated_effort", None),
|
|
113
|
+
"agent_assigned": getattr(item, "agent_assigned", None),
|
|
114
|
+
"blocks_count": 0,
|
|
115
|
+
"blocked_by": [],
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Add dependency information
|
|
119
|
+
if hasattr(item, "edges"):
|
|
120
|
+
# Check if this item blocks others
|
|
121
|
+
blocks = item.edges.get("blocks", [])
|
|
122
|
+
queue_item["blocks_count"] = len(blocks)
|
|
123
|
+
|
|
124
|
+
# Check if this item is blocked
|
|
125
|
+
blocked_by = item.edges.get("blocked_by", [])
|
|
126
|
+
queue_item["blocked_by"] = blocked_by
|
|
127
|
+
|
|
128
|
+
queue_items.append(queue_item)
|
|
129
|
+
|
|
130
|
+
# Sort by score (descending)
|
|
131
|
+
queue_items.sort(key=lambda x: x["score"], reverse=True)
|
|
132
|
+
|
|
133
|
+
# Limit results
|
|
134
|
+
return queue_items[:limit]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def work_next(
|
|
138
|
+
sdk: Any,
|
|
139
|
+
agent_id: str | None = None,
|
|
140
|
+
auto_claim: bool = False,
|
|
141
|
+
min_score: float = 0.0,
|
|
142
|
+
) -> Node | None:
|
|
143
|
+
"""
|
|
144
|
+
Get the next best task for an agent using smart routing.
|
|
145
|
+
|
|
146
|
+
Uses both strategic analytics and capability-based routing to find
|
|
147
|
+
the optimal next task.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
sdk: SDK instance
|
|
151
|
+
agent_id: Agent to get task for (defaults to SDK agent)
|
|
152
|
+
auto_claim: Automatically claim the task (default: False)
|
|
153
|
+
min_score: Minimum score threshold (default: 0.0)
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Next best Node or None if no suitable task found
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
>>> sdk = SDK(agent="claude")
|
|
160
|
+
>>> task = sdk.work_next(auto_claim=True)
|
|
161
|
+
>>> if task:
|
|
162
|
+
... logger.info(f"Working on: {task.title}")
|
|
163
|
+
... # Task is automatically claimed and assigned
|
|
164
|
+
"""
|
|
165
|
+
agent = agent_id or sdk._agent_id or "cli"
|
|
166
|
+
|
|
167
|
+
# Get work queue - get more items since we filter for actionable (todo) only
|
|
168
|
+
queue = get_work_queue(sdk, agent_id=agent, limit=20, min_score=min_score)
|
|
169
|
+
|
|
170
|
+
if not queue:
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
# Find the first actionable (todo) task - blocked tasks are not actionable
|
|
174
|
+
top_item = None
|
|
175
|
+
for item in queue:
|
|
176
|
+
if item["status"] == "todo":
|
|
177
|
+
top_item = item
|
|
178
|
+
break
|
|
179
|
+
|
|
180
|
+
if top_item is None:
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
# Fetch the actual node
|
|
184
|
+
task = None
|
|
185
|
+
for collection_name in ["features", "bugs", "spikes", "chores", "epics"]:
|
|
186
|
+
collection = getattr(sdk, collection_name, None)
|
|
187
|
+
if collection:
|
|
188
|
+
try:
|
|
189
|
+
task = collection.get(top_item["task_id"])
|
|
190
|
+
if task:
|
|
191
|
+
break
|
|
192
|
+
except (ValueError, FileNotFoundError):
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
if not task:
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
# Auto-claim if requested
|
|
199
|
+
if auto_claim and task.status == "todo" and collection is not None:
|
|
200
|
+
# Claim the task
|
|
201
|
+
# collection.edit returns context manager or None
|
|
202
|
+
task_editor: Any = collection.edit(task.id)
|
|
203
|
+
if task_editor is not None:
|
|
204
|
+
# collection.edit returns context manager
|
|
205
|
+
with task_editor as t:
|
|
206
|
+
t.status = "in-progress"
|
|
207
|
+
t.agent_assigned = agent
|
|
208
|
+
|
|
209
|
+
result: Node | None = task
|
|
210
|
+
return result
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Smart work recommendations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def recommend_next_work(sdk: Any, agent_count: int = 1) -> list[dict[str, Any]]:
|
|
12
|
+
"""
|
|
13
|
+
Get smart recommendations for what to work on next.
|
|
14
|
+
|
|
15
|
+
Note: Prefer using sdk.dep_analytics.recommend_next_tasks() directly.
|
|
16
|
+
This method exists for backward compatibility.
|
|
17
|
+
|
|
18
|
+
Considers priority, dependencies, and transitive impact.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
sdk: SDK instance
|
|
22
|
+
agent_count: Number of agents/tasks to recommend
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
List of recommended tasks with reasoning
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
>>> sdk = SDK(agent="claude")
|
|
29
|
+
>>> # Preferred approach
|
|
30
|
+
>>> recs = sdk.dep_analytics.recommend_next_tasks(agent_count=3)
|
|
31
|
+
>>> # Or via SDK (backward compatibility)
|
|
32
|
+
>>> recs = sdk.recommend_next_work(agent_count=3)
|
|
33
|
+
>>> for rec in recs:
|
|
34
|
+
... logger.info(f"{rec['title']} (score: {rec['score']})")
|
|
35
|
+
... logger.info(f" Reasons: {rec['reasons']}")
|
|
36
|
+
"""
|
|
37
|
+
recommendations = sdk.dep_analytics.recommend_next_tasks(
|
|
38
|
+
agent_count=agent_count, lookahead=5
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return [
|
|
42
|
+
{
|
|
43
|
+
"id": rec.id,
|
|
44
|
+
"title": rec.title,
|
|
45
|
+
"priority": rec.priority,
|
|
46
|
+
"score": rec.score,
|
|
47
|
+
"reasons": rec.reasons,
|
|
48
|
+
"estimated_hours": rec.estimated_effort,
|
|
49
|
+
"unlocks_count": len(rec.unlocks),
|
|
50
|
+
"unlocks": rec.unlocks[:3],
|
|
51
|
+
}
|
|
52
|
+
for rec in recommendations.recommendations
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def analyze_impact(sdk: Any, node_id: str) -> dict[str, Any]:
|
|
57
|
+
"""
|
|
58
|
+
Analyze the impact of completing a specific task.
|
|
59
|
+
|
|
60
|
+
Note: Prefer using sdk.dep_analytics.impact_analysis() directly.
|
|
61
|
+
This method exists for backward compatibility.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
sdk: SDK instance
|
|
65
|
+
node_id: Task to analyze
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Dict with impact analysis
|
|
69
|
+
|
|
70
|
+
Example:
|
|
71
|
+
>>> sdk = SDK(agent="claude")
|
|
72
|
+
>>> # Preferred approach
|
|
73
|
+
>>> impact = sdk.dep_analytics.impact_analysis("feature-001")
|
|
74
|
+
>>> # Or via SDK (backward compatibility)
|
|
75
|
+
>>> impact = sdk.analyze_impact("feature-001")
|
|
76
|
+
>>> logger.info(f"Completing this unlocks {impact['unlocks_count']} tasks")
|
|
77
|
+
"""
|
|
78
|
+
impact = sdk.dep_analytics.impact_analysis(node_id)
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
"node_id": node_id,
|
|
82
|
+
"direct_dependents": impact.direct_dependents,
|
|
83
|
+
"total_impact": impact.transitive_dependents,
|
|
84
|
+
"completion_impact": impact.completion_impact,
|
|
85
|
+
"unlocks_count": len(impact.affected_nodes),
|
|
86
|
+
"affected_tasks": impact.affected_nodes[:10],
|
|
87
|
+
}
|