Selective Sync

You decide what flows. Directories, files, specific functions. The default is "the code I work in, nothing else."

Selective sync lets you shape the set of functions Live Sync watches, pushes, and pulls. Include globs narrow the scan to the modules you care about. Exclude globs keep generated code, tests, and vendored third-party out of the stream. Per-function opt-outs let you pin individual functions as local-only — useful for experiments, personal debug helpers, and anything you do not want a teammate to see land on their copy yet. The whole thing is expressed in a single [live.selective] block in .aura/live.toml, with sensible defaults that cover most repos without configuration.

Overview

Not every function in a repo is worth syncing. Generated protobuf clients churn on every build and produce meaningless AST changes. Snapshot test fixtures change in bulk but do not matter for cross-peer awareness. Vendored dependencies should never leave your disk. And sometimes you have a local tweak — a print statement, a feature flag, a debug shim — that you explicitly do not want teammates to inherit.

Selective sync answers all of these. The mental model: Live Sync's scan walks the working tree, applies an include filter, applies an exclude filter, then applies a per-function opt-out filter. Anything that survives all three is eligible for push and for conflict checking on pull.

    working tree


    [include globs]   ──────── only files matching any include pattern


    [exclude globs]   ──────── drop files matching any exclude pattern


    [per-fn opt-out]  ──────── drop functions tagged @aura-local


    sync candidate set

How It Works

The filter is evaluated on every scan tick. Globs are standard double-star globs (**/*.rs), matched with forward slashes on all platforms. Order does not matter — include and exclude are sets, not sequences.

A file is in the candidate set if:

  1. It matches at least one include pattern, AND
  2. It matches no exclude pattern.

A function is in the candidate set if:

  1. Its file is in the candidate set, AND
  2. It is not tagged with a per-function opt-out marker.

Config

The config file is .aura/live.toml. Live Sync re-reads it whenever it changes.

[live.selective]
# Default: everything source-like. Override to narrow.
include = [
  "src/**/*.rs",
  "src/**/*.ts",
  "src/**/*.py",
  "crates/**/*.rs",
]

# Default: the obvious noise. Extend to taste.
exclude = [
  "**/target/**",
  "**/node_modules/**",
  "**/.next/**",
  "**/dist/**",
  "**/build/**",
  "**/*.gen.rs",
  "**/*.pb.rs",
  "**/*_generated.ts",
  "**/vendored/**",
  "**/fixtures/**",
  "**/*.snap",
]

# Per-function opt-out marker.
# Functions with this marker in a leading doc/attribute are not pushed.
local_only_marker = "@aura-local"

Using include to narrow on huge repos

On a monorepo with ten services and you are only working on billing, include just that module:

[live.selective]
include = ["services/billing/**/*.rs"]

You will still receive pushes from other services if they impact you (impact alerts flag cross-module changes), but your scan cost collapses to the billing subtree. This is the single most effective perf lever on large repos — see bandwidth and perf.

Stacking includes for multi-language projects

Include is a union. List every language you care about:

include = [
  "backend/**/*.rs",
  "frontend/**/*.ts",
  "frontend/**/*.tsx",
  "shared/**/*.proto",
]

Excluding tests

Opinions differ. The default includes tests because they often encode intent and you want teammates to see your test changes in real time. If you disagree:

[live.selective]
exclude = ["**/tests/**", "**/*_test.rs", "**/*.spec.ts"]

Per-function opt-out

For finer control than file globs, tag individual functions as local-only. The marker is a string the parser looks for in the function's leading doc comment or attribute. Default: @aura-local.

Rust

/// Personal debug helper. @aura-local
fn dump_state_to_file() {
    std::fs::write("/tmp/state.json", serde_json::to_string(&STATE).unwrap()).unwrap();
}

TypeScript

/** Local experiment. @aura-local */
function probeTimings() {
  console.time("work");
  // ...
}

Python

def _debug_hook():
    """@aura-local — trace only, never ship."""
    import ipdb; ipdb.set_trace()

Marked functions are filtered out of outbound pushes. They are not filtered out of inbound — if a teammate pushed one, you would receive it. But by construction, if you both mark it, neither pushes.

Listing local-only functions

aura live selective local-only
  local-only functions (3):
    debug::dump_state_to_file         src/debug.rs:41
    experiments::probe_timings        src/experiments.ts:14
    tests::_debug_hook                tests/helpers.py:8

Flipping a function to shared

Remove the marker and save. Next tick, it goes out.

Precedence and conflicts

If a file matches both include and exclude, exclude wins. This is intentional — exclude is usually the "I am sure" rule ("never this directory"), while include is often looser ("anything that looks like source").

If a per-function opt-out is on a function in an included file, the function is skipped but the file is still scanned. Other functions in the same file push normally.

