Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,894 changes: 1,798 additions & 96 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
[package]
name = "synapse"
version = "0.1.5"
version = "0.2.0"
edition = "2024"

[[bin]]
name = "synapse"
path = "src/main.rs"

[features]
default = ["ladybug"]
default = ["ladybug", "share"]
ladybug = ["dep:lbug"]
# Share the indexed graph via an OCI registry (`synapse push` / `pull`).
# Pulls in an async (tokio) + TLS stack, so it's a toggle for lean builds.
share = ["dep:oci-client", "dep:tokio", "dep:docker_credential"]

[dependencies]
lbug = { version = "0.17.0", optional = true }
oci-client = { version = "0.17", default-features = false, features = [
"rustls-tls",
], optional = true }
tokio = { version = "1", features = ["rt"], optional = true }
docker_credential = { version = "1.3", optional = true }
anyhow = "1.0.102"
blake3 = "1.8.5"
chrono = { version = "0.4.44", features = ["serde"] }
Expand Down
11 changes: 11 additions & 0 deletions LLM.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ synapse explore # Ladybug Explorer UI on http://localhost:8000 (read-
synapse explore --print # just print the docker command (no Docker needed)
```

## Share the graph via an OCI registry (optional)
```bash
synapse pull # fetch the team's shared graph (tag "latest") instead of indexing
synapse pull --tag <sha> # fetch the graph for a specific commit
synapse push --yes # publish (restricted; CI skips the confirm prompt)
```
- Configured in the `[share]` section of synapse.toml (`registry`, `repository`, `push_enabled`).
- Auth is auto-discovered from `docker login` (no tokens in config); anonymous for public registries; env `SYNAPSE_REGISTRY_USER`/`PASS`/`TOKEN` override for headless.
- `pull` verifies integrity and WARNS if the graph's commit != local HEAD (it may be stale → `synapse index`). `status` shows the pulled `Origin:` commit + `originStale` in `--json`.
- Push is gated: needs `push_enabled=true` + clean tree (or `--allow-dirty`) + confirm (or `--yes`). A fresh clone can't push by accident.

## Gotchas
- Re-run `synapse index` after code changes; stale results otherwise (`status` shows `staleFiles`).
- `pack` needs exactly one of `--changed/--path/--symbol/--query`.
Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ synapse pack --symbol MyHandler --output context.md
synapse pack --changed --budget 40000 --output context.md --explain
synapse pack --symbol MyHandler --format json # structured output for tools
synapse explore # visualize the graph in a browser
synapse pull # fetch a shared graph from an OCI registry
synapse push --yes # publish the graph (restricted; see below)
synapse clean --all
```

