htmlgraph 0.26.4__py3-none-any.whl → 0.26.6__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 (69) 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 +189 -35
  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/validator.py +192 -79
  38. htmlgraph/operations/__init__.py +18 -0
  39. htmlgraph/operations/initialization.py +596 -0
  40. htmlgraph/operations/initialization.py.backup +228 -0
  41. htmlgraph/orchestration/__init__.py +16 -1
  42. htmlgraph/orchestration/claude_launcher.py +185 -0
  43. htmlgraph/orchestration/command_builder.py +71 -0
  44. htmlgraph/orchestration/headless_spawner.py +72 -1332
  45. htmlgraph/orchestration/plugin_manager.py +136 -0
  46. htmlgraph/orchestration/prompts.py +137 -0
  47. htmlgraph/orchestration/spawners/__init__.py +16 -0
  48. htmlgraph/orchestration/spawners/base.py +194 -0
  49. htmlgraph/orchestration/spawners/claude.py +170 -0
  50. htmlgraph/orchestration/spawners/codex.py +442 -0
  51. htmlgraph/orchestration/spawners/copilot.py +299 -0
  52. htmlgraph/orchestration/spawners/gemini.py +478 -0
  53. htmlgraph/orchestration/subprocess_runner.py +33 -0
  54. htmlgraph/orchestration.md +563 -0
  55. htmlgraph/orchestrator-system-prompt-optimized.txt +620 -55
  56. htmlgraph/orchestrator_config.py +357 -0
  57. htmlgraph/orchestrator_mode.py +45 -12
  58. htmlgraph/transcript.py +16 -4
  59. htmlgraph-0.26.6.data/data/htmlgraph/dashboard.html +6592 -0
  60. {htmlgraph-0.26.4.dist-info → htmlgraph-0.26.6.dist-info}/METADATA +1 -1
  61. {htmlgraph-0.26.4.dist-info → htmlgraph-0.26.6.dist-info}/RECORD +67 -33
  62. {htmlgraph-0.26.4.dist-info → htmlgraph-0.26.6.dist-info}/entry_points.txt +1 -1
  63. htmlgraph/cli.py +0 -7256
  64. htmlgraph-0.26.4.data/data/htmlgraph/dashboard.html +0 -812
  65. {htmlgraph-0.26.4.data → htmlgraph-0.26.6.data}/data/htmlgraph/styles.css +0 -0
  66. {htmlgraph-0.26.4.data → htmlgraph-0.26.6.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  67. {htmlgraph-0.26.4.data → htmlgraph-0.26.6.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  68. {htmlgraph-0.26.4.data → htmlgraph-0.26.6.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  69. {htmlgraph-0.26.4.dist-info → htmlgraph-0.26.6.dist-info}/WHEEL +0 -0
@@ -0,0 +1,596 @@
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
+ from pathlib import Path
22
+ from typing import TYPE_CHECKING
23
+
24
+ if TYPE_CHECKING:
25
+ from htmlgraph.cli.models import InitConfig, InitResult, ValidationResult
26
+
27
+
28
+ # Default collections to create
29
+ DEFAULT_COLLECTIONS = [
30
+ "features",
31
+ "bugs",
32
+ "chores",
33
+ "spikes",
34
+ "epics",
35
+ "tracks",
36
+ "sessions",
37
+ "insights",
38
+ "metrics",
39
+ "cigs",
40
+ "patterns", # Learning collection (SDK patterns API)
41
+ "todos", # Persistent task tracking
42
+ "task-delegations", # Spawned agent observability
43
+ ]
44
+
45
+ # Additional directories
46
+ ADDITIONAL_DIRECTORIES = [
47
+ "events",
48
+ "logs",
49
+ "archive-index",
50
+ "archives",
51
+ ]
52
+
53
+
54
+ def validate_directory(base_dir: Path) -> ValidationResult:
55
+ """
56
+ Validate that directory is ready for initialization.
57
+
58
+ Args:
59
+ base_dir: Directory to validate
60
+
61
+ Returns:
62
+ ValidationResult with validation status and details
63
+ """
64
+ from htmlgraph.cli.models import ValidationResult
65
+
66
+ result = ValidationResult(exists=base_dir.exists())
67
+
68
+ # Check if directory exists
69
+ if not base_dir.exists():
70
+ result.valid = False
71
+ result.errors.append(f"Directory does not exist: {base_dir}")
72
+ return result
73
+
74
+ # Check if already initialized
75
+ graph_dir = base_dir / ".htmlgraph"
76
+ if graph_dir.exists():
77
+ result.is_initialized = True
78
+
79
+ # Check for nested .htmlgraph directory (initialization corruption bug)
80
+ nested_graph = graph_dir / ".htmlgraph"
81
+ if nested_graph.exists():
82
+ result.errors.append(
83
+ f"ERROR: Nested .htmlgraph directory detected at {nested_graph}\n"
84
+ " This indicates initialization corruption.\n"
85
+ " Fix: Remove nested directory with: rm -rf .htmlgraph/.htmlgraph/"
86
+ )
87
+ result.valid = False
88
+ return result
89
+
90
+ result.errors.append(
91
+ f"Directory already initialized: {graph_dir}\n"
92
+ " Directory already contains .htmlgraph folder"
93
+ )
94
+ result.valid = False
95
+ return result
96
+
97
+ # Check if in git repository
98
+ try:
99
+ subprocess.run(
100
+ ["git", "rev-parse", "--git-dir"],
101
+ cwd=base_dir,
102
+ capture_output=True,
103
+ check=True,
104
+ )
105
+ result.has_git = True
106
+ except (subprocess.CalledProcessError, FileNotFoundError):
107
+ result.has_git = False
108
+
109
+ return result
110
+
111
+
112
+ def create_directory_structure(base_dir: Path) -> list[str]:
113
+ """
114
+ Create the .htmlgraph directory structure.
115
+
116
+ Args:
117
+ base_dir: Base directory (usually current working directory)
118
+
119
+ Returns:
120
+ List of created directory paths
121
+ """
122
+ created = []
123
+ graph_dir = base_dir / ".htmlgraph"
124
+
125
+ # Create main graph directory
126
+ if not graph_dir.exists():
127
+ graph_dir.mkdir(parents=True)
128
+ created.append(str(graph_dir))
129
+
130
+ # Create collection directories
131
+ for collection in DEFAULT_COLLECTIONS:
132
+ coll_dir = graph_dir / collection
133
+ if not coll_dir.exists():
134
+ coll_dir.mkdir(parents=True)
135
+ created.append(str(coll_dir))
136
+
137
+ # Create additional directories
138
+ for dirname in ADDITIONAL_DIRECTORIES:
139
+ add_dir = graph_dir / dirname
140
+ if not add_dir.exists():
141
+ add_dir.mkdir(parents=True)
142
+ created.append(str(add_dir))
143
+
144
+ # Create logs subdirectory for errors
145
+ logs_errors = graph_dir / "logs" / "errors"
146
+ if not logs_errors.exists():
147
+ logs_errors.mkdir(parents=True)
148
+ created.append(str(logs_errors))
149
+
150
+ return created
151
+
152
+
153
+ def create_database(graph_dir: Path) -> str:
154
+ """
155
+ Create the SQLite database for agent events and sessions.
156
+
157
+ Args:
158
+ graph_dir: Path to .htmlgraph directory
159
+
160
+ Returns:
161
+ Path to created database file
162
+ """
163
+ db_path = graph_dir / "htmlgraph.db"
164
+
165
+ # Create database with schema
166
+ conn = sqlite3.connect(db_path)
167
+ cursor = conn.cursor()
168
+
169
+ # Sessions table
170
+ cursor.execute("""
171
+ CREATE TABLE IF NOT EXISTS sessions (
172
+ session_id TEXT PRIMARY KEY,
173
+ agent TEXT NOT NULL,
174
+ status TEXT DEFAULT 'active',
175
+ started_at TEXT NOT NULL,
176
+ ended_at TEXT,
177
+ event_count INTEGER DEFAULT 0,
178
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
179
+ )
180
+ """)
181
+
182
+ # Agent events table
183
+ cursor.execute("""
184
+ CREATE TABLE IF NOT EXISTS agent_events (
185
+ event_id TEXT PRIMARY KEY,
186
+ session_id TEXT NOT NULL,
187
+ tool_name TEXT NOT NULL,
188
+ timestamp TEXT NOT NULL,
189
+ success INTEGER DEFAULT 1,
190
+ feature_id TEXT,
191
+ work_type TEXT,
192
+ context TEXT,
193
+ FOREIGN KEY (session_id) REFERENCES sessions(session_id)
194
+ )
195
+ """)
196
+
197
+ # Create indexes for common queries
198
+ cursor.execute("""
199
+ CREATE INDEX IF NOT EXISTS idx_events_session
200
+ ON agent_events(session_id)
201
+ """)
202
+
203
+ cursor.execute("""
204
+ CREATE INDEX IF NOT EXISTS idx_events_timestamp
205
+ ON agent_events(timestamp)
206
+ """)
207
+
208
+ cursor.execute("""
209
+ CREATE INDEX IF NOT EXISTS idx_events_feature
210
+ ON agent_events(feature_id)
211
+ """)
212
+
213
+ conn.commit()
214
+ conn.close()
215
+
216
+ return str(db_path)
217
+
218
+
219
+ def create_analytics_index(graph_dir: Path) -> str:
220
+ """
221
+ Create the analytics cache database (index.sqlite).
222
+
223
+ Args:
224
+ graph_dir: Path to .htmlgraph directory
225
+
226
+ Returns:
227
+ Path to created index file
228
+ """
229
+ index_path = graph_dir / "index.sqlite"
230
+
231
+ try:
232
+ # Use AnalyticsIndex if available to ensure proper schema
233
+ from htmlgraph.analytics_index import AnalyticsIndex
234
+
235
+ index = AnalyticsIndex(str(index_path))
236
+ index.ensure_schema()
237
+ except ImportError:
238
+ # Fallback to simple creation if AnalyticsIndex not available
239
+ conn = sqlite3.connect(index_path)
240
+ cursor = conn.cursor()
241
+
242
+ # Create basic cache table structure
243
+ cursor.execute("""
244
+ CREATE TABLE IF NOT EXISTS cache_metadata (
245
+ key TEXT PRIMARY KEY,
246
+ value TEXT,
247
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP
248
+ )
249
+ """)
250
+
251
+ # Store schema version
252
+ cursor.execute("""
253
+ INSERT OR REPLACE INTO cache_metadata (key, value)
254
+ VALUES ('schema_version', '1.0')
255
+ """)
256
+
257
+ conn.commit()
258
+ conn.close()
259
+
260
+ return str(index_path)
261
+
262
+
263
+ def create_config_files(graph_dir: Path) -> list[str]:
264
+ """
265
+ Create initial configuration files.
266
+
267
+ Args:
268
+ graph_dir: Path to .htmlgraph directory
269
+
270
+ Returns:
271
+ List of created config file paths
272
+ """
273
+ created = []
274
+
275
+ # Create hooks-config.json
276
+ hooks_config = graph_dir / "hooks-config.json"
277
+ if not hooks_config.exists():
278
+ hooks_config.write_text(
279
+ json.dumps(
280
+ {
281
+ "enabled": True,
282
+ "track_events": True,
283
+ "detect_drift": True,
284
+ "auto_spikes": False,
285
+ },
286
+ indent=2,
287
+ )
288
+ )
289
+ created.append(str(hooks_config))
290
+
291
+ # Create drift-queue.json
292
+ drift_queue = graph_dir / "drift-queue.json"
293
+ if not drift_queue.exists():
294
+ drift_queue.write_text(json.dumps([], indent=2))
295
+ created.append(str(drift_queue))
296
+
297
+ # Create active-auto-spikes.json
298
+ auto_spikes = graph_dir / "active-auto-spikes.json"
299
+ if not auto_spikes.exists():
300
+ auto_spikes.write_text(json.dumps({}, indent=2))
301
+ created.append(str(auto_spikes))
302
+
303
+ # Create .gitkeep for events directory
304
+ events_gitkeep = graph_dir / "events" / ".gitkeep"
305
+ if not events_gitkeep.exists():
306
+ events_gitkeep.touch()
307
+ created.append(str(events_gitkeep))
308
+
309
+ return created
310
+
311
+
312
+ def create_hook_scripts(graph_dir: Path) -> list[str]:
313
+ """
314
+ Copy hook scripts to .htmlgraph/hooks directory.
315
+
316
+ Args:
317
+ graph_dir: Path to .htmlgraph directory
318
+
319
+ Returns:
320
+ List of created hook file paths
321
+ """
322
+ created: list[str] = []
323
+
324
+ try:
325
+ # Get the path to the hooks directory in the package
326
+ import htmlgraph
327
+
328
+ htmlgraph_dir = Path(htmlgraph.__file__).parent
329
+ hooks_src = htmlgraph_dir / "hooks"
330
+
331
+ if not hooks_src.exists():
332
+ return created
333
+
334
+ # Create hooks directory in graph_dir
335
+ hooks_dir = graph_dir / "hooks"
336
+ if not hooks_dir.exists():
337
+ hooks_dir.mkdir(parents=True)
338
+
339
+ # Copy hook scripts
340
+ hook_names = [
341
+ "post-commit.sh",
342
+ "post-checkout.sh",
343
+ "post-merge.sh",
344
+ "pre-push.sh",
345
+ ]
346
+ for hook_name in hook_names:
347
+ src_hook = hooks_src / hook_name
348
+ dest_hook = hooks_dir / hook_name
349
+
350
+ if src_hook.exists() and not dest_hook.exists():
351
+ # Copy the file
352
+ dest_hook.write_text(src_hook.read_text(encoding="utf-8"))
353
+ # Make it executable
354
+ import stat
355
+
356
+ dest_hook.chmod(
357
+ dest_hook.stat().st_mode
358
+ | stat.S_IXUSR
359
+ | stat.S_IXGRP
360
+ | stat.S_IXOTH
361
+ )
362
+ created.append(str(dest_hook))
363
+
364
+ except Exception:
365
+ # Silently fail if hooks can't be copied
366
+ pass
367
+
368
+ return created
369
+
370
+
371
+ def update_gitignore(base_dir: Path) -> str | None:
372
+ """
373
+ Update .gitignore to exclude HtmlGraph cache files.
374
+
375
+ Args:
376
+ base_dir: Base directory containing .gitignore
377
+
378
+ Returns:
379
+ Path to .gitignore if updated, None otherwise
380
+ """
381
+ gitignore_path = base_dir / ".gitignore"
382
+
383
+ # Read existing .gitignore or create new
384
+ existing_lines = []
385
+ if gitignore_path.exists():
386
+ existing_lines = gitignore_path.read_text().splitlines()
387
+
388
+ # Check if HtmlGraph section already exists
389
+ if any("# HtmlGraph" in line for line in existing_lines):
390
+ return None
391
+
392
+ # Add HtmlGraph section
393
+ new_lines = [
394
+ "",
395
+ "# HtmlGraph cache and regenerable files",
396
+ ".htmlgraph/index.sqlite",
397
+ ".htmlgraph/index.sqlite.backup",
398
+ ".htmlgraph/database.db",
399
+ ".htmlgraph/sessions/*.jsonl",
400
+ ".htmlgraph/events/*.jsonl",
401
+ ".htmlgraph/parent-activity.json",
402
+ ".htmlgraph/hook-debug.jsonl",
403
+ ".htmlgraph/errors.jsonl",
404
+ ]
405
+
406
+ # Append to existing .gitignore
407
+ with gitignore_path.open("a") as f:
408
+ f.write("\n".join(new_lines) + "\n")
409
+
410
+ return str(gitignore_path)
411
+
412
+
413
+ def install_git_hooks(base_dir: Path) -> bool:
414
+ """
415
+ Install Git hooks for event logging.
416
+
417
+ Args:
418
+ base_dir: Base directory containing .git
419
+
420
+ Returns:
421
+ True if hooks installed successfully, False otherwise
422
+ """
423
+ # Check if .git directory exists
424
+ git_dir = base_dir / ".git"
425
+ if not git_dir.exists():
426
+ return False
427
+
428
+ # Import hook installation from operations.hooks
429
+ try:
430
+ from htmlgraph.operations.hooks import install_hooks
431
+
432
+ result = install_hooks(project_dir=base_dir, use_copy=False)
433
+ return bool(result.installed)
434
+ except Exception:
435
+ return False
436
+
437
+
438
+ def _run_interactive_setup(graph_dir: Path, result: InitResult) -> None:
439
+ """
440
+ Run interactive setup wizard.
441
+
442
+ Args:
443
+ graph_dir: Path to .htmlgraph directory
444
+ result: InitResult to update with created files
445
+ """
446
+ print("\n=== HtmlGraph Interactive Setup ===\n")
447
+
448
+ # Ask about project name
449
+ project_name = input("Project name (optional, press Enter to skip): ").strip()
450
+
451
+ # Ask about default agent
452
+ default_agent = input("Default agent name (default: claude): ").strip()
453
+ if not default_agent:
454
+ default_agent = "claude"
455
+
456
+ # Create config file
457
+ config_file = graph_dir / "config.json"
458
+ if not config_file.exists():
459
+ config_data = {}
460
+ if project_name:
461
+ config_data["project_name"] = project_name
462
+ config_data["default_agent"] = default_agent
463
+
464
+ config_file.write_text(json.dumps(config_data, indent=2) + "\n")
465
+ result.files_created.append(str(config_file))
466
+ print(f"\n✓ Created config file: {config_file}")
467
+
468
+ print("\n✓ Interactive setup complete!\n")
469
+
470
+
471
+ def create_dashboard_index(base_dir: Path) -> str | None:
472
+ """
473
+ Copy the dashboard HTML file to index.html at the root.
474
+
475
+ Args:
476
+ base_dir: Base directory where index.html should be created
477
+
478
+ Returns:
479
+ Path to created index.html, or None if not created
480
+ """
481
+ try:
482
+ # Get the path to the dashboard template
483
+ import htmlgraph
484
+
485
+ htmlgraph_dir = Path(htmlgraph.__file__).parent
486
+ dashboard_path = htmlgraph_dir / "dashboard.html"
487
+
488
+ if not dashboard_path.exists():
489
+ return None
490
+
491
+ # Copy to root as index.html
492
+ index_path = base_dir / "index.html"
493
+ index_path.write_text(dashboard_path.read_text(encoding="utf-8"))
494
+ return str(index_path)
495
+ except Exception:
496
+ return None
497
+
498
+
499
+ def initialize_htmlgraph(config: InitConfig) -> InitResult:
500
+ """
501
+ Initialize HtmlGraph directory structure.
502
+
503
+ This is the main entry point for the init command.
504
+
505
+ Args:
506
+ config: InitConfig with initialization settings
507
+
508
+ Returns:
509
+ InitResult with initialization details
510
+ """
511
+ from htmlgraph.cli.models import InitResult
512
+
513
+ base_dir = Path(config.dir).resolve()
514
+ graph_dir = base_dir / ".htmlgraph"
515
+
516
+ # Validate directory
517
+ validation = validate_directory(base_dir)
518
+ if not validation.valid:
519
+ return InitResult(
520
+ success=False,
521
+ graph_dir=str(graph_dir),
522
+ errors=validation.errors,
523
+ )
524
+
525
+ result = InitResult(graph_dir=str(graph_dir))
526
+
527
+ try:
528
+ # Create directory structure
529
+ dirs_created = create_directory_structure(base_dir)
530
+ result.directories_created.extend(dirs_created)
531
+
532
+ # Create database
533
+ db_path = create_database(graph_dir)
534
+ result.files_created.append(db_path)
535
+
536
+ # Create analytics index (unless disabled)
537
+ if not config.no_index:
538
+ index_path = create_analytics_index(graph_dir)
539
+ result.files_created.append(index_path)
540
+ else:
541
+ result.warnings.append("Skipped analytics cache creation (--no-index)")
542
+
543
+ # Create config files (unless --no-events-keep)
544
+ if not config.no_events_keep:
545
+ config_files = create_config_files(graph_dir)
546
+ result.files_created.extend(config_files)
547
+ else:
548
+ result.warnings.append("Skipped .gitkeep creation (--no-events-keep)")
549
+
550
+ # Create hook scripts
551
+ hook_files = create_hook_scripts(graph_dir)
552
+ result.files_created.extend(hook_files)
553
+
554
+ # Create dashboard index.html at root
555
+ dashboard_index = create_dashboard_index(base_dir)
556
+ if dashboard_index:
557
+ result.files_created.append(dashboard_index)
558
+
559
+ # Update .gitignore (unless disabled)
560
+ if not config.no_update_gitignore:
561
+ gitignore_path = update_gitignore(base_dir)
562
+ if gitignore_path:
563
+ result.files_created.append(gitignore_path)
564
+ result.warnings.append("Updated .gitignore with HtmlGraph cache rules")
565
+ else:
566
+ result.warnings.append("Skipped .gitignore update (--no-update-gitignore)")
567
+
568
+ # Install Git hooks (if requested)
569
+ if config.install_hooks:
570
+ hooks_installed = install_git_hooks(base_dir)
571
+ result.hooks_installed = hooks_installed
572
+ if hooks_installed:
573
+ result.warnings.append("Installed Git hooks for event logging")
574
+ else:
575
+ result.warnings.append(
576
+ "Failed to install Git hooks (not in git repository?)"
577
+ )
578
+
579
+ # Interactive setup wizard (if requested)
580
+ if config.interactive:
581
+ _run_interactive_setup(graph_dir, result)
582
+
583
+ # Add Git reminder if not initialized
584
+ if not validation.has_git and not config.install_hooks:
585
+ result.warnings.append(
586
+ "Not in a Git repository. Run 'htmlgraph init --install-hooks' "
587
+ "if you want to track Git events."
588
+ )
589
+
590
+ result.success = True
591
+
592
+ except Exception as e:
593
+ result.success = False
594
+ result.errors.append(f"Initialization failed: {e}")
595
+
596
+ return result