Ignoring a specific AST kind

Beyond files and named functions, you can exclude whole AST kinds — e.g. never sync trait impl blocks, or never sync closures. This is rarely needed but it exists:

[live.selective.ast]
exclude_kinds = ["closure_expression", "macro_invocation"]

Gotcha: Excluding an AST kind excludes it everywhere, including in files you explicitly include. Use sparingly and document why.

Verifying what will be synced

Before enabling Live Sync on a fresh repo, inspect the candidate set:

aura live selective show
  include patterns:    4
  exclude patterns:   11
  files in candidate:  1,247
  functions in candidate:  14,902
  filtered by exclude: 3,421
  filtered by marker:     19

  sample included:
    src/billing/compute_tax.rs
    src/billing/format_invoice.rs
    ...

  sample excluded:
    target/debug/build/...
    src/proto/billing.pb.rs  [matches **/*.pb.rs]

A quick show after every config edit is cheap insurance against "why is my debug function on Alice's laptop."

Interaction with Live Sync privacy

Selective sync decides what functions participate. Privacy decides what parts of a function are masked inside the participating set. Secrets are scrubbed from function bodies regardless of include/exclude — see Live Sync privacy for details on masking and the private-by-default flag.

Troubleshooting

A file I expect to sync is not syncing. Run aura live selective why path/to/file.rs. Aura prints which rule filtered it (include miss, exclude hit, or not dirty).

A file I do not want to sync is leaking. Same command — confirms the file is currently in the candidate set, then you know which exclude to add.

Per-function marker not respected. The marker must be in the leading comment or attribute of the function. Markers in the body are ignored (they are inside the AST node, not attached to it). Also check you spelled it exactly — no trailing period.

Config edit not picked up. Aura watches .aura/live.toml for changes. If the watch missed it (rare), aura live reload forces a reread.

Recipes

A few patterns that come up repeatedly.

"Work on one service in a monorepo"

[live.selective]
include = ["services/billing/**/*.{rs,ts}"]
exclude = ["**/target/**", "**/node_modules/**"]

Scan cost drops to the service subtree; you still receive cross-service impact alerts for functions you call outside the include set.

"Hide my personal debug module"

[live.selective]
exclude = ["src/debug_personal/**"]

The entire directory is invisible to sync. Even listing the files in .gitignore separately is still a good idea if you do not want them in Git either.

"Share only the public API while building"

[live.selective]
default_private = true
include = ["src/api/**"]

Every function outside src/api/** is opt-out by default. Inside, every function syncs. Combine with @aura-public marker for fine-grained overrides.

"Polyglot repo, language-scoped includes"

[live.selective]
include = [
  "backend/**/*.rs",
  "cli/**/*.rs",
  "web/**/*.{ts,tsx}",
  "shared/**/*.proto",
]

"Exclude generated noise"

[live.selective]
exclude = [
  "**/*.pb.rs",
  "**/*.pb.go",
  "**/*_generated.ts",
  "**/*.sql.go",
  "**/migrations/**",
]

Interaction with Git

Selective sync is independent of .gitignore. A file can be gitignored and still sync — useful for shared dev helpers that should not hit main but that teammates want to see in real time. Conversely, a file can be in Git and excluded from sync. The two filters are separate.

That said, most teams keep them aligned. A common pattern:

[live.selective]
respect_gitignore = true

With respect_gitignore = true, Aura treats every gitignored path as implicitly excluded from sync. Explicit excludes still stack on top.

Defaults we ship

Aura's first-run defaults try to be reasonable without being presumptuous. Here is what aura live init writes on a new repo:

[live.selective]
respect_gitignore = true
include = ["src/**/*", "crates/**/*", "app/**/*", "services/**/*"]
exclude = [
  "**/target/**", "**/node_modules/**", "**/dist/**", "**/build/**",
  "**/.next/**", "**/.turbo/**", "**/coverage/**",
  "**/*.gen.rs", "**/*.pb.rs", "**/*_generated.*",
  "**/vendor/**", "**/vendored/**", "**/.venv/**",
]
local_only_marker = "@aura-local"

If your repo uses non-standard layouts (e.g. Python's src-less convention, or a custom tooling directory), edit include on first enable.

Migration: broadening or narrowing later

Changing include/exclude after Live Sync is already running is safe. On the next scan tick, newly-included files are parsed and their current hashes are pushed as baseline; newly-excluded files are simply stopped — peers retain their last received copy.

One subtle note: narrowing include does not retract previously synced functions from peers' copies. Those functions are still on their disk. You can force a "sync reset" if you want a clean slate:

aura live sync reset --confirm

This rewinds your WAL and replays a baseline snapshot under the new filter. Most teams do not need this — natural edits will overwrite old bodies soon enough.

See Also