Expand Down Expand Up @@ -85,8 +87,32 @@ try `--tag dev` or a matching image tag.
| `packages` | List indexed package dependencies (or `--projects`), with versions resolved via .NET Central Package Management (`--ecosystem`, `--json`). Use `--importers <pkg>` for impact analysis: the files that import a package. |
| `explore` | Launch [Ladybug Explorer](https://docs.ladybugdb.com/visualization/) (Docker) to visualize the indexed graph in a browser (`--port`, `--read-write`, `--detach`, `--in-memory`, `--tag`, `--print`). Mounts the index read-only by default; `--print` shows the `docker run` command without executing it. |
| `pack` | Emit a context pack (`--changed`/`--path`/`--symbol`/`--query`, `--budget`, `--include-tests`, `--include-config`, `--include-diff`, `--dry-run`, `--explain`, `--output`). `--format markdown` (default) or `--format json` for programmatic callers. Writes to stdout unless `--output` is given; diagnostics go to stderr. |
| `pull` | Fetch a shared graph from the configured OCI registry (`--tag`, `--registry`, `--repository`). Verifies integrity (blake3), writes the graph atomically, and warns if its indexed commit differs from local `HEAD`. |
| `push` | Publish the indexed graph to the configured OCI registry (`--tag`, `--registry`, `--repository`, `--yes`, `--allow-dirty`). Restricted — see [Sharing the graph](#sharing-the-graph-oci-registry). |
| `clean` | Remove `--cache` / `--index` / `--packs` / `--all`. |

## Sharing the graph (OCI registry)

The indexed graph (`synapse.lbug`) is a multi-MB binary — too heavy for git. Instead, share it with your team via any OCI registry (GHCR, ECR, ACR, Harbor, …): CI (or a maintainer) `push`es the graph, teammates `pull` it instead of re-indexing.

```bash
# One-time: point the [share] section at your registry (in .synapse/synapse.toml)
# [share]
# registry = "ghcr.io"
# repository = "myorg/myrepo-synapse-graph"
# push_enabled = true # required to allow `push` at all (default false)

synapse pull # fetch the current shared graph (tag "latest")
synapse pull --tag a1b2c3d # fetch the graph for a specific commit
synapse push --yes # publish (CI-friendly; skips the confirm prompt)
```

- **Credentials are auto-discovered** from your existing `docker login` (`~/.docker/config.json` + OS credential helpers). You do **not** put a token in synapse's config. Public registries pull anonymously with no setup. For headless setups without a docker config, set `SYNAPSE_REGISTRY_USER` + `SYNAPSE_REGISTRY_PASS` (or `SYNAPSE_REGISTRY_TOKEN`).
- **Push is heavily guarded** (a fresh clone / CI can never push by accident): it requires `push_enabled = true` in config **and** a clean working tree (or `--allow-dirty`) **and** interactive type-to-confirm (or `--yes`). The graph is tagged by commit (a per-commit tag plus the moving `latest`), so a tag always describes a known state.
- **Staleness is surfaced, never hidden.** `pull` warns loudly when the graph's indexed commit differs from your `HEAD`, and `synapse status` shows the pulled graph's `Origin:` commit on every run. If it's stale, `synapse index` rebuilds locally.
- **Visibility = the registry's visibility.** The graph encodes file paths, symbol names and dependencies — treat a public registry accordingly.
- Sharing is the `share` Cargo feature (on by default); a `--no-default-features` build without it drops the networking/TLS stack and the `push`/`pull` commands.

## Languages

Symbol extraction (via tree-sitter) covers **C#, Rust, Python, Go, JavaScript,
Expand Down
38 changes: 38 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,48 @@ pub enum Command {
Pack(PackArgs),
/// Launch Ladybug Explorer (Docker) to visualize the indexed graph.
Explore(ExploreArgs),
/// Push the indexed graph to the configured OCI registry (restricted).
Push(PushArgs),
/// Pull an indexed graph from the configured OCI registry.
Pull(PullArgs),
/// Remove cached/index/pack data.
Clean(CleanArgs),
}

#[derive(Debug, clap::Args)]
pub struct PushArgs {
/// Override the per-commit tag (defaults to the short HEAD commit SHA).
#[arg(long)]
pub tag: Option<String>,
/// Override the configured registry host for this invocation.
#[arg(long)]
pub registry: Option<String>,
/// Override the configured repository for this invocation.
#[arg(long)]
pub repository: Option<String>,
/// Skip the interactive type-to-confirm prompt (for CI). Does NOT bypass
/// `push_enabled` or the dirty-tree guard.
#[arg(long)]
pub yes: bool,
/// Allow pushing from a working tree with uncommitted changes.
#[arg(long)]
pub allow_dirty: bool,
}

#[derive(Debug, clap::Args)]
pub struct PullArgs {
/// Tag to pull (defaults to the HEAD commit tag if present, else the
/// configured moving tag).
#[arg(long)]
pub tag: Option<String>,
/// Override the configured registry host for this invocation.
#[arg(long)]
pub registry: Option<String>,
/// Override the configured repository for this invocation.
#[arg(long)]
pub repository: Option<String>,
}

#[derive(Debug, clap::Args)]
pub struct InitArgs {
/// Overwrite an existing config.
Expand Down
56 changes: 56 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub struct SynapseConfig {
pub index: IndexConfig,
pub graph: GraphConfig,
pub pack: PackConfig,
#[serde(default)]
pub share: ShareConfig,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
Expand Down Expand Up @@ -80,6 +82,59 @@ pub struct PackConfig {
pub include_selection_reasons: bool,
}

/// Sharing the indexed graph via an OCI registry (`synapse push` / `pull`).
///
/// Push is OFF by default (`push_enabled = false`) — it must be explicitly
/// enabled, and even then requires interactive confirmation and a clean tree.
/// Credentials are never stored here: they are discovered from the existing
/// docker login (`~/.docker/config.json` + credential helpers).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ShareConfig {
/// Registry host[:port], e.g. "ghcr.io". Empty = sharing not configured.
#[serde(default)]
pub registry: String,
/// Repository path within the registry, e.g. "myorg/myrepo-synapse-graph".
/// Empty = sharing not configured.
#[serde(default)]
pub repository: String,
/// Moving tag updated on every push alongside the per-commit tag.
#[serde(default = "default_share_moving_tag")]
pub moving_tag: String,
/// MUST be true for `synapse push` to be permitted at all.
#[serde(default)]
pub push_enabled: bool,
/// Transport: "https" (default) or "http" (plaintext; dev/local only).
#[serde(default = "default_share_protocol")]
pub protocol: String,
/// Credential strategy: "auto" (docker creds -> env -> anonymous),
/// "docker", "env", or "anonymous".
#[serde(default = "default_share_auth")]
pub auth: String,
}

impl Default for ShareConfig {
fn default() -> Self {
ShareConfig {
registry: String::new(),
repository: String::new(),
moving_tag: default_share_moving_tag(),
push_enabled: false,
protocol: default_share_protocol(),
auth: default_share_auth(),
}
}
}

fn default_share_moving_tag() -> String {
"latest".to_string()
}
fn default_share_protocol() -> String {
"https".to_string()
}
fn default_share_auth() -> String {
"auto".to_string()
}

fn default_root() -> String {
".".to_string()
}
Expand Down Expand Up @@ -132,6 +187,7 @@ impl Default for SynapseConfig {
default_format: default_format(),
include_selection_reasons: true,
},
share: ShareConfig::default(),
}
}
}
Expand Down
35 changes: 35 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,39 @@ pub enum SynapseError {
/// The graph backend reported a failure.
#[error("graph backend error: {0}")]
Backend(String),

/// `synapse push` was invoked but `[share].push_enabled` is false.
#[error(
"push is disabled; set `push_enabled = true` in the [share] section of .synapse/synapse.toml to allow it"
)]
PushDisabled,

