htmlgraph 0.26.25__py3-none-any.whl → 0.27.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- htmlgraph/__init__.py +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/cost_monitor.py +664 -0
- htmlgraph/analytics/cross_session.py +13 -9
- htmlgraph/analytics/dependency.py +10 -6
- 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 +15 -11
- htmlgraph/analytics_index.py +2 -1
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +167 -62
- htmlgraph/api/websocket.py +538 -0
- 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/config/cost_models.json +56 -0
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/db/schema.py +67 -6
- htmlgraph/dependency_models.py +2 -1
- htmlgraph/edge_index.py +2 -1
- htmlgraph/event_log.py +83 -64
- 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/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 +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.1.dist-info}/METADATA +1 -1
- htmlgraph-0.27.1.dist-info/RECORD +332 -0
- htmlgraph/sdk.py +0 -3500
- htmlgraph-0.26.25.dist-info/RECORD +0 -274
- {htmlgraph-0.26.25.data → htmlgraph-0.27.1.data}/data/htmlgraph/dashboard.html +0 -0
- {htmlgraph-0.26.25.data → htmlgraph-0.27.1.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.26.25.data → htmlgraph-0.27.1.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.26.25.data → htmlgraph-0.27.1.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.26.25.data → htmlgraph-0.27.1.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.26.25.dist-info → htmlgraph-0.27.1.dist-info}/WHEEL +0 -0
- {htmlgraph-0.26.25.dist-info → htmlgraph-0.27.1.dist-info}/entry_points.txt +0 -0
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
"""Headless AI spawner for multi-AI orchestration.
|
|
2
2
|
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
3
7
|
This module provides backward compatibility by delegating to modular spawner implementations.
|
|
4
8
|
"""
|
|
5
9
|
|
|
@@ -263,8 +267,8 @@ class HeadlessSpawner:
|
|
|
263
267
|
>>> spawner = HeadlessSpawner()
|
|
264
268
|
>>> result = spawner.spawn_claude("What is 2+2?")
|
|
265
269
|
>>> if result.success:
|
|
266
|
-
...
|
|
267
|
-
...
|
|
270
|
+
... logger.info("%s", result.response) # "4"
|
|
271
|
+
... logger.info(f"Cost: ${result.raw_output['total_cost_usd']}")
|
|
268
272
|
"""
|
|
269
273
|
return self._claude_spawner.spawn(
|
|
270
274
|
prompt=prompt,
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
"""Intelligent model selection for task routing.
|
|
2
2
|
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
3
7
|
This module provides functionality to select the best AI model for a given task
|
|
4
8
|
based on task type, complexity, and budget constraints.
|
|
5
9
|
|
|
@@ -179,7 +183,7 @@ class ModelSelection:
|
|
|
179
183
|
|
|
180
184
|
Example:
|
|
181
185
|
>>> model = ModelSelection.select_model("implementation", "high", "balanced")
|
|
182
|
-
>>>
|
|
186
|
+
>>> logger.info("%s", model)
|
|
183
187
|
'claude-opus'
|
|
184
188
|
"""
|
|
185
189
|
# Normalize inputs
|
|
@@ -218,7 +222,7 @@ class ModelSelection:
|
|
|
218
222
|
|
|
219
223
|
Example:
|
|
220
224
|
>>> fallbacks = ModelSelection.get_fallback_chain("gemini")
|
|
221
|
-
>>>
|
|
225
|
+
>>> logger.info("%s", fallbacks)
|
|
222
226
|
['claude-haiku', 'claude-sonnet', 'claude-opus']
|
|
223
227
|
"""
|
|
224
228
|
return ModelSelection.FALLBACK_CHAINS.get(primary_model, ["claude-sonnet"])
|
|
@@ -305,7 +309,7 @@ def select_model(
|
|
|
305
309
|
|
|
306
310
|
Example:
|
|
307
311
|
>>> model = select_model("implementation", "high")
|
|
308
|
-
>>>
|
|
312
|
+
>>> logger.info("%s", model)
|
|
309
313
|
"""
|
|
310
314
|
return ModelSelection.select_model(task_type, complexity, budget)
|
|
311
315
|
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""Plugin management for HtmlGraph Claude Code integration.
|
|
2
4
|
|
|
3
5
|
Centralizes plugin installation, directory management, and validation.
|
|
4
6
|
"""
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
import logging
|
|
8
9
|
import subprocess
|
|
9
10
|
import sys
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from typing import TYPE_CHECKING
|
|
12
13
|
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
13
16
|
if TYPE_CHECKING:
|
|
14
17
|
pass
|
|
15
18
|
|
|
@@ -40,12 +43,12 @@ class PluginManager:
|
|
|
40
43
|
verbose: Whether to show progress messages
|
|
41
44
|
"""
|
|
42
45
|
if verbose:
|
|
43
|
-
|
|
46
|
+
logger.info("\n📦 Installing/upgrading HtmlGraph plugin...\n")
|
|
44
47
|
|
|
45
48
|
# Step 1: Update marketplace
|
|
46
49
|
try:
|
|
47
50
|
if verbose:
|
|
48
|
-
|
|
51
|
+
logger.info(" Updating marketplace...")
|
|
49
52
|
result = subprocess.run(
|
|
50
53
|
["claude", "plugin", "marketplace", "update", "htmlgraph"],
|
|
51
54
|
capture_output=True,
|
|
@@ -54,7 +57,7 @@ class PluginManager:
|
|
|
54
57
|
)
|
|
55
58
|
if result.returncode == 0:
|
|
56
59
|
if verbose:
|
|
57
|
-
|
|
60
|
+
logger.info(" ✓ Marketplace updated")
|
|
58
61
|
else:
|
|
59
62
|
# Non-blocking errors
|
|
60
63
|
if (
|
|
@@ -62,20 +65,20 @@ class PluginManager:
|
|
|
62
65
|
or "no marketplace" in result.stderr.lower()
|
|
63
66
|
):
|
|
64
67
|
if verbose:
|
|
65
|
-
|
|
68
|
+
logger.info(" ℹ Marketplace not configured (OK, continuing)")
|
|
66
69
|
elif verbose:
|
|
67
|
-
|
|
70
|
+
logger.info(f" ⚠ Marketplace update: {result.stderr.strip()}")
|
|
68
71
|
except FileNotFoundError:
|
|
69
72
|
if verbose:
|
|
70
|
-
|
|
73
|
+
logger.info(" ⚠ 'claude' command not found")
|
|
71
74
|
except Exception as e:
|
|
72
75
|
if verbose:
|
|
73
|
-
|
|
76
|
+
logger.info(f" ⚠ Error updating marketplace: {e}")
|
|
74
77
|
|
|
75
78
|
# Step 2: Try update, fallback to install
|
|
76
79
|
try:
|
|
77
80
|
if verbose:
|
|
78
|
-
|
|
81
|
+
logger.info(" Updating plugin to latest version...")
|
|
79
82
|
result = subprocess.run(
|
|
80
83
|
["claude", "plugin", "update", "htmlgraph"],
|
|
81
84
|
capture_output=True,
|
|
@@ -84,7 +87,7 @@ class PluginManager:
|
|
|
84
87
|
)
|
|
85
88
|
if result.returncode == 0:
|
|
86
89
|
if verbose:
|
|
87
|
-
|
|
90
|
+
logger.info(" ✓ Plugin updated successfully")
|
|
88
91
|
else:
|
|
89
92
|
# Fallback to install
|
|
90
93
|
if (
|
|
@@ -92,7 +95,7 @@ class PluginManager:
|
|
|
92
95
|
or "not found" in result.stderr.lower()
|
|
93
96
|
):
|
|
94
97
|
if verbose:
|
|
95
|
-
|
|
98
|
+
logger.info(" ℹ Plugin not yet installed, installing...")
|
|
96
99
|
install_result = subprocess.run(
|
|
97
100
|
["claude", "plugin", "install", "htmlgraph"],
|
|
98
101
|
capture_output=True,
|
|
@@ -101,20 +104,22 @@ class PluginManager:
|
|
|
101
104
|
)
|
|
102
105
|
if install_result.returncode == 0:
|
|
103
106
|
if verbose:
|
|
104
|
-
|
|
107
|
+
logger.info(" ✓ Plugin installed successfully")
|
|
105
108
|
elif verbose:
|
|
106
|
-
|
|
109
|
+
logger.info(
|
|
110
|
+
f" ⚠ Plugin install: {install_result.stderr.strip()}"
|
|
111
|
+
)
|
|
107
112
|
elif verbose:
|
|
108
|
-
|
|
113
|
+
logger.info(f" ⚠ Plugin update: {result.stderr.strip()}")
|
|
109
114
|
except FileNotFoundError:
|
|
110
115
|
if verbose:
|
|
111
|
-
|
|
116
|
+
logger.info(" ⚠ 'claude' command not found")
|
|
112
117
|
except Exception as e:
|
|
113
118
|
if verbose:
|
|
114
|
-
|
|
119
|
+
logger.info(f" ⚠ Error updating plugin: {e}")
|
|
115
120
|
|
|
116
121
|
if verbose:
|
|
117
|
-
|
|
122
|
+
logger.info("\n✓ Plugin installation complete\n")
|
|
118
123
|
|
|
119
124
|
@staticmethod
|
|
120
125
|
def validate_plugin_dir(plugin_dir: Path) -> None:
|
|
@@ -127,7 +132,7 @@ class PluginManager:
|
|
|
127
132
|
SystemExit: If plugin directory doesn't exist
|
|
128
133
|
"""
|
|
129
134
|
if not plugin_dir.exists():
|
|
130
|
-
|
|
135
|
+
logger.warning(f"Error: Plugin directory not found: {plugin_dir}")
|
|
131
136
|
print(
|
|
132
137
|
"Expected location: packages/claude-plugin/.claude-plugin",
|
|
133
138
|
file=sys.stderr,
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"""Claude spawner implementation."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import logging
|
|
4
5
|
import subprocess
|
|
5
6
|
from typing import TYPE_CHECKING
|
|
6
7
|
|
|
7
8
|
from .base import AIResult, BaseSpawner
|
|
8
9
|
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
9
12
|
if TYPE_CHECKING:
|
|
10
13
|
pass
|
|
11
14
|
|
|
@@ -64,8 +67,8 @@ class ClaudeSpawner(BaseSpawner):
|
|
|
64
67
|
>>> spawner = ClaudeSpawner()
|
|
65
68
|
>>> result = spawner.spawn("What is 2+2?")
|
|
66
69
|
>>> if result.success:
|
|
67
|
-
...
|
|
68
|
-
...
|
|
70
|
+
... logger.info("%s", result.response) # "4"
|
|
71
|
+
... logger.info(f"Cost: ${result.raw_output['total_cost_usd']}")
|
|
69
72
|
"""
|
|
70
73
|
cmd = ["claude", "-p"]
|
|
71
74
|
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"""Codex spawner implementation."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import logging
|
|
4
5
|
import subprocess
|
|
5
|
-
import sys
|
|
6
6
|
import time
|
|
7
7
|
from typing import TYPE_CHECKING, Any
|
|
8
8
|
|
|
9
9
|
from .base import AIResult, BaseSpawner
|
|
10
10
|
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
11
13
|
if TYPE_CHECKING:
|
|
12
14
|
from htmlgraph.sdk import SDK
|
|
13
15
|
|
|
@@ -232,15 +234,11 @@ class CodexSpawner(BaseSpawner):
|
|
|
232
234
|
|
|
233
235
|
# Record subprocess invocation if tracker is available
|
|
234
236
|
subprocess_event_id = None
|
|
235
|
-
|
|
236
|
-
f"DEBUG: tracker={tracker is not None}, parent_event_id={parent_event_id}"
|
|
237
|
-
file=sys.stderr,
|
|
237
|
+
logger.warning(
|
|
238
|
+
f"DEBUG: tracker={tracker is not None}, parent_event_id={parent_event_id}"
|
|
238
239
|
)
|
|
239
240
|
if tracker and parent_event_id:
|
|
240
|
-
|
|
241
|
-
"DEBUG: Recording subprocess invocation for Codex...",
|
|
242
|
-
file=sys.stderr,
|
|
243
|
-
)
|
|
241
|
+
logger.debug("Recording subprocess invocation for Codex...")
|
|
244
242
|
try:
|
|
245
243
|
subprocess_event = tracker.record_tool_call(
|
|
246
244
|
tool_name="subprocess.codex",
|
|
@@ -250,23 +248,18 @@ class CodexSpawner(BaseSpawner):
|
|
|
250
248
|
)
|
|
251
249
|
if subprocess_event:
|
|
252
250
|
subprocess_event_id = subprocess_event.get("event_id")
|
|
253
|
-
|
|
254
|
-
f"DEBUG: Subprocess event created for Codex: {subprocess_event_id}"
|
|
255
|
-
file=sys.stderr,
|
|
251
|
+
logger.warning(
|
|
252
|
+
f"DEBUG: Subprocess event created for Codex: {subprocess_event_id}"
|
|
256
253
|
)
|
|
257
254
|
else:
|
|
258
|
-
|
|
255
|
+
logger.debug("subprocess_event was None")
|
|
259
256
|
except Exception as e:
|
|
260
257
|
# Tracking failure should not break execution
|
|
261
|
-
|
|
262
|
-
f"DEBUG: Exception recording Codex subprocess: {e}",
|
|
263
|
-
file=sys.stderr,
|
|
264
|
-
)
|
|
258
|
+
logger.warning(f"DEBUG: Exception recording Codex subprocess: {e}")
|
|
265
259
|
pass
|
|
266
260
|
else:
|
|
267
|
-
|
|
268
|
-
f"DEBUG: Skipping Codex subprocess tracking - tracker={tracker is not None}, parent_event_id={parent_event_id}"
|
|
269
|
-
file=sys.stderr,
|
|
261
|
+
logger.warning(
|
|
262
|
+
f"DEBUG: Skipping Codex subprocess tracking - tracker={tracker is not None}, parent_event_id={parent_event_id}"
|
|
270
263
|
)
|
|
271
264
|
|
|
272
265
|
result = subprocess.run(
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""Copilot spawner implementation."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import subprocess
|
|
4
|
-
import sys
|
|
5
5
|
import time
|
|
6
6
|
from typing import TYPE_CHECKING, Any
|
|
7
7
|
|
|
8
8
|
from .base import AIResult, BaseSpawner
|
|
9
9
|
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
10
12
|
if TYPE_CHECKING:
|
|
11
13
|
from htmlgraph.sdk import SDK
|
|
12
14
|
|
|
@@ -129,15 +131,11 @@ class CopilotSpawner(BaseSpawner):
|
|
|
129
131
|
|
|
130
132
|
# Record subprocess invocation if tracker is available
|
|
131
133
|
subprocess_event_id = None
|
|
132
|
-
|
|
133
|
-
f"DEBUG: tracker={tracker is not None}, parent_event_id={parent_event_id}"
|
|
134
|
-
file=sys.stderr,
|
|
134
|
+
logger.warning(
|
|
135
|
+
f"DEBUG: tracker={tracker is not None}, parent_event_id={parent_event_id}"
|
|
135
136
|
)
|
|
136
137
|
if tracker and parent_event_id:
|
|
137
|
-
|
|
138
|
-
"DEBUG: Recording subprocess invocation for Copilot...",
|
|
139
|
-
file=sys.stderr,
|
|
140
|
-
)
|
|
138
|
+
logger.debug("Recording subprocess invocation for Copilot...")
|
|
141
139
|
try:
|
|
142
140
|
subprocess_event = tracker.record_tool_call(
|
|
143
141
|
tool_name="subprocess.copilot",
|
|
@@ -147,23 +145,20 @@ class CopilotSpawner(BaseSpawner):
|
|
|
147
145
|
)
|
|
148
146
|
if subprocess_event:
|
|
149
147
|
subprocess_event_id = subprocess_event.get("event_id")
|
|
150
|
-
|
|
151
|
-
f"DEBUG: Subprocess event created for Copilot: {subprocess_event_id}"
|
|
152
|
-
file=sys.stderr,
|
|
148
|
+
logger.warning(
|
|
149
|
+
f"DEBUG: Subprocess event created for Copilot: {subprocess_event_id}"
|
|
153
150
|
)
|
|
154
151
|
else:
|
|
155
|
-
|
|
152
|
+
logger.debug("subprocess_event was None")
|
|
156
153
|
except Exception as e:
|
|
157
154
|
# Tracking failure should not break execution
|
|
158
|
-
|
|
159
|
-
f"DEBUG: Exception recording Copilot subprocess: {e}"
|
|
160
|
-
file=sys.stderr,
|
|
155
|
+
logger.warning(
|
|
156
|
+
f"DEBUG: Exception recording Copilot subprocess: {e}"
|
|
161
157
|
)
|
|
162
158
|
pass
|
|
163
159
|
else:
|
|
164
|
-
|
|
165
|
-
f"DEBUG: Skipping Copilot subprocess tracking - tracker={tracker is not None}, parent_event_id={parent_event_id}"
|
|
166
|
-
file=sys.stderr,
|
|
160
|
+
logger.warning(
|
|
161
|
+
f"DEBUG: Skipping Copilot subprocess tracking - tracker={tracker is not None}, parent_event_id={parent_event_id}"
|
|
167
162
|
)
|
|
168
163
|
|
|
169
164
|
result = subprocess.run(
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"""Gemini spawner implementation."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import logging
|
|
4
5
|
import subprocess
|
|
5
|
-
import sys
|
|
6
6
|
import time
|
|
7
7
|
from typing import TYPE_CHECKING, Any
|
|
8
8
|
|
|
9
9
|
from .base import AIResult, BaseSpawner
|
|
10
10
|
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
11
13
|
if TYPE_CHECKING:
|
|
12
14
|
from htmlgraph.sdk import SDK
|
|
13
15
|
|
|
@@ -220,15 +222,11 @@ class GeminiSpawner(BaseSpawner):
|
|
|
220
222
|
|
|
221
223
|
# Record subprocess invocation if tracker is available
|
|
222
224
|
subprocess_event_id = None
|
|
223
|
-
|
|
224
|
-
f"DEBUG: tracker={tracker is not None}, parent_event_id={parent_event_id}"
|
|
225
|
-
file=sys.stderr,
|
|
225
|
+
logger.warning(
|
|
226
|
+
f"DEBUG: tracker={tracker is not None}, parent_event_id={parent_event_id}"
|
|
226
227
|
)
|
|
227
228
|
if tracker and parent_event_id:
|
|
228
|
-
|
|
229
|
-
"DEBUG: Recording subprocess invocation for Gemini...",
|
|
230
|
-
file=sys.stderr,
|
|
231
|
-
)
|
|
229
|
+
logger.debug("Recording subprocess invocation for Gemini...")
|
|
232
230
|
try:
|
|
233
231
|
subprocess_event = tracker.record_tool_call(
|
|
234
232
|
tool_name="subprocess.gemini",
|
|
@@ -238,23 +236,18 @@ class GeminiSpawner(BaseSpawner):
|
|
|
238
236
|
)
|
|
239
237
|
if subprocess_event:
|
|
240
238
|
subprocess_event_id = subprocess_event.get("event_id")
|
|
241
|
-
|
|
242
|
-
f"DEBUG: Subprocess event created for Gemini: {subprocess_event_id}"
|
|
243
|
-
file=sys.stderr,
|
|
239
|
+
logger.warning(
|
|
240
|
+
f"DEBUG: Subprocess event created for Gemini: {subprocess_event_id}"
|
|
244
241
|
)
|
|
245
242
|
else:
|
|
246
|
-
|
|
243
|
+
logger.debug("subprocess_event was None")
|
|
247
244
|
except Exception as e:
|
|
248
245
|
# Tracking failure should not break execution
|
|
249
|
-
|
|
250
|
-
f"DEBUG: Exception recording Gemini subprocess: {e}",
|
|
251
|
-
file=sys.stderr,
|
|
252
|
-
)
|
|
246
|
+
logger.warning(f"DEBUG: Exception recording Gemini subprocess: {e}")
|
|
253
247
|
pass
|
|
254
248
|
else:
|
|
255
|
-
|
|
256
|
-
f"DEBUG: Skipping Gemini subprocess tracking - tracker={tracker is not None}, parent_event_id={parent_event_id}"
|
|
257
|
-
file=sys.stderr,
|
|
249
|
+
logger.warning(
|
|
250
|
+
f"DEBUG: Skipping Gemini subprocess tracking - tracker={tracker is not None}, parent_event_id={parent_event_id}"
|
|
258
251
|
)
|
|
259
252
|
|
|
260
253
|
# Execute with timeout and stderr redirection
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""Subprocess execution with standardized error handling.
|
|
2
4
|
|
|
3
5
|
Provides consistent error handling for Claude Code CLI invocations.
|
|
4
6
|
"""
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
import logging
|
|
8
9
|
import subprocess
|
|
9
10
|
import sys
|
|
10
11
|
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
11
14
|
|
|
12
15
|
class SubprocessRunner:
|
|
13
16
|
"""Execute subprocess commands with error handling."""
|
|
@@ -25,7 +28,7 @@ class SubprocessRunner:
|
|
|
25
28
|
try:
|
|
26
29
|
subprocess.run(cmd, check=False)
|
|
27
30
|
except FileNotFoundError:
|
|
28
|
-
|
|
31
|
+
logger.warning("Error: 'claude' command not found.")
|
|
29
32
|
print(
|
|
30
33
|
"Please install Claude Code CLI: https://code.claude.com",
|
|
31
34
|
file=sys.stderr,
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
logger = logging.getLogger(__name__)
|
|
4
|
+
|
|
1
5
|
"""
|
|
2
6
|
Orchestration helpers for reliable parallel task coordination.
|
|
3
7
|
|
|
@@ -7,9 +11,13 @@ Provides Task ID pattern for retrieving results from parallel delegations.
|
|
|
7
11
|
import time
|
|
8
12
|
import uuid
|
|
9
13
|
from datetime import datetime, timedelta
|
|
10
|
-
from typing import Any
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from htmlgraph.sdk import SDK
|
|
18
|
+
else:
|
|
19
|
+
# Avoid circular import during module initialization
|
|
20
|
+
SDK = None
|
|
13
21
|
|
|
14
22
|
|
|
15
23
|
def generate_task_id() -> str:
|
|
@@ -72,7 +80,7 @@ Provide detailed findings in your response.
|
|
|
72
80
|
|
|
73
81
|
|
|
74
82
|
def get_results_by_task_id(
|
|
75
|
-
sdk: SDK,
|
|
83
|
+
sdk: "SDK",
|
|
76
84
|
task_id: str,
|
|
77
85
|
timeout: int = 60,
|
|
78
86
|
poll_interval: int = 2,
|
|
@@ -138,7 +146,7 @@ def get_results_by_task_id(
|
|
|
138
146
|
|
|
139
147
|
|
|
140
148
|
def parallel_delegate(
|
|
141
|
-
sdk: SDK,
|
|
149
|
+
sdk: "SDK",
|
|
142
150
|
tasks: list[dict[str, str]],
|
|
143
151
|
timeout: int = 120,
|
|
144
152
|
) -> dict[str, dict[str, Any]]:
|
|
@@ -161,7 +169,7 @@ def parallel_delegate(
|
|
|
161
169
|
])
|
|
162
170
|
|
|
163
171
|
for task_id, result in results.items():
|
|
164
|
-
|
|
172
|
+
logger.info(f"{task_id}: {result['findings']}")
|
|
165
173
|
"""
|
|
166
174
|
# Generate task IDs and enhanced prompts
|
|
167
175
|
task_mapping = {}
|
|
@@ -189,7 +197,7 @@ def parallel_delegate(
|
|
|
189
197
|
|
|
190
198
|
|
|
191
199
|
def save_task_results(
|
|
192
|
-
sdk: SDK,
|
|
200
|
+
sdk: "SDK",
|
|
193
201
|
task_id: str,
|
|
194
202
|
description: str,
|
|
195
203
|
results: str,
|
|
@@ -263,7 +271,7 @@ def save_task_results(
|
|
|
263
271
|
|
|
264
272
|
|
|
265
273
|
def validate_and_save(
|
|
266
|
-
sdk: SDK,
|
|
274
|
+
sdk: "SDK",
|
|
267
275
|
task_id: str,
|
|
268
276
|
description: str,
|
|
269
277
|
results: str,
|
|
@@ -303,7 +311,7 @@ def validate_and_save(
|
|
|
303
311
|
)
|
|
304
312
|
|
|
305
313
|
if outcome["validated"]:
|
|
306
|
-
|
|
314
|
+
logger.info(f"✅ Saved to spike: {outcome['spike_id']}")
|
|
307
315
|
"""
|
|
308
316
|
validated = True
|
|
309
317
|
validation_results = None
|
htmlgraph/orchestrator.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
SubagentOrchestrator for context-preserving delegation.
|
|
3
5
|
|
|
@@ -83,7 +85,6 @@ Key Patterns
|
|
|
83
85
|
4. Parallel execution: Multiple subagents can work simultaneously
|
|
84
86
|
"""
|
|
85
87
|
|
|
86
|
-
from __future__ import annotations
|
|
87
88
|
|
|
88
89
|
from dataclasses import dataclass, field
|
|
89
90
|
from datetime import datetime
|
htmlgraph/parallel.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Parallel workflow execution coordinator for multi-agent task processing.
|
|
3
5
|
|
|
@@ -76,7 +78,6 @@ Best Practices:
|
|
|
76
78
|
- Limit to 3-5 parallel agents for optimal results
|
|
77
79
|
"""
|
|
78
80
|
|
|
79
|
-
from __future__ import annotations
|
|
80
81
|
|
|
81
82
|
from dataclasses import dataclass, field
|
|
82
83
|
from datetime import datetime
|
htmlgraph/query_builder.py
CHANGED
htmlgraph/reflection.py
CHANGED
htmlgraph/refs.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Short reference manager for graph nodes.
|
|
3
5
|
|
|
@@ -5,7 +7,6 @@ Manages persistent mapping of short refs (@f1, @t2, @b5) to full node IDs,
|
|
|
5
7
|
enabling AI-friendly snapshots and queries.
|
|
6
8
|
"""
|
|
7
9
|
|
|
8
|
-
from __future__ import annotations
|
|
9
10
|
|
|
10
11
|
import json
|
|
11
12
|
from datetime import datetime
|
htmlgraph/repo_hash.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Repository Hashing and Git Awareness Module.
|
|
3
5
|
|
|
@@ -22,7 +24,6 @@ Architecture:
|
|
|
22
24
|
- monorepo_project: "project-name" (if in monorepo)
|
|
23
25
|
"""
|
|
24
26
|
|
|
25
|
-
from __future__ import annotations
|
|
26
27
|
|
|
27
28
|
import hashlib
|
|
28
29
|
import logging
|