htmlgraph 0.26.5__py3-none-any.whl → 0.26.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. htmlgraph/.htmlgraph/.session-warning-state.json +1 -1
  2. htmlgraph/__init__.py +1 -1
  3. htmlgraph/api/main.py +50 -10
  4. htmlgraph/api/templates/dashboard-redesign.html +608 -54
  5. htmlgraph/api/templates/partials/activity-feed.html +21 -0
  6. htmlgraph/api/templates/partials/features.html +81 -12
  7. htmlgraph/api/templates/partials/orchestration.html +35 -0
  8. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  9. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  10. htmlgraph/cli/__init__.py +42 -0
  11. htmlgraph/cli/__main__.py +6 -0
  12. htmlgraph/cli/analytics.py +939 -0
  13. htmlgraph/cli/base.py +660 -0
  14. htmlgraph/cli/constants.py +206 -0
  15. htmlgraph/cli/core.py +856 -0
  16. htmlgraph/cli/main.py +143 -0
  17. htmlgraph/cli/models.py +462 -0
  18. htmlgraph/cli/templates/__init__.py +1 -0
  19. htmlgraph/cli/templates/cost_dashboard.py +398 -0
  20. htmlgraph/cli/work/__init__.py +159 -0
  21. htmlgraph/cli/work/features.py +567 -0
  22. htmlgraph/cli/work/orchestration.py +675 -0
  23. htmlgraph/cli/work/sessions.py +465 -0
  24. htmlgraph/cli/work/tracks.py +485 -0
  25. htmlgraph/dashboard.html +6414 -634
  26. htmlgraph/db/schema.py +8 -3
  27. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +20 -13
  28. htmlgraph/docs/README.md +2 -3
  29. htmlgraph/hooks/event_tracker.py +355 -26
  30. htmlgraph/hooks/git_commands.py +175 -0
  31. htmlgraph/hooks/orchestrator.py +137 -71
  32. htmlgraph/hooks/orchestrator_reflector.py +23 -0
  33. htmlgraph/hooks/pretooluse.py +29 -6
  34. htmlgraph/hooks/session_handler.py +28 -0
  35. htmlgraph/hooks/session_summary.py +391 -0
  36. htmlgraph/hooks/subagent_detection.py +202 -0
  37. htmlgraph/hooks/subagent_stop.py +71 -12
  38. htmlgraph/hooks/validator.py +192 -79
  39. htmlgraph/operations/__init__.py +18 -0
  40. htmlgraph/operations/initialization.py +596 -0
  41. htmlgraph/operations/initialization.py.backup +228 -0
  42. htmlgraph/orchestration/__init__.py +16 -1
  43. htmlgraph/orchestration/claude_launcher.py +185 -0
  44. htmlgraph/orchestration/command_builder.py +71 -0
  45. htmlgraph/orchestration/headless_spawner.py +72 -1332
  46. htmlgraph/orchestration/plugin_manager.py +136 -0
  47. htmlgraph/orchestration/prompts.py +137 -0
  48. htmlgraph/orchestration/spawners/__init__.py +16 -0
  49. htmlgraph/orchestration/spawners/base.py +194 -0
  50. htmlgraph/orchestration/spawners/claude.py +170 -0
  51. htmlgraph/orchestration/spawners/codex.py +442 -0
  52. htmlgraph/orchestration/spawners/copilot.py +299 -0
  53. htmlgraph/orchestration/spawners/gemini.py +478 -0
  54. htmlgraph/orchestration/subprocess_runner.py +33 -0
  55. htmlgraph/orchestration.md +563 -0
  56. htmlgraph/orchestrator-system-prompt-optimized.txt +620 -55
  57. htmlgraph/orchestrator_config.py +357 -0
  58. htmlgraph/orchestrator_mode.py +45 -12
  59. htmlgraph/transcript.py +16 -4
  60. htmlgraph-0.26.7.data/data/htmlgraph/dashboard.html +6592 -0
  61. {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.7.dist-info}/METADATA +1 -1
  62. {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.7.dist-info}/RECORD +68 -34
  63. {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.7.dist-info}/entry_points.txt +1 -1
  64. htmlgraph/cli.py +0 -7256
  65. htmlgraph-0.26.5.data/data/htmlgraph/dashboard.html +0 -812
  66. {htmlgraph-0.26.5.data → htmlgraph-0.26.7.data}/data/htmlgraph/styles.css +0 -0
  67. {htmlgraph-0.26.5.data → htmlgraph-0.26.7.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  68. {htmlgraph-0.26.5.data → htmlgraph-0.26.7.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  69. {htmlgraph-0.26.5.data → htmlgraph-0.26.7.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  70. {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.7.dist-info}/WHEEL +0 -0
@@ -0,0 +1,228 @@
1
+ """HtmlGraph initialization operations.
2
+
3
+ This module provides functions for initializing the .htmlgraph directory structure,
4
+ creating necessary files, and optionally installing Git hooks.
5
+
6
+ The initialization process includes:
7
+ 1. Directory structure creation (.htmlgraph with subdirectories)
8
+ 2. Database initialization (htmlgraph.db)
9
+ 3. Index creation (index.sqlite)
10
+ 4. Configuration files
11
+ 5. Optional Git hooks installation
12
+
13
+ Extracted from monolithic cmd_init() implementation for better maintainability.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import sqlite3
20
+ import subprocess
21
+ import sys
22
+ from pathlib import Path
23
+ from typing import TYPE_CHECKING
24
+
25
+ if TYPE_CHECKING:
26
+ from htmlgraph.cli.models import InitConfig, InitResult, ValidationResult
27
+
28
+
29
+ # Default collections to create
30
+ DEFAULT_COLLECTIONS = [
31
+ "features",
32
+ "bugs",
33
+ "chores",
34
+ "spikes",
35
+ "epics",
36
+ "phases",
37
+ "tracks",
38
+ "sessions",
39
+ "insights",
40
+ "metrics",
41
+ "agents",
42
+ "cigs",
43
+ ]
44
+
45
+ # Additional directories
46
+ ADDITIONAL_DIRECTORIES = [
47
+ "events",
48
+ "logs",
49
+ "archive-index",
50
+ "archives",
51
+ ]
52
+
53
+
54
+ def initialize_htmlgraph(config: InitConfig) -> InitResult:
55
+ """Initialize .htmlgraph directory structure.
56
+
57
+ Creates the standard HtmlGraph directory structure:
58
+ - .htmlgraph/
59
+ - features/
60
+ - sessions/
61
+ - spikes/
62
+ - bugs/
63
+ - tracks/
64
+ - events/
65
+ - logs/
66
+
67
+ Args:
68
+ config: Initialization configuration
69
+
70
+ Returns:
71
+ InitResult with success status and details
72
+ """
73
+ try:
74
+ # Resolve directory path
75
+ base_dir = Path(config.dir).resolve()
76
+ htmlgraph_dir = base_dir / ".htmlgraph"
77
+
78
+ created_dirs: list[str] = []
79
+ updated_files: list[str] = []
80
+
81
+ # Create main directory
82
+ if not htmlgraph_dir.exists():
83
+ htmlgraph_dir.mkdir(parents=True, exist_ok=True)
84
+ created_dirs.append(str(htmlgraph_dir))
85
+
86
+ # Create standard subdirectories
87
+ subdirs = [
88
+ "features",
89
+ "sessions",
90
+ "spikes",
91
+ "bugs",
92
+ "tracks",
93
+ "events",
94
+ "logs",
95
+ "logs/errors",
96
+ ]
97
+
98
+ for subdir in subdirs:
99
+ subdir_path = htmlgraph_dir / subdir
100
+ if not subdir_path.exists():
101
+ subdir_path.mkdir(parents=True, exist_ok=True)
102
+ created_dirs.append(str(subdir_path))
103
+
104
+ # Create .gitkeep in events directory (unless disabled)
105
+ if not config.no_events_keep:
106
+ events_dir = htmlgraph_dir / "events"
107
+ gitkeep_file = events_dir / ".gitkeep"
108
+ if not gitkeep_file.exists():
109
+ gitkeep_file.touch()
110
+ updated_files.append(str(gitkeep_file))
111
+
112
+ # Update .gitignore (unless disabled)
113
+ if not config.no_update_gitignore:
114
+ gitignore_path = base_dir / ".gitignore"
115
+ gitignore_entries = [
116
+ "# HtmlGraph cache files",
117
+ ".htmlgraph/index.sqlite",
118
+ ".htmlgraph/htmlgraph.db",
119
+ ".htmlgraph/sessions/*.jsonl",
120
+ ".htmlgraph/events/*.jsonl",
121
+ ".htmlgraph/parent-activity.json",
122
+ ]
123
+
124
+ # Read existing .gitignore
125
+ existing_entries: set[str] = set()
126
+ if gitignore_path.exists():
127
+ existing_entries = set(
128
+ line.strip() for line in gitignore_path.read_text().splitlines()
129
+ )
130
+
131
+ # Add missing entries
132
+ new_entries = [
133
+ entry for entry in gitignore_entries if entry not in existing_entries
134
+ ]
135
+
136
+ if new_entries:
137
+ mode = "a" if gitignore_path.exists() else "w"
138
+ with gitignore_path.open(mode) as f:
139
+ if mode == "a":
140
+ f.write("\n") # Add newline before appending
141
+ f.write("\n".join(new_entries) + "\n")
142
+ updated_files.append(str(gitignore_path))
143
+
144
+ # Initialize analytics index (unless disabled)
145
+ if not config.no_index:
146
+ from htmlgraph.analytics_index import AnalyticsIndex
147
+
148
+ index_path = htmlgraph_dir / "index.sqlite"
149
+ if not index_path.exists():
150
+ # Create empty index (will be populated on first use)
151
+ index = AnalyticsIndex(str(index_path))
152
+ index.ensure_schema()
153
+ created_dirs.append(str(index_path))
154
+
155
+ # Install Git hooks (if requested)
156
+ if config.install_hooks:
157
+ from htmlgraph.operations.hooks import install_hooks
158
+
159
+ try:
160
+ hooks_result = install_hooks(project_dir=base_dir, use_copy=False)
161
+ if hooks_result.installed:
162
+ updated_files.extend(hooks_result.installed)
163
+ if hooks_result.warnings:
164
+ for warning in hooks_result.warnings:
165
+ print(f"Warning: {warning}", file=sys.stderr)
166
+ except Exception as e:
167
+ # Non-fatal: hooks installation failed but init succeeded
168
+ print(f"Warning: Failed to install hooks: {e}", file=sys.stderr)
169
+
170
+ # Interactive setup wizard (if requested)
171
+ if config.interactive:
172
+ _run_interactive_setup(htmlgraph_dir)
173
+
174
+ # Build success message
175
+ message_parts = []
176
+ if created_dirs:
177
+ message_parts.append(f"Created {len(created_dirs)} directories")
178
+ if updated_files:
179
+ message_parts.append(f"Updated {len(updated_files)} files")
180
+
181
+ message = ", ".join(message_parts) if message_parts else "Already initialized"
182
+
183
+ return InitResult(
184
+ success=True,
185
+ directory=htmlgraph_dir,
186
+ message=message,
187
+ created_dirs=created_dirs,
188
+ updated_files=updated_files,
189
+ )
190
+
191
+ except Exception as e:
192
+ return InitResult(
193
+ success=False,
194
+ directory=Path(config.dir).resolve() / ".htmlgraph",
195
+ error=f"Initialization failed: {e}",
196
+ )
197
+
198
+
199
+ def _run_interactive_setup(htmlgraph_dir: Path) -> None:
200
+ """Run interactive setup wizard.
201
+
202
+ Args:
203
+ htmlgraph_dir: Path to .htmlgraph directory
204
+ """
205
+ print("\n=== HtmlGraph Interactive Setup ===\n")
206
+
207
+ # Ask about project name
208
+ project_name = input("Project name (optional, press Enter to skip): ").strip()
209
+
210
+ # Ask about default agent
211
+ default_agent = input("Default agent name (default: claude): ").strip()
212
+ if not default_agent:
213
+ default_agent = "claude"
214
+
215
+ # Create config file
216
+ config_file = htmlgraph_dir / "config.json"
217
+ if not config_file.exists():
218
+ import json
219
+
220
+ config_data = {}
221
+ if project_name:
222
+ config_data["project_name"] = project_name
223
+ config_data["default_agent"] = default_agent
224
+
225
+ config_file.write_text(json.dumps(config_data, indent=2) + "\n")
226
+ print(f"\n✓ Created config file: {config_file}")
227
+
228
+ print("\n✓ Interactive setup complete!\n")
@@ -9,6 +9,15 @@ from .model_selection import (
9
9
  get_fallback_chain,
10
10
  select_model,
11
11
  )
12
+
13
+ # Export modular spawners for advanced usage
14
+ from .spawners import (
15
+ BaseSpawner,
16
+ ClaudeSpawner,
17
+ CodexSpawner,
18
+ CopilotSpawner,
19
+ GeminiSpawner,
20
+ )
12
21
  from .task_coordination import (
13
22
  delegate_with_id,
14
23
  generate_task_id,
@@ -19,9 +28,15 @@ from .task_coordination import (
19
28
  )
20
29
 
21
30
  __all__ = [
22
- # Headless AI spawning
31
+ # Headless AI spawning (unified interface)
23
32
  "HeadlessSpawner",
24
33
  "AIResult",
34
+ # Modular spawner implementations
35
+ "BaseSpawner",
36
+ "GeminiSpawner",
37
+ "CodexSpawner",
38
+ "CopilotSpawner",
39
+ "ClaudeSpawner",
25
40
  # Model selection
26
41
  "ModelSelection",
27
42
  "TaskType",
@@ -0,0 +1,185 @@
1
+ """Claude Code launcher with multiple integration modes.
2
+
3
+ Coordinates launching Claude Code with various HtmlGraph integration options.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ from htmlgraph.orchestration.command_builder import ClaudeCommandBuilder
13
+ from htmlgraph.orchestration.plugin_manager import PluginManager
14
+ from htmlgraph.orchestration.prompts import get_orchestrator_prompt
15
+ from htmlgraph.orchestration.subprocess_runner import SubprocessRunner
16
+
17
+
18
+ class ClaudeLauncher:
19
+ """Launch Claude Code with various HtmlGraph integration modes.
20
+
21
+ Supports four launch scenarios:
22
+ 1. --init: Orchestrator mode with plugin installation
23
+ 2. --continue: Resume last session with orchestrator rules
24
+ 3. --dev: Development mode with local plugin
25
+ 4. default: Minimal orchestrator rules
26
+ """
27
+
28
+ def __init__(self, args: argparse.Namespace) -> None:
29
+ """Initialize launcher with parsed arguments.
30
+
31
+ Args:
32
+ args: Parsed command-line arguments
33
+ """
34
+ self.args = args
35
+ self.interactive = not (args.quiet or args.format == "json")
36
+
37
+ def launch(self) -> None:
38
+ """Main entry point - routes to appropriate scenario.
39
+
40
+ Raises:
41
+ SystemExit: On error during launch
42
+ """
43
+ try:
44
+ if self.args.init:
45
+ self._launch_orchestrator_mode()
46
+ elif self.args.continue_session:
47
+ self._launch_resume_mode()
48
+ elif self.args.dev:
49
+ self._launch_dev_mode()
50
+ else:
51
+ self._launch_default_mode()
52
+ except Exception as e:
53
+ print(f"Error: Failed to start Claude Code: {e}", file=sys.stderr)
54
+ sys.exit(1)
55
+
56
+ def _launch_orchestrator_mode(self) -> None:
57
+ """Launch with orchestrator prompt (--init).
58
+
59
+ Installs plugin, loads orchestrator system prompt, and starts Claude Code
60
+ in orchestrator mode with multi-AI delegation rules.
61
+ """
62
+ # Install plugin
63
+ PluginManager.install_or_update(verbose=self.interactive)
64
+
65
+ # Load prompt
66
+ prompt = get_orchestrator_prompt(include_dev_mode=False)
67
+
68
+ # Show banner
69
+ if self.interactive:
70
+ self._print_orchestrator_banner()
71
+
72
+ # Build command
73
+ cmd = ClaudeCommandBuilder().with_system_prompt(prompt).build()
74
+
75
+ # Execute
76
+ SubprocessRunner.run_claude_command(cmd)
77
+
78
+ def _launch_resume_mode(self) -> None:
79
+ """Resume last session with orchestrator rules (--continue).
80
+
81
+ Installs plugin, loads plugin directory, and resumes the last Claude Code
82
+ session with orchestrator system prompt.
83
+ """
84
+ # Install plugin
85
+ PluginManager.install_or_update(verbose=self.interactive)
86
+
87
+ # Get plugin directory
88
+ plugin_dir = PluginManager.get_plugin_dir()
89
+
90
+ # Load prompt
91
+ prompt = get_orchestrator_prompt(include_dev_mode=False)
92
+
93
+ # Show status
94
+ if self.interactive:
95
+ print("Resuming last Claude Code session...")
96
+ print(" ✓ Multi-AI delegation rules injected")
97
+
98
+ # Build command
99
+ builder = ClaudeCommandBuilder().with_resume().with_system_prompt(prompt)
100
+
101
+ # Add plugin directory if exists
102
+ if plugin_dir.exists():
103
+ builder.with_plugin_dir(str(plugin_dir))
104
+ if self.interactive:
105
+ print(f" ✓ Loading plugin from: {plugin_dir}")
106
+
107
+ cmd = builder.build()
108
+
109
+ # Execute
110
+ SubprocessRunner.run_claude_command(cmd)
111
+
112
+ def _launch_dev_mode(self) -> None:
113
+ """Launch with local plugin for development (--dev).
114
+
115
+ Loads plugin from local source directory for development/testing.
116
+ Changes to plugin files take effect after restart.
117
+ """
118
+ # Get and validate plugin directory
119
+ plugin_dir = PluginManager.get_plugin_dir()
120
+ PluginManager.validate_plugin_dir(plugin_dir)
121
+
122
+ # Load prompt with dev mode
123
+ prompt = get_orchestrator_prompt(include_dev_mode=True)
124
+
125
+ # Show banner
126
+ if self.interactive:
127
+ self._print_dev_mode_banner(plugin_dir)
128
+
129
+ # Build command
130
+ cmd = (
131
+ ClaudeCommandBuilder()
132
+ .with_plugin_dir(str(plugin_dir))
133
+ .with_system_prompt(prompt)
134
+ .build()
135
+ )
136
+
137
+ # Execute
138
+ SubprocessRunner.run_claude_command(cmd)
139
+
140
+ def _launch_default_mode(self) -> None:
141
+ """Launch with minimal orchestrator rules (default).
142
+
143
+ Starts Claude Code with basic multi-AI delegation rules but no plugin.
144
+ """
145
+ # Load prompt
146
+ prompt = get_orchestrator_prompt(include_dev_mode=False)
147
+
148
+ # Show status
149
+ if self.interactive:
150
+ print("Starting Claude Code with multi-AI delegation rules...")
151
+
152
+ # Build command
153
+ cmd = ClaudeCommandBuilder().with_system_prompt(prompt).build()
154
+
155
+ # Execute
156
+ SubprocessRunner.run_claude_command(cmd)
157
+
158
+ def _print_orchestrator_banner(self) -> None:
159
+ """Print orchestrator mode banner."""
160
+ print("=" * 60)
161
+ print("🤖 HtmlGraph Orchestrator Mode")
162
+ print("=" * 60)
163
+ print("\nStarting Claude Code with orchestrator system prompt...")
164
+ print("Key directives:")
165
+ print(" ✓ Delegate to Gemini (FREE), Codex, Copilot first")
166
+ print(" ✓ Use Task() only as fallback")
167
+ print(" ✓ Create work items before delegating")
168
+ print(" ✓ Track all work in .htmlgraph/")
169
+ print()
170
+
171
+ def _print_dev_mode_banner(self, plugin_dir: Path) -> None:
172
+ """Print development mode banner.
173
+
174
+ Args:
175
+ plugin_dir: Path to local plugin directory
176
+ """
177
+ print("=" * 60)
178
+ print("🔧 HtmlGraph Development Mode")
179
+ print("=" * 60)
180
+ print(f"\nLoading plugin from: {plugin_dir}")
181
+ print(" ✓ Skills, agents, and hooks will be loaded from local files")
182
+ print(" ✓ Orchestrator system prompt will be appended")
183
+ print(" ✓ Multi-AI delegation rules will be injected")
184
+ print(" ✓ Changes to plugin files will take effect after restart")
185
+ print()
@@ -0,0 +1,71 @@
1
+ """Command builder for Claude Code CLI invocations.
2
+
3
+ Provides fluent interface for constructing Claude CLI commands.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from pathlib import Path
12
+
13
+
14
+ class ClaudeCommandBuilder:
15
+ """Fluent builder for Claude Code CLI commands.
16
+
17
+ Example:
18
+ >>> builder = ClaudeCommandBuilder()
19
+ >>> cmd = builder.with_resume() \\
20
+ ... .with_plugin_dir("/path/to/plugin") \\
21
+ ... .with_system_prompt("System prompt text") \\
22
+ ... .build()
23
+ >>> # Result: ["claude", "--resume", "--plugin-dir", "/path/to/plugin",
24
+ ... # "--append-system-prompt", "System prompt text"]
25
+ """
26
+
27
+ def __init__(self) -> None:
28
+ """Initialize with base command."""
29
+ self._cmd: list[str] = ["claude"]
30
+
31
+ def with_resume(self) -> ClaudeCommandBuilder:
32
+ """Add --resume flag to resume last session.
33
+
34
+ Returns:
35
+ Self for method chaining
36
+ """
37
+ self._cmd.append("--resume")
38
+ return self
39
+
40
+ def with_plugin_dir(self, plugin_dir: str | Path) -> ClaudeCommandBuilder:
41
+ """Add --plugin-dir flag.
42
+
43
+ Args:
44
+ plugin_dir: Path to plugin directory
45
+
46
+ Returns:
47
+ Self for method chaining
48
+ """
49
+ self._cmd.extend(["--plugin-dir", str(plugin_dir)])
50
+ return self
51
+
52
+ def with_system_prompt(self, prompt: str) -> ClaudeCommandBuilder:
53
+ """Add --append-system-prompt flag.
54
+
55
+ Args:
56
+ prompt: System prompt text to append
57
+
58
+ Returns:
59
+ Self for method chaining
60
+ """
61
+ if prompt: # Only add if prompt is not empty
62
+ self._cmd.extend(["--append-system-prompt", prompt])
63
+ return self
64
+
65
+ def build(self) -> list[str]:
66
+ """Build the final command list.
67
+
68
+ Returns:
69
+ List of command arguments ready for subprocess.run()
70
+ """
71
+ return self._cmd