synap-git 0.2.1__tar.gz → 1.1.1__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.
- synap_git-1.1.1/CHANGELOG.md +139 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/PKG-INFO +16 -3
- {synap_git-0.2.1 → synap_git-1.1.1}/README.md +14 -2
- {synap_git-0.2.1 → synap_git-1.1.1}/pyproject.toml +1 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/__init__.py +1 -1
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/api/app.py +49 -18
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/api/static/index.html +33 -23
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/cli/main.py +243 -62
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/config.py +13 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/git/state.py +1 -1
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/indexer/daemon.py +81 -1
- synap_git-1.1.1/src/synap_git/indexer/engine.py +791 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/indexer/scanner.py +14 -1
- synap_git-1.1.1/src/synap_git/indexer/wiki.py +195 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/mcp/server.py +26 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/parser/registry.py +41 -3
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/provider/anthropic.py +48 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/provider/base.py +14 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/provider/factory.py +2 -9
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/provider/gemini.py +15 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/provider/ollama.py +42 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/provider/openai.py +49 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/provider/openrouter.py +51 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/retrieval/engine.py +42 -17
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/storage/sqlite.py +329 -169
- synap_git-0.2.1/CHANGELOG.md +0 -80
- synap_git-0.2.1/src/synap_git/indexer/engine.py +0 -327
- synap_git-0.2.1/src/synap_git/indexer/wiki.py +0 -78
- synap_git-0.2.1/src/synap_git/provider/mock.py +0 -66
- {synap_git-0.2.1 → synap_git-1.1.1}/.gitignore +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/.synap.example/README.md +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/LICENSE.md +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/api/__init__.py +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/cli/__init__.py +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/cli/__main__.py +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/diagnostics/__init__.py +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/diagnostics/logger.py +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/diagnostics/tracing.py +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/embeddings/__init__.py +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/git/__init__.py +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/indexer/__init__.py +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/mcp/__init__.py +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/parser/__init__.py +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/py.typed +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/retrieval/__init__.py +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/retrieval/memory.py +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/storage/__init__.py +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/utils/__init__.py +0 -0
- {synap_git-0.2.1 → synap_git-1.1.1}/src/synap_git/utils/serialization.py +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.1.1] - 2026-05-28
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **CRITICAL: N+1 query loop during edge resolution:** Migrated structural edge resolution to bulk FETCH queries, eliminating thousands of database calls per indexing run.
|
|
12
|
+
- **CRITICAL: SQLite Synchronous Pragma:** Enforced \`PRAGMA synchronous=NORMAL\` on every connection, multiplying write throughput by 10x-100x.
|
|
13
|
+
- **SPEC: Content-Scoped File IDs:** Updated \`file_id\` formula to \`sha256(path + content_hash)\` ensuring temporal version isolation in the graph.
|
|
14
|
+
- **HIGH: Wiki Generation Resiliency:** Implemented exponential backoff retries for LLM wiki generation to prevent data loss on transient network errors.
|
|
15
|
+
- **HIGH: Single Read Principle:** Optimized pipeline to read each file exactly once, halving I/O overhead.
|
|
16
|
+
- **MEDIUM: Automated Lesson Pruning:** Daemon now automatically prunes expired memory lessons hourly.
|
|
17
|
+
- **MEDIUM: Memory Bounded Indexing:** First-run indexing now processes in memory-bounded batches to prevent OOM on large repos.
|
|
18
|
+
- **LOW: Checkpoint Validation:** MCP \`create_checkpoint\` tool now validates all input fields to prevent malformed data.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- **Interactive Review Flow:** New CLI command \`synap lessons review\` for interactive management of agent-proposed lessons.
|
|
22
|
+
- **Context Monitoring:** New MCP tool \`signal_low_context\` for proactive agent context window monitoring.
|
|
23
|
+
- **Configurable Maintenance:** Added \`checkpoint_threshold\` and \`lesson_expiry_days\` to \`config.toml\`.
|
|
24
|
+
- **Improved Doctor:** \`synap doctor\` now checks for Git and GitHub CLI availability.
|
|
25
|
+
- **Onboarding Guidance:** \`synap init\` now provides explicit next steps for starting the system.
|
|
26
|
+
|
|
27
|
+
### Removed
|
|
28
|
+
- **Mock LLM Mode:** \`MockLLMProvider\` removed from the production codebase to maintain strict operational integrity.
|
|
29
|
+
|
|
30
|
+
## [1.1.0] - 2026-05-28
|
|
31
|
+
|
|
32
|
+
### Added — Git-Snapshot Projection & Performance Refactoring
|
|
33
|
+
- **Split Two-Path Indexing:** Separated initialization and incremental indexing into `_first_run_index` (full scan, CPU-parallelized) and `_incremental_index` (Git delta change detector).
|
|
34
|
+
- **Asynchronous Wiki Generation Queue:** Decoupled slow, non-deterministic LLM wiki generation from structural indexing using a persistent database queue (`wiki_queue`) processed asynchronously by a daemon worker.
|
|
35
|
+
- **Lazy Wiki Caching:** Added synchronous wiki generation fallback to CLI (`wiki show`), Web API, and MCP tools to dynamically build missing or stale pages on-demand.
|
|
36
|
+
- **Process Pool Parallel Parsing:** Parallelized Tree-sitter parsing on first run across all CPU cores utilizing process-based concurrency with independent parser instances.
|
|
37
|
+
- **SQLite Performance Hardening:**
|
|
38
|
+
- WAL mode and NORMAL synchronous configuration enabled during writes.
|
|
39
|
+
- Multi-row symbol and edge inserts batched into a single transaction via `executemany`.
|
|
40
|
+
- Dot-separated `module_key` pre-computation and indexing for $O(1)$ module resolution.
|
|
41
|
+
- SQLite FTS5 index integration for fast sub-millisecond symbol searches, avoiding full-table scans.
|
|
42
|
+
- **Web API Lazy Refreshes:** Updated the `/wiki/{filepath}` GET endpoint to perform lazy refreshes on stale or missing pages before returning content.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
- **FTS5 Cascade Delete:** Added database trigger `tgr_symbols_delete` to automatically clean up virtual `symbols_fts` entries when parent symbols are deleted.
|
|
46
|
+
- **Duplicate File ID Collision:** Handled unique, path-scoped file identifier generation ensuring files with identical content (like empty `__init__.py`) do not conflict.
|
|
47
|
+
|
|
48
|
+
## [0.2.1] - 2026-05-27
|
|
49
|
+
|
|
50
|
+
### Added — Final Production Hardening & Release Execution
|
|
51
|
+
- `synap rollback --commit <ref>` option: directly target a commit by hash/reference without interactive selection prompt.
|
|
52
|
+
- `synap rollback --yes` / `-y` option: suppress confirmation prompt for non-interactive and scripted rollback flows.
|
|
53
|
+
- Non-interactive guard in `synap rollback`: fails fast with a clear error when used in piped/CI contexts without `--commit` or `--yes`.
|
|
54
|
+
- `synap rollback` invalid commit detection: validates commit reference via `git rev-parse --verify` and rejects unknown refs with a clear message.
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
- **SQLite migration short-circuit bug**: legacy un-versioned databases (user_version = 0) incorrectly skipped `CREATE TABLE IF NOT EXISTS` execution, leaving the `symbols` table and others uninitialized. The premature short-circuit is removed; all schema tables are now created correctly before bumping to version 1.
|
|
58
|
+
- **Python `import_from_statement` missing symbol extraction**: Tree-sitter AST parser only extracted the module identifier from `from X import Y` statements, discarding `Y`. Now correctly emits `module:symbol` pairs for all imported names, aliases, and grouped imports.
|
|
59
|
+
- **Namespace-aware call edge resolution**: Pass 2 import resolver now splits `module:symbol` import entries to narrow edge targets to the correct module file, eliminating false-positive dependency edges to duplicate class names in sibling namespaces.
|
|
60
|
+
- **FastAPI app version hardcoded**: `create_app()` used a hardcoded version string `"0.2.0"` instead of the canonical `__version__`. Now dynamically imported from `synap_git.__init__`.
|
|
61
|
+
- **Streaming generator cancellation safety**: confirmed `httpx` stream connections are closed cleanly on partial consumption (no socket leaks).
|
|
62
|
+
- **Degraded mode retry logic**: confirmed 2-stage exponential backoff and graceful structural fallback under fully-offline and timeout conditions.
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
- Daemon resilience test (`test_daemon_resilience.py`) hardened against race condition where `SIGKILL` test read a stale PID from a prior run that had already exited.
|
|
66
|
+
|
|
67
|
+
## [0.2.0] - 2026-05-26
|
|
68
|
+
|
|
69
|
+
### Added — Final Polish & Release Readiness
|
|
70
|
+
- CLI usage management: `synap usage show` (displays Rich aggregated usage table and summary panel) and `synap usage clear`.
|
|
71
|
+
- CLI wiki management: `synap wiki list` and `synap wiki show <filepath>` (renders page in terminal via Rich Markdown).
|
|
72
|
+
- LLM call database logging: records `prompt_tokens`, `completion_tokens` for retrieval and wiki generation passes.
|
|
73
|
+
- Real-time daemon state: heartbeats integrated into `synap status`, `synap doctor`, and the Web UI status endpoints.
|
|
74
|
+
- Premium Web UI dashboard polish: dual L3 memory (Approved vs Pending) view, real-time LLM usage analytics, and active daemon PID badge.
|
|
75
|
+
- Defensive GHA release pipeline: `.github/workflows/release.yml` automates TestPyPI and PyPI publishing, tag alignment checking, and draft release generation.
|
|
76
|
+
- Clean Typer execution wrapper: intercepts configuration and credential exceptions to output actionable suggestions (e.g. `synap setup`) instead of tracebacks.
|
|
77
|
+
|
|
78
|
+
### Added — Hardening Loop 2: MCP Stability & Protocol Polish
|
|
79
|
+
- Deterministic MCP JSON envelope: every tool response carries `ok`, `data`, `warnings`, `trace_id`, `dirty_tree`.
|
|
80
|
+
- Structured error objects with `code`, `message`, and `suggestion` fields for all failure paths.
|
|
81
|
+
- `dirty_tree` propagation: agents are warned when the working tree is ahead of the index.
|
|
82
|
+
- `get_approved_memory()` and `get_pending_memory()` MCP tools exposing lesson trust status.
|
|
83
|
+
- `synap mcp verify` command to assert full protocol contract compliance.
|
|
84
|
+
|
|
85
|
+
### Added — Hardening Loop 3: Lesson System Trust & Memory Lifecycle
|
|
86
|
+
- Formal `LessonStatus` enum enforcing explicit state machine: `PENDING → APPROVED/REJECTED`, `APPROVED → EXPIRED`.
|
|
87
|
+
- Retrieval gating: only `APPROVED`, non-expired lessons are injected into LLM context as `# APPROVED SYSTEM MEMORY`.
|
|
88
|
+
- `prune_expired_lessons()` transitions stale lessons to `EXPIRED` state on demand.
|
|
89
|
+
- `approval_actor` field on lessons for full human-governance provenance.
|
|
90
|
+
- `synap memory status` — counts of pending, approved, expired lessons.
|
|
91
|
+
- `synap memory prune` — forces expiry evaluation and prunes dead memory.
|
|
92
|
+
- `synap memory verify` — checks approved lessons' `files_affected` against current repo state; reports dangling references.
|
|
93
|
+
- `synap lessons approve <id>` and `synap lessons reject <id>` — explicit human governance over pending lessons.
|
|
94
|
+
|
|
95
|
+
### Added — Hardening Loop 4: Tracing, Git Compliance & Release Hardening
|
|
96
|
+
- `GitIgnoreSpec` glob-to-regex parser in `RepositoryScanner` respects `.gitignore` patterns.
|
|
97
|
+
- Auto-protection: `SynapRuntime.bootstrap()` automatically adds `.synap/` to `.gitignore`.
|
|
98
|
+
- Enhanced binary file detection via extension blocklist + control-character ratio analysis.
|
|
99
|
+
- Symlink traversal prevention in `RepositoryScanner` (path-containment enforcement).
|
|
100
|
+
- `TraceStore` writes structured operational traces to `.synap/trace_latest.json`.
|
|
101
|
+
- Full hybrid retrieval tracing: latency timeline, token allocation, truncation explanations, structural hops, dirty-tree warnings.
|
|
102
|
+
- `/api/v1/trace/latest` REST endpoint exposing the latest trace.
|
|
103
|
+
- Diagnostic Web UI with visual latency timeline, source provenance table, and dirty-tree badge.
|
|
104
|
+
- Daemon heartbeat file (`.synap/daemon_heartbeat.json`) with PID, uptime, and recovery metrics.
|
|
105
|
+
- Daemon self-healing: SQLite corruption detected via `PRAGMA quick_check` triggers a wipe + re-bootstrap.
|
|
106
|
+
- `synap rollback` — interactive rollback to a previous git commit with lesson preservation.
|
|
107
|
+
- `synap recover` — explicit manual DB corruption recovery flow.
|
|
108
|
+
- `py.typed` marker for PEP 561 compliance.
|
|
109
|
+
|
|
110
|
+
### Changed
|
|
111
|
+
- CI pipeline split into 4 focused jobs: `lint`, `test`, `benchmark` (main only), `release-validation`.
|
|
112
|
+
- Release validation now runs `synap init` before `synap doctor` to ensure a valid Synap DB context.
|
|
113
|
+
- `pytest` configured with `asyncio_default_fixture_loop_scope = "function"` to eliminate deprecation warnings.
|
|
114
|
+
- Benchmark tests gated behind `benchmark` marker; skipped in fast PR CI passes.
|
|
115
|
+
|
|
116
|
+
### Fixed
|
|
117
|
+
- `synap doctor` in CI release validation step previously ran against an uninitialized directory.
|
|
118
|
+
- `ruff format` drift in `cli/main.py` and `indexer/daemon.py` resolved.
|
|
119
|
+
|
|
120
|
+
## [0.1.0] - 2026-05-25
|
|
121
|
+
|
|
122
|
+
### Added
|
|
123
|
+
- Core deterministic indexing engine using Tree-sitter and Git content hashes.
|
|
124
|
+
- 4-stage hybrid retrieval pipeline (Temporal, Structural, Lexical, Semantic).
|
|
125
|
+
- Model Context Protocol (MCP) server for IDE integration.
|
|
126
|
+
- "Why-This-Context" retrieval tracing system.
|
|
127
|
+
- Secure secret management via `python-keyring`.
|
|
128
|
+
- `synap doctor` for system validation.
|
|
129
|
+
- Diagnostic UI dashboard.
|
|
130
|
+
|
|
131
|
+
### Changed
|
|
132
|
+
- Refactored entire architecture from event-sourcing to deterministic Git projections.
|
|
133
|
+
- Consolidated storage into unified SQLite schema with Recursive CTE support.
|
|
134
|
+
- Upgraded documentation to production infrastructure standards.
|
|
135
|
+
|
|
136
|
+
### Removed
|
|
137
|
+
- Legacy "cognitive OS" and "graph operating system" abstractions.
|
|
138
|
+
- Speculative async priority queues and replay engines.
|
|
139
|
+
- Brittle regex-based parsers.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: synap-git
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.1.1
|
|
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
|
|
@@ -27,6 +27,7 @@ Requires-Dist: keyring==25.6.0
|
|
|
27
27
|
Requires-Dist: markdown-it-py==3.0.0
|
|
28
28
|
Requires-Dist: mcp==1.3.0
|
|
29
29
|
Requires-Dist: msgpack==1.1.0
|
|
30
|
+
Requires-Dist: prompt-toolkit<3.0.44
|
|
30
31
|
Requires-Dist: pydantic-settings==2.8.1
|
|
31
32
|
Requires-Dist: pydantic==2.10.6
|
|
32
33
|
Requires-Dist: questionary==2.1.0
|
|
@@ -73,6 +74,16 @@ Synap is **NOT a RAG system.** It is a strict, deterministic background daemon t
|
|
|
73
74
|
|
|
74
75
|
---
|
|
75
76
|
|
|
77
|
+
## ⚡ Performance Architecture (v1.1.0)
|
|
78
|
+
|
|
79
|
+
Synap is built on the **Git-Snapshot Paradigm** to guarantee sub-100ms response times for everyday agent workflows:
|
|
80
|
+
- **Two separate code paths:** First-run indexing uses process-pool parallel parsing (`ProcessPoolExecutor`) to build the initial codebase structure, while subsequent indexing uses a Git delta change detector (`git diff-tree`) to process only changed files. (Reason: Avoids full filesystem scans on every run).
|
|
81
|
+
- **No filesystem scan / file hashing on incremental runs:** Synap uses Git blob OIDs to detect changed files. (Reason: Git already hashes everything, making filesystem reads redundant).
|
|
82
|
+
- **Decoupled non-deterministic LLM pipeline:** Structural parsing (deterministic, fast) completes immediately and enqueues wiki generation (non-deterministic, slow) to a background worker, while lazy caching fallback handles CLI/API wiki requests. (Reason: Never block structural indexing or CLI commands on LLM API response latency).
|
|
83
|
+
- **SQLite optimizations:** SQLite writes are grouped into a single transaction per batch (Reason: Avoids disk sync overhead per row). SQLite FTS5 index matches symbols in sub-milliseconds (Reason: Eliminates slow wildcard `LIKE` table scans). Pre-computed dot-separated `module_key` columns allow O(1) module resolution (Reason: Prevents suffix-matching scans).
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
76
87
|
## 🏗️ High-Level Architecture (HLD)
|
|
77
88
|
|
|
78
89
|
Synap bridges the gap between your local file system, Git history, and the LLM via a 3-layer indexing strategy.
|
|
@@ -227,6 +238,7 @@ Synap uses a powerful, strict CLI interface. Every destructive action prompts fo
|
|
|
227
238
|
- `synap memory status .` : View counts of approved, pending, and expired lessons.
|
|
228
239
|
- `synap memory prune .` : Prune expired rules and cleanup memory.
|
|
229
240
|
- `synap memory verify .` : Detect dangling file references in active memory.
|
|
241
|
+
- `synap lessons review .` : Interactively review and Approve/Edit/Reject pending lessons.
|
|
230
242
|
- `synap lessons approve <id> .` : Approve a pending revert lesson to activate it.
|
|
231
243
|
- `synap lessons reject <id> .` : Reject and discard a pending lesson.
|
|
232
244
|
- `synap checkpoint create . --doing "..."` : Save the current context state.
|
|
@@ -236,8 +248,8 @@ Synap uses a powerful, strict CLI interface. Every destructive action prompts fo
|
|
|
236
248
|
### Developer Tools
|
|
237
249
|
- `synap wiki list .` : List all generated wiki documentation files.
|
|
238
250
|
- `synap wiki show <filepath> .` : Render a specific wiki markdown page to the console.
|
|
239
|
-
- `synap
|
|
240
|
-
- `synap
|
|
251
|
+
- `synap usage show .` : Display detailed aggregated LLM token usage.
|
|
252
|
+
- `synap usage clear .` : Purge all LLM call history.
|
|
241
253
|
- `synap doctor .` : Validate SQLite integrity, Tree-sitter, tokenizers, LLM providers, and daemon heartbeat.
|
|
242
254
|
- `synap mcp verify .` : Verify MCP protocol, tool schemas, and contract stability.
|
|
243
255
|
|
|
@@ -251,3 +263,4 @@ Synap is built on the philosophy that AI tools must be transparent and controlla
|
|
|
251
263
|
- Ensure all states are stored exclusively in `synap.db` or `.synap/wiki/`.
|
|
252
264
|
|
|
253
265
|
License: [Apache 2.0](LICENSE.md)
|
|
266
|
+
](LICENSE.md)
|
|
@@ -24,6 +24,16 @@ Synap is **NOT a RAG system.** It is a strict, deterministic background daemon t
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
+
## ⚡ Performance Architecture (v1.1.0)
|
|
28
|
+
|
|
29
|
+
Synap is built on the **Git-Snapshot Paradigm** to guarantee sub-100ms response times for everyday agent workflows:
|
|
30
|
+
- **Two separate code paths:** First-run indexing uses process-pool parallel parsing (`ProcessPoolExecutor`) to build the initial codebase structure, while subsequent indexing uses a Git delta change detector (`git diff-tree`) to process only changed files. (Reason: Avoids full filesystem scans on every run).
|
|
31
|
+
- **No filesystem scan / file hashing on incremental runs:** Synap uses Git blob OIDs to detect changed files. (Reason: Git already hashes everything, making filesystem reads redundant).
|
|
32
|
+
- **Decoupled non-deterministic LLM pipeline:** Structural parsing (deterministic, fast) completes immediately and enqueues wiki generation (non-deterministic, slow) to a background worker, while lazy caching fallback handles CLI/API wiki requests. (Reason: Never block structural indexing or CLI commands on LLM API response latency).
|
|
33
|
+
- **SQLite optimizations:** SQLite writes are grouped into a single transaction per batch (Reason: Avoids disk sync overhead per row). SQLite FTS5 index matches symbols in sub-milliseconds (Reason: Eliminates slow wildcard `LIKE` table scans). Pre-computed dot-separated `module_key` columns allow O(1) module resolution (Reason: Prevents suffix-matching scans).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
27
37
|
## 🏗️ High-Level Architecture (HLD)
|
|
28
38
|
|
|
29
39
|
Synap bridges the gap between your local file system, Git history, and the LLM via a 3-layer indexing strategy.
|
|
@@ -178,6 +188,7 @@ Synap uses a powerful, strict CLI interface. Every destructive action prompts fo
|
|
|
178
188
|
- `synap memory status .` : View counts of approved, pending, and expired lessons.
|
|
179
189
|
- `synap memory prune .` : Prune expired rules and cleanup memory.
|
|
180
190
|
- `synap memory verify .` : Detect dangling file references in active memory.
|
|
191
|
+
- `synap lessons review .` : Interactively review and Approve/Edit/Reject pending lessons.
|
|
181
192
|
- `synap lessons approve <id> .` : Approve a pending revert lesson to activate it.
|
|
182
193
|
- `synap lessons reject <id> .` : Reject and discard a pending lesson.
|
|
183
194
|
- `synap checkpoint create . --doing "..."` : Save the current context state.
|
|
@@ -187,8 +198,8 @@ Synap uses a powerful, strict CLI interface. Every destructive action prompts fo
|
|
|
187
198
|
### Developer Tools
|
|
188
199
|
- `synap wiki list .` : List all generated wiki documentation files.
|
|
189
200
|
- `synap wiki show <filepath> .` : Render a specific wiki markdown page to the console.
|
|
190
|
-
- `synap
|
|
191
|
-
- `synap
|
|
201
|
+
- `synap usage show .` : Display detailed aggregated LLM token usage.
|
|
202
|
+
- `synap usage clear .` : Purge all LLM call history.
|
|
192
203
|
- `synap doctor .` : Validate SQLite integrity, Tree-sitter, tokenizers, LLM providers, and daemon heartbeat.
|
|
193
204
|
- `synap mcp verify .` : Verify MCP protocol, tool schemas, and contract stability.
|
|
194
205
|
|
|
@@ -202,3 +213,4 @@ Synap is built on the philosophy that AI tools must be transparent and controlla
|
|
|
202
213
|
- Ensure all states are stored exclusively in `synap.db` or `.synap/wiki/`.
|
|
203
214
|
|
|
204
215
|
License: [Apache 2.0](LICENSE.md)
|
|
216
|
+
](LICENSE.md)
|
|
@@ -10,6 +10,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
|
|
10
10
|
from fastapi.responses import HTMLResponse, StreamingResponse
|
|
11
11
|
from fastapi.staticfiles import StaticFiles
|
|
12
12
|
|
|
13
|
+
from synap_git import __version__
|
|
13
14
|
from synap_git.indexer.engine import SynapRuntime
|
|
14
15
|
|
|
15
16
|
|
|
@@ -17,7 +18,7 @@ def create_app(runtime: SynapRuntime) -> FastAPI:
|
|
|
17
18
|
app = FastAPI(
|
|
18
19
|
title="Synap Context Diagnostics",
|
|
19
20
|
description="Deterministic structural context infrastructure for AI coding agents.",
|
|
20
|
-
version=
|
|
21
|
+
version=__version__,
|
|
21
22
|
)
|
|
22
23
|
|
|
23
24
|
app.add_middleware(
|
|
@@ -36,17 +37,20 @@ def create_app(runtime: SynapRuntime) -> FastAPI:
|
|
|
36
37
|
@app.get("/", response_class=HTMLResponse)
|
|
37
38
|
async def get_index() -> str:
|
|
38
39
|
index_file = static_dir / "index.html"
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
exists = await asyncio.to_thread(index_file.exists)
|
|
41
|
+
if exists:
|
|
42
|
+
return await asyncio.to_thread(index_file.read_text, encoding="utf-8")
|
|
41
43
|
return "<h1>Synap Diagnostic UI is ready.</h1>"
|
|
42
44
|
|
|
43
45
|
@app.get("/api/v1/status")
|
|
44
46
|
async def get_status() -> dict[str, Any]:
|
|
45
47
|
try:
|
|
46
|
-
status = runtime.status
|
|
48
|
+
status = await asyncio.to_thread(runtime.status)
|
|
47
49
|
from synap_git.cli.main import _read_daemon_heartbeat
|
|
48
50
|
|
|
49
|
-
daemon_info =
|
|
51
|
+
daemon_info = await asyncio.to_thread(
|
|
52
|
+
_read_daemon_heartbeat, Path(status.repository_path)
|
|
53
|
+
)
|
|
50
54
|
return {
|
|
51
55
|
"repository_path": status.repository_path,
|
|
52
56
|
"branch": status.branch,
|
|
@@ -63,7 +67,9 @@ def create_app(runtime: SynapRuntime) -> FastAPI:
|
|
|
63
67
|
@app.get("/api/v1/trace/latest")
|
|
64
68
|
async def get_latest_trace() -> dict[str, Any]:
|
|
65
69
|
try:
|
|
66
|
-
|
|
70
|
+
if runtime.trace_store:
|
|
71
|
+
return await asyncio.to_thread(runtime.trace_store.get_latest)
|
|
72
|
+
return {}
|
|
67
73
|
except Exception as exc:
|
|
68
74
|
raise HTTPException(status_code=500, detail=str(exc))
|
|
69
75
|
|
|
@@ -73,7 +79,7 @@ def create_app(runtime: SynapRuntime) -> FastAPI:
|
|
|
73
79
|
|
|
74
80
|
async def event_generator() -> AsyncGenerator[str, None]:
|
|
75
81
|
while True:
|
|
76
|
-
status = runtime.status
|
|
82
|
+
status = await asyncio.to_thread(runtime.status)
|
|
77
83
|
yield f"data: {json.dumps(status.__dict__)}\n\n"
|
|
78
84
|
await asyncio.sleep(2)
|
|
79
85
|
|
|
@@ -81,31 +87,56 @@ def create_app(runtime: SynapRuntime) -> FastAPI:
|
|
|
81
87
|
|
|
82
88
|
@app.get("/wiki/{filepath:path}")
|
|
83
89
|
async def get_wiki_page(filepath: str) -> dict[str, Any]:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
base_dir = runtime.wiki.wiki_dir.resolve()
|
|
91
|
+
try:
|
|
92
|
+
target_path = (base_dir / filepath).resolve()
|
|
93
|
+
except Exception:
|
|
94
|
+
raise HTTPException(status_code=400, detail="Invalid path")
|
|
95
|
+
|
|
96
|
+
if not target_path.is_relative_to(base_dir):
|
|
97
|
+
raise HTTPException(status_code=403, detail="Path traversal detected")
|
|
98
|
+
|
|
99
|
+
target = filepath
|
|
100
|
+
if target.endswith(".md"):
|
|
101
|
+
target = target[:-3]
|
|
102
|
+
try:
|
|
103
|
+
await asyncio.to_thread(runtime.wiki.ensure_wiki_page, target)
|
|
104
|
+
except Exception:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
wiki_path = runtime.wiki.wiki_dir / f"{target}.md"
|
|
108
|
+
exists = await asyncio.to_thread(wiki_path.exists)
|
|
109
|
+
if exists:
|
|
110
|
+
content = await asyncio.to_thread(wiki_path.read_text, encoding="utf-8")
|
|
111
|
+
return {"status": "ok", "content": content}
|
|
87
112
|
return {"status": "error", "message": "Wiki not found"}
|
|
88
113
|
|
|
89
114
|
@app.get("/api/v1/memory")
|
|
90
115
|
async def get_memory_page() -> dict[str, Any]:
|
|
91
|
-
approved = runtime.store.get_lessons
|
|
92
|
-
pending = runtime.store.get_lessons
|
|
116
|
+
approved = await asyncio.to_thread(runtime.store.get_lessons, "approved")
|
|
117
|
+
pending = await asyncio.to_thread(runtime.store.get_lessons, "pending")
|
|
93
118
|
return {"status": "ok", "approved": approved, "pending": pending}
|
|
94
119
|
|
|
95
|
-
|
|
96
|
-
async def get_cost_page() -> dict[str, Any]:
|
|
120
|
+
def _fetch_calls() -> list[dict[str, Any]]:
|
|
97
121
|
with runtime.store.connect() as conn:
|
|
98
122
|
rows = conn.execute("SELECT * FROM llm_calls ORDER BY created_at DESC").fetchall()
|
|
99
|
-
|
|
123
|
+
return [dict(r) for r in rows]
|
|
124
|
+
|
|
125
|
+
@app.get("/api/v1/usage")
|
|
126
|
+
async def get_usage_page() -> dict[str, Any]:
|
|
127
|
+
calls = await asyncio.to_thread(_fetch_calls)
|
|
100
128
|
return {"status": "ok", "calls": calls}
|
|
101
129
|
|
|
102
|
-
|
|
103
|
-
async def get_checkpoints_page() -> dict[str, Any]:
|
|
130
|
+
def _fetch_checkpoints() -> list[dict[str, Any]]:
|
|
104
131
|
with runtime.store.connect() as conn:
|
|
105
132
|
rows = conn.execute(
|
|
106
133
|
"SELECT * FROM checkpoints ORDER BY created_at DESC LIMIT 20"
|
|
107
134
|
).fetchall()
|
|
108
|
-
|
|
135
|
+
return [dict(r) for r in rows]
|
|
136
|
+
|
|
137
|
+
@app.get("/api/v1/checkpoints")
|
|
138
|
+
async def get_checkpoints_page() -> dict[str, Any]:
|
|
139
|
+
cps = await asyncio.to_thread(_fetch_checkpoints)
|
|
109
140
|
return {"status": "ok", "checkpoints": cps}
|
|
110
141
|
|
|
111
142
|
return app
|
|
@@ -202,15 +202,11 @@
|
|
|
202
202
|
</div>
|
|
203
203
|
</div>
|
|
204
204
|
|
|
205
|
-
<!-- LLM
|
|
205
|
+
<!-- LLM Usage -->
|
|
206
206
|
<div class="card" style="grid-column: span 2;">
|
|
207
|
-
<h2>LLM Call
|
|
207
|
+
<h2>LLM Call Usage</h2>
|
|
208
208
|
<div style="display: grid; grid-template-columns: 1fr 2fr; gap: 1.5rem;">
|
|
209
209
|
<div class="stats-grid" style="grid-template-columns: 1fr; gap: 0.75rem;">
|
|
210
|
-
<div class="stat-box">
|
|
211
|
-
<div class="stat-value" id="cost-total-usd">$0.000000</div>
|
|
212
|
-
<div class="stat-label">Total Cost (USD)</div>
|
|
213
|
-
</div>
|
|
214
210
|
<div class="stat-box">
|
|
215
211
|
<div class="stat-value" id="cost-total-calls">0</div>
|
|
216
212
|
<div class="stat-label">Total LLM Calls</div>
|
|
@@ -231,11 +227,10 @@
|
|
|
231
227
|
<th>Model</th>
|
|
232
228
|
<th>Purpose</th>
|
|
233
229
|
<th>Tokens</th>
|
|
234
|
-
<th>Cost</th>
|
|
235
230
|
</tr>
|
|
236
231
|
</thead>
|
|
237
232
|
<tbody id="cost-calls-body">
|
|
238
|
-
<tr><td colspan="
|
|
233
|
+
<tr><td colspan="5" class="empty-state">No calls recorded.</td></tr>
|
|
239
234
|
</tbody>
|
|
240
235
|
</table>
|
|
241
236
|
</div>
|
|
@@ -443,44 +438,59 @@
|
|
|
443
438
|
pendingBody.innerHTML = `<tr><td colspan="3" class="empty-state">No pending lessons needing approval.</td></tr>`;
|
|
444
439
|
}
|
|
445
440
|
|
|
446
|
-
// Fetch
|
|
447
|
-
const
|
|
448
|
-
const
|
|
441
|
+
// Fetch Usage
|
|
442
|
+
const usageRes = await fetch('/api/v1/usage');
|
|
443
|
+
const usageData = await usageRes.json();
|
|
449
444
|
const costBody = document.getElementById('cost-calls-body');
|
|
450
445
|
|
|
451
|
-
let totalCost = 0;
|
|
452
446
|
let totalCalls = 0;
|
|
453
447
|
let totalTokens = 0;
|
|
454
448
|
|
|
455
|
-
if (
|
|
456
|
-
totalCalls =
|
|
457
|
-
|
|
458
|
-
totalCost += c.cost_usd;
|
|
449
|
+
if (usageData.calls && usageData.calls.length > 0) {
|
|
450
|
+
totalCalls = usageData.calls.length;
|
|
451
|
+
usageData.calls.slice(0, 15).map(c => {
|
|
459
452
|
const tokens = c.input_tokens + c.output_tokens;
|
|
460
453
|
totalTokens += tokens;
|
|
461
454
|
|
|
462
455
|
const timeStr = new Date(c.created_at * 1000).toLocaleTimeString();
|
|
463
|
-
|
|
456
|
+
const row = `
|
|
464
457
|
<tr class="row-hover">
|
|
465
458
|
<td>${timeStr}</td>
|
|
466
459
|
<td>${c.provider}</td>
|
|
467
460
|
<td><code>${c.model}</code></td>
|
|
468
461
|
<td>${c.purpose}</td>
|
|
469
462
|
<td>${tokens.toLocaleString()}</td>
|
|
470
|
-
<td style="color:var(--success); font-family:var(--font-mono); font-size:11px;">$${c.cost_usd.toFixed(6)}</td>
|
|
471
463
|
</tr>
|
|
472
464
|
`;
|
|
473
|
-
|
|
465
|
+
costBody.insertAdjacentHTML('beforeend', row);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// Clear the loading message if it exists
|
|
469
|
+
if (usageData.calls.length > 0 && costBody.querySelector('.empty-state')) {
|
|
470
|
+
costBody.innerHTML = '';
|
|
471
|
+
// Re-render the 15 calls because we just cleared it
|
|
472
|
+
costBody.innerHTML = usageData.calls.slice(0, 15).map(c => {
|
|
473
|
+
const tokens = c.input_tokens + c.output_tokens;
|
|
474
|
+
const timeStr = new Date(c.created_at * 1000).toLocaleTimeString();
|
|
475
|
+
return `
|
|
476
|
+
<tr class="row-hover">
|
|
477
|
+
<td>${timeStr}</td>
|
|
478
|
+
<td>${c.provider}</td>
|
|
479
|
+
<td><code>${c.model}</code></td>
|
|
480
|
+
<td>${c.purpose}</td>
|
|
481
|
+
<td>${tokens.toLocaleString()}</td>
|
|
482
|
+
</tr>
|
|
483
|
+
`;
|
|
484
|
+
}).join('');
|
|
485
|
+
}
|
|
474
486
|
|
|
475
|
-
|
|
476
|
-
totalCost += c.cost_usd;
|
|
487
|
+
usageData.calls.slice(15).forEach(c => {
|
|
477
488
|
totalTokens += (c.input_tokens + c.output_tokens);
|
|
478
489
|
});
|
|
479
490
|
} else {
|
|
480
|
-
costBody.innerHTML = `<tr><td colspan="
|
|
491
|
+
costBody.innerHTML = `<tr><td colspan="5" class="empty-state">No LLM calls recorded yet.</td></tr>`;
|
|
481
492
|
}
|
|
482
493
|
|
|
483
|
-
document.getElementById('cost-total-usd').textContent = `$${totalCost.toFixed(6)}`;
|
|
484
494
|
document.getElementById('cost-total-calls').textContent = totalCalls.toLocaleString();
|
|
485
495
|
document.getElementById('cost-total-tokens').textContent = totalTokens.toLocaleString();
|
|
486
496
|
|