synap-git 2.1.2__tar.gz → 2.1.3__tar.gz

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 (45) hide show
  1. {synap_git-2.1.2 → synap_git-2.1.3}/CHANGELOG.md +6 -0
  2. {synap_git-2.1.2 → synap_git-2.1.3}/PKG-INFO +1 -1
  3. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/__init__.py +1 -1
  4. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/cli/main.py +43 -26
  5. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/indexer/engine.py +65 -43
  6. {synap_git-2.1.2 → synap_git-2.1.3}/.gitignore +0 -0
  7. {synap_git-2.1.2 → synap_git-2.1.3}/.synap.example/README.md +0 -0
  8. {synap_git-2.1.2 → synap_git-2.1.3}/LICENSE.md +0 -0
  9. {synap_git-2.1.2 → synap_git-2.1.3}/README.md +0 -0
  10. {synap_git-2.1.2 → synap_git-2.1.3}/pyproject.toml +0 -0
  11. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/api/__init__.py +0 -0
  12. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/api/app.py +0 -0
  13. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/api/static/index.html +0 -0
  14. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/cli/__init__.py +0 -0
  15. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/cli/__main__.py +0 -0
  16. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/config.py +0 -0
  17. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/diagnostics/__init__.py +0 -0
  18. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/diagnostics/logger.py +0 -0
  19. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/diagnostics/tracing.py +0 -0
  20. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/embeddings/__init__.py +0 -0
  21. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/git/__init__.py +0 -0
  22. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/git/state.py +0 -0
  23. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/indexer/__init__.py +0 -0
  24. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/indexer/daemon.py +0 -0
  25. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/indexer/scanner.py +0 -0
  26. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/indexer/wiki.py +0 -0
  27. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/mcp/__init__.py +0 -0
  28. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/mcp/server.py +0 -0
  29. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/parser/__init__.py +0 -0
  30. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/parser/registry.py +0 -0
  31. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/provider/anthropic.py +0 -0
  32. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/provider/base.py +0 -0
  33. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/provider/factory.py +0 -0
  34. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/provider/gemini.py +0 -0
  35. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/provider/ollama.py +0 -0
  36. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/provider/openai.py +0 -0
  37. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/provider/openrouter.py +0 -0
  38. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/py.typed +0 -0
  39. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/retrieval/__init__.py +0 -0
  40. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/retrieval/engine.py +0 -0
  41. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/retrieval/memory.py +0 -0
  42. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/storage/__init__.py +0 -0
  43. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/storage/sqlite.py +0 -0
  44. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/utils/__init__.py +0 -0
  45. {synap_git-2.1.2 → synap_git-2.1.3}/src/synap_git/utils/serialization.py +0 -0
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.1.3] - 2026-06-11
9
+
10
+ ### Changed
11
+ - **CLI Interactivity Enhancement**: Wrapped all long-running `synap` CLI commands (`init`, `start`, `stop`, `rollback`, `repair`) with interactive rich status spinners to ensure users never stare at an empty screen.
12
+ - **Incremental Indexing Progress**: Added a rich progress status to the `_incremental_index` background loop to provide visibility into parsing tasks during cache hits.
13
+
8
14
  ## [2.1.2] - 2026-06-11
9
15
 
10
16
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: synap-git
3
- Version: 2.1.2
3
+ Version: 2.1.3
4
4
  Summary: Persistent structural context infrastructure for AI coding agents.
5
5
  Project-URL: Homepage, https://github.com/saahilpal/synap-git
6
6
  Project-URL: Repository, https://github.com/saahilpal/synap-git
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "2.1.2"
5
+ __version__ = "2.1.3"
@@ -524,7 +524,13 @@ def init(
524
524
  _auto_protect_synap(settings.repository_path)
525
525
 
526
526
  runtime = SynapRuntime(settings)
527
- commit = runtime.bootstrap(force=force)
527
+ if not quiet and not json_output:
528
+ with console.status(
529
+ "[yellow]Initializing Synap runtime and scanning repository...[/yellow]", spinner="dots"
530
+ ):
531
+ commit = runtime.bootstrap(force=force)
532
+ else:
533
+ commit = runtime.bootstrap(force=force)
528
534
 
529
535
  if json_output:
530
536
  _emit({"active_commit": commit, "state": "initialized"}, json_output=True)
@@ -670,20 +676,22 @@ def start(
670
676
  console.print(f"[bold red]✗ Failed to start daemon:[/bold red] {e}")
671
677
  raise typer.Exit(1)
672
678
 
673
- console.print("[yellow]Starting Synap daemon...[/yellow]")
674
679
  success = False
675
- for _ in range(15): # wait up to 3 seconds
676
- time.sleep(0.2)
677
- if pid_file.exists():
678
- hb = _read_daemon_heartbeat(abs_path)
679
- if hb and hb.get("status") in ("healthy", "degraded"):
680
- success = True
681
- port = hb.get("port", 9876)
682
- pid = int(hb.get("pid", 0))
683
- console.print(f"[green]✓ Synap daemon started (PID {pid})[/green]")
684
- console.print("[green]✓ Runtime healthy[/green]")
685
- console.print(f"[green]✓ UI available at http://127.0.0.1:{port}[/green]")
686
- break
680
+ with console.status(
681
+ "[yellow]Starting Synap daemon and awaiting health check...[/yellow]", spinner="dots"
682
+ ):
683
+ for _ in range(15): # wait up to 3 seconds
684
+ time.sleep(0.2)
685
+ if pid_file.exists():
686
+ hb = _read_daemon_heartbeat(abs_path)
687
+ if hb and hb.get("status") in ("healthy", "degraded"):
688
+ success = True
689
+ port = hb.get("port", 9876)
690
+ pid = int(hb.get("pid", 0))
691
+ console.print(f"[green]✓ Synap daemon started (PID {pid})[/green]")
692
+ console.print("[green]✓ Runtime healthy[/green]")
693
+ console.print(f"[green]✓ UI available at http://127.0.0.1:{port}[/green]")
694
+ break
687
695
 
688
696
  if not success:
689
697
  console.print(
@@ -766,11 +774,15 @@ def stop(
766
774
 
767
775
  # Wait for graceful shutdown (up to 10 seconds)
768
776
  success = False
769
- for _ in range(50):
770
- time.sleep(0.2)
771
- if not _is_process_running(pid):
772
- success = True
773
- break
777
+ with console.status(
778
+ f"[yellow]Waiting for daemon (PID {pid}) to shut down gracefully...[/yellow]",
779
+ spinner="dots",
780
+ ):
781
+ for _ in range(50):
782
+ time.sleep(0.2)
783
+ if not _is_process_running(pid):
784
+ success = True
785
+ break
774
786
 
775
787
  # If graceful failed, force kill
776
788
  if not success:
@@ -1234,7 +1246,10 @@ def rollback(
1234
1246
 
1235
1247
  structlog.get_logger().error("suppressed_error_caught", error=str(e), exc_info=True)
1236
1248
 
1237
- runtime.bootstrap(force=True)
1249
+ with console.status(
1250
+ f"[yellow]Reindexing repository to match {selected_commit}...[/yellow]", spinner="dots"
1251
+ ):
1252
+ runtime.bootstrap(force=True)
1238
1253
  console.print(f"[green]✓ Rolled back successfully to {selected_commit}[/green]")
1239
1254
 
1240
1255
 
@@ -1294,15 +1309,17 @@ def repair(
1294
1309
 
1295
1310
  runtime.initialize_storage()
1296
1311
  console.print("[2/3] Reindexing all symbols")
1297
- runtime.bootstrap(force=True)
1312
+ with console.status("[yellow]Reindexing repository...[/yellow]", spinner="dots"):
1313
+ runtime.bootstrap(force=True)
1298
1314
 
1299
1315
  console.print("[3/3] Regenerating wiki from last known state")
1300
- try:
1301
- runtime.wiki.generate_project_wiki()
1302
- except Exception as e:
1303
- import structlog
1316
+ with console.status("[yellow]Regenerating wiki documentation...[/yellow]", spinner="dots"):
1317
+ try:
1318
+ runtime.wiki.generate_project_wiki()
1319
+ except Exception as e:
1320
+ import structlog
1304
1321
 
1305
- structlog.get_logger().error("suppressed_error_caught", error=str(e), exc_info=True)
1322
+ structlog.get_logger().error("suppressed_error_caught", error=str(e), exc_info=True)
1306
1323
 
1307
1324
  status_info = runtime.status()
1308
1325
  console.print(f"\n[green]✓ Recovery complete. {status_info.files} files restored.[/green]")
@@ -601,55 +601,77 @@ class SynapRuntime:
601
601
  # Process additions and modifications
602
602
  registry = CodeParserRegistry()
603
603
  parsed_results = []
604
- for rel_path in filtered_added_or_modified:
605
- self.logger.info("reindexing_file", path=rel_path)
606
- p = self.settings.repository_path / rel_path
607
604
 
608
- try:
609
- content = p.read_text(encoding="utf-8", errors="replace")
610
- except Exception as e:
611
- self.logger.warning("failed_to_read_file", path=rel_path, error=str(e))
612
- continue
605
+ import contextlib
613
606
 
614
- import hashlib
615
-
616
- content_hash = hashlib.sha256(content.encode("utf-8")).hexdigest()
617
- file_id_hash = hashlib.sha256((rel_path + content_hash).encode("utf-8")).hexdigest()
618
-
619
- parse_result = registry.parse(p, relative_path=rel_path, text=content)
620
- symbols_list = [
621
- {
622
- "symbol_id": sym.stable_id,
623
- "name": sym.name,
624
- "kind": sym.kind,
625
- "start_line": sym.start_line,
626
- "end_line": sym.end_line,
627
- "ast_hash": sym.ast_hash,
628
- "metadata": sym.metadata,
629
- }
630
- for sym in parse_result.symbols
631
- ]
632
-
633
- self.store.upsert_file_and_symbols(
634
- file_id=file_id_hash,
635
- path=rel_path,
636
- git_oid=git_oids.get(rel_path, ""),
637
- content_hash=content_hash,
638
- language=parse_result.language,
639
- symbols=symbols_list,
640
- )
641
- self._schedule_embedding(file_id_hash, symbols_list, content_hash)
607
+ from rich.console import Console
642
608
 
643
- self.store.set_wiki_status(rel_path, None, "stale")
644
- self.store.enqueue_wiki(rel_path)
609
+ console = Console(stderr=True)
610
+ show_progress = (
611
+ self.settings.logging_mode.name == "HUMAN" and len(filtered_added_or_modified) > 0
612
+ )
645
613
 
646
- with self.store.connect() as conn:
647
- conn.execute(
648
- "DELETE FROM edges WHERE source_symbol IN (SELECT symbol_id FROM symbols WHERE file_id = ?)",
649
- (file_id_hash,),
614
+ with (
615
+ console.status(
616
+ "[yellow]Parsing incrementally changed files...[/yellow]", spinner="dots"
617
+ )
618
+ if show_progress
619
+ else contextlib.nullcontext()
620
+ ) as status_ctx:
621
+ for idx, rel_path in enumerate(filtered_added_or_modified, 1):
622
+ if show_progress and hasattr(status_ctx, "update"):
623
+ status_ctx.update( # type: ignore[union-attr]
624
+ f"[yellow]Parsing incrementally changed files ([white]{idx}/{len(filtered_added_or_modified)}[/white])...[/yellow]"
625
+ )
626
+
627
+ self.logger.info("reindexing_file", path=rel_path)
628
+ p = self.settings.repository_path / rel_path
629
+
630
+ try:
631
+ content = p.read_text(encoding="utf-8", errors="replace")
632
+ except Exception as e:
633
+ self.logger.warning("failed_to_read_file", path=rel_path, error=str(e))
634
+ continue
635
+
636
+ import hashlib
637
+
638
+ content_hash = hashlib.sha256(content.encode("utf-8")).hexdigest()
639
+ file_id_hash = hashlib.sha256((rel_path + content_hash).encode("utf-8")).hexdigest()
640
+
641
+ parse_result = registry.parse(p, relative_path=rel_path, text=content)
642
+ symbols_list = [
643
+ {
644
+ "symbol_id": sym.stable_id,
645
+ "name": sym.name,
646
+ "kind": sym.kind,
647
+ "start_line": sym.start_line,
648
+ "end_line": sym.end_line,
649
+ "ast_hash": sym.ast_hash,
650
+ "metadata": sym.metadata,
651
+ }
652
+ for sym in parse_result.symbols
653
+ ]
654
+
655
+ self.store.upsert_file_and_symbols(
656
+ file_id=file_id_hash,
657
+ path=rel_path,
658
+ git_oid=git_oids.get(rel_path, ""),
659
+ content_hash=content_hash,
660
+ language=parse_result.language,
661
+ symbols=symbols_list,
650
662
  )
663
+ self._schedule_embedding(file_id_hash, symbols_list, content_hash)
664
+
665
+ self.store.set_wiki_status(rel_path, None, "stale")
666
+ self.store.enqueue_wiki(rel_path)
667
+
668
+ with self.store.connect() as conn:
669
+ conn.execute(
670
+ "DELETE FROM edges WHERE source_symbol IN (SELECT symbol_id FROM symbols WHERE file_id = ?)",
671
+ (file_id_hash,),
672
+ )
651
673
 
652
- parsed_results.append((file_id_hash, rel_path, list(parse_result.imports)))
674
+ parsed_results.append((file_id_hash, rel_path, list(parse_result.imports)))
653
675
 
654
676
  # Re-resolve edges for changed files
655
677
  if parsed_results:
File without changes
File without changes
File without changes
File without changes