/// The registry/repository for sharing is not configured.
#[error(
"share target not configured; set `registry` and `repository` in the [share] section (or pass --registry/--repository)"
)]
ShareNotConfigured,

/// Push refused because the working tree has uncommitted changes.
#[error(
"working tree has uncommitted changes ({0} file(s)); commit/stash or pass --allow-dirty (the graph is tagged by commit)"
)]
DirtyTree(usize),

/// Push was not confirmed (interactive prompt declined or non-interactive
/// without `--yes`).
#[error("push not confirmed (interactive confirmation required, or pass --yes)")]
PushNotConfirmed,

/// A registry network/transport call failed.
#[error("registry network error: {0}")]
RegistryNetwork(String),

/// Registry authentication failed.
#[error("registry authentication failed: {0}")]
RegistryAuth(String),

/// A pulled graph failed its blake3 integrity check.
#[error("pulled graph failed integrity check (blake3 mismatch); the artifact may be corrupt")]
IntegrityMismatch,
}
6 changes: 6 additions & 0 deletions src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ pub fn info(root: &Path) -> GitInfo {
}
}

/// The full (40-char) commit SHA of HEAD, or `None` outside a git repo / with no
/// commits. Used as the canonical identity for a shared graph artifact.
pub fn full_commit(root: &Path) -> Option<String> {
run(root, &["rev-parse", "HEAD"]).filter(|s| !s.is_empty())
}

/// True if `root` is inside a git work tree.
pub fn is_git_repo(root: &Path) -> bool {
run(root, &["rev-parse", "--is-inside-work-tree"])
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ pub mod indexer;
pub mod output;
pub mod pack;
pub mod repo;
#[cfg(feature = "share")]
pub mod share;
Loading
Loading