Skip to content

Multi-Repository Shared Database Support & Branch Isolation - #7

Open
dtor wants to merge 37 commits into
masoncl:mainfrom
dtor:multi-repo
Open

Multi-Repository Shared Database Support & Branch Isolation#7
dtor wants to merge 37 commits into
masoncl:mainfrom
dtor:multi-repo

Conversation

@dtor

@dtor dtor commented Jul 23, 2026

Copy link
Copy Markdown

NOTE: You need to sync your main branch from the main repo and then I can rebase as well to only include relevant commits.

Summary & Motivation

When working on large projects like the Linux kernel, developers frequently maintain multiple repository checkouts (e.g. linux-mainline, bpf-next, net-next, stable) or multiple linked Git worktrees (git worktree add ...). Since repositories share a lot of the same objects it is very tempting to use a shared database with them.

Previously, semcode stored indexed_branches without repository identity scoping. When indexing multiple repositories into a single shared central database (SEMCODE_DB=~/.cache/semcode.db), branch names like main, master, or work collided, polluting branch lookups across repositories.

This PR improves support for multiple repositories indexed in the same database:

  • Strict Branch Reference Isolation: Scopes branch records (indexed_branches) per repository via repo_path filtering, preventing branch name collisions between trees.
  • Relocatable Local Databases: Preserves 100% position-independent relocatability for local databases (.semcode.db inside a repository folder).

Architectural Highlights

1. Repository Identity Resolution & Worktree Consolidation

  • git::get_repo_root(): Resolves any repository directory, subfolder, bare repo, or linked worktree to its canonical repository identity using common_dir (.git).
  • Worktree Consolidation: Linked worktrees share the same .git commit graph as their parent repository, transparently sharing the same branch index without creating duplicate worktree records.
  • Home Directory Normalization (git::normalize_repo_path): Replaces absolute home directory prefixes (/home/user, /usr/local/google/home/user) with ~/, ensuring repository identity keys are portable across machines with different home mount paths.

2. Dual Database Locality Modes (determine_db_locality)

  • Local Database Mode (database stored inside repository .git/ or .semcode.db):
    • Uses dynamic SQL filter (repo_path = '{repo_identity}' OR repo_path IS NULL).
    • Guarantees zero record invalidation if the user moves or renames their local repository folder.
  • Shared / Central Database Mode (database stored in ~/.cache/semcode.db or external path):
    • Enforces strict SQL filter repo_path = '{repo_identity}'.
    • Completely isolates branch tips between different repository checkouts.

3. On-Demand Lazy Schema Migration

  • Existing databases created with older 4-column schemas operate seamlessly without forcing a manual migration tool or breaking read queries.
  • IndexedBranchStore::record_branch_indexed() lazily adds the repo_path column and its scalar BTree index on the first write operation.

4. Automatic Garbage Collection (DatabaseManager::garbage_collect)

  • Automatically executed at the end of semcode-index runs.
  • Purges indexed_branches records for repository paths that no longer exist on the local filesystem.
  • Purges un-scoped legacy NULL branch records in standalone database mode.

Documentation & Test Coverage

  • docs/shared-db.md: New comprehensive guide covering shared database architecture, locality detection, worktree handling, and CLI usage.
  • docs/schema.md: Updated indexed_branches table schema specification and indices.
  • Unit Tests: Added 6 new unit tests covering:
    • git::tests::test_normalize_repo_path
    • git::tests::test_determine_db_locality_* (local, external, bare repo, worktree)
    • database::branches::tests::test_shared_db_branch_isolation
    • database::branches::tests::test_local_db_matches_null_and_active
    • database::branches::tests::test_garbage_collect_null_records_in_standalone_db
    • database::branches::tests::test_read_old_indexed_branches_table_does_not_alter_schema

chucklever and others added 30 commits April 3, 2026 10:56
insert_lore_emails() returns Ok(()) unconditionally, even
when individual merge_insert calls fail in the chunked
fallback path.  The pipeline inserter treats Ok(()) as full
success: it records every commit SHA from the batch in
lore_indexed_commits and credits the full batch size to the
inserted counter.

Emails that failed to insert are therefore marked as indexed
and filtered out on all subsequent runs.  Because
reconcile_lore_indexed_commits() runs only during specific
schema migrations, the orphaned SHA entries are never
cleaned up in normal operation.  Over time the set of
processable new commits shrinks to near zero.

Change insert_lore_emails() to return the indices of emails
that could not be stored.  The pipeline inserter now
excludes failed emails when recording indexed commit SHAs,
so they remain eligible for retry on the next run.  The
inserted counter reflects actual insertions.

Fixes: 01b9399 ("semcode: add --lore for email indexing (database schema change)")
Signed-off-by: Chuck Lever <cel@kernel.org>
Compact, Prune, and OptimizeAction::Index each create new
manifest versions that drop FTS index references, destroying
full-text search capability until the next semcode-index
--lore rebuild. Skip the lore table entirely during
optimize_single_table and perform only a checkout_latest
to release stale handles.

Fixes: 01b9399 ("semcode: add --lore for email indexing (database schema change)")
Signed-off-by: Chuck Lever <cel@kernel.org>
Lance 3.0.1's merge_insert resolves columns by position during
its internal join/write phase rather than by name.  The lore
table's on-disk schema has date_timestamp at position 10
(appended by the add_columns migration), but insert_lore_emails
builds its RecordBatch with date_timestamp at position 3.
The resulting column misalignment causes nullable column values
(in_reply_to) to appear in non-nullable slots (subject),
producing a spurious "field subject contained null values"
error on every insert.

table.add() resolves columns by name and is not affected by
column-order differences between the insert batch and the
on-disk schema.

Replace merge_insert with table.add(), and add a
filter_existing_lore_ids() check that queries the message_id
BTree index before inserting to prevent duplicates from
partial-failure retries.  This is also cheaper than
merge_insert, which performs a full read-modify-write cycle
on every call.

Fixes: 391b9d2 ("update deps to fix lance recursion_limit build failure")
Signed-off-by: Chuck Lever <cel@kernel.org>
FTS pattern normalization strips all non-alphanumeric characters,
so purely symbolic regexes like ".*" produce an empty FTS query
string.  An empty FTS query returns zero candidates, and the
regex post-filter never gets a chance to match, causing the
search to silently return no results.

When the normalized FTS pattern is empty, bypass FTS and issue
a plain table scan with date filtering, allowing the regex
post-filter to operate on the full candidate set.  Both
search_lore_emails (single-field path) and query_field_impl
(multi-field intersection path) are affected.

Fixes: 01b9399 ("semcode: add --lore for email indexing (database schema change)")
Signed-off-by: Chuck Lever <cel@kernel.org>
The MCP host process controls the semcode-mcp command line, so there
is no way to pass a -d flag to override the database location. An
environment variable is the only mechanism available for directing
semcode-mcp to a database that lives outside the current working
directory.

This also enables semcode to share the same database (and thus the
same lore archives and source code commits) across multiple copies
of the same source code base, avoiding the storage gigabytes of
data.

Add SEMCODE_DB to the database path resolution order, between the
-d flag and the source-dir / current-directory fallbacks. The
variable receives the same path normalization as the flag: a path
ending in .semcode.db is used as-is, an existing directory gets
.semcode.db appended, and anything else is taken literally.

The change is contained in process_database_path(), so all binaries
that call it (semcode-index, semcode, semcode-mcp) pick up the new
behavior without modification. semcode-lsp resolves its database
path before calling that function, so it receives an explicit
SEMCODE_DB check in its own resolution chain.

Signed-off-by: Chuck Lever <cel@kernel.org>
Commit b66cb3c replaced rev_walk + with_hidden with a first-parent
walk to avoid gix 0.81's slow graph painting. However, the first-parent
walk only stops when it encounters the exact boundary commit ID. When
the range start is a tag (e.g., v7.0-rc6) that was merged rather than
being on the first-parent chain, the walk never encounters it and
traverses the entire repository history.

For the linux kernel with v7.0-rc6..HEAD, this produced 75,454 commits
instead of 397, causing every file to be re-processed across all
commits and inflating the indexing time from ~92 seconds to 3+ hours.

Restore rev_walk + with_hidden which correctly computes the set
difference regardless of graph topology.

Fixes: b66cb3c (fix startup performance regression from gix 0.81 upgrade)
Signed-off-by: Chris Mason <clm@meta.com>
Add tests that create a temporary git repo with a side branch and
merge commit, then verify list_shas_in_range correctly handles:

- Tags on side branches (not on the first-parent chain)
- Range boundaries on the first-parent chain
- No-range mode (all reachable commits)

The tag-on-side-branch test specifically catches the regression from
b66cb3c where a first-parent-only walk would miss the boundary and
traverse the entire history.

Signed-off-by: Chris Mason <clm@meta.com>
tower-lsp is unmaintained (no updates in 3 years). Migrate to the
community fork tower-lsp-server, which brings lsp-types 0.97/ls-types,
native async traits (no more async_trait), and fluent-uri based Uri.

Key changes:
- tower_lsp -> tower_lsp_server, lsp_types -> ls_types
- Drop #[tower_lsp::async_trait] (RPITIT in 0.21+)
- url::Url -> ls_types::Uri with inherent to_file_path/from_file_path
- Prefer workspace_folders over deprecated root_uri
- Add offset_encoding field to InitializeResult (new in ls-types)
- Remove url crate dependency (no longer needed)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Chris Mason <clm@meta.com>
Signed-off-by: Chris Mason <clm@meta.com>
Signed-off-by: Chris Mason <clm@meta.com>
The lore search multi-field intersection had three bugs
that caused queries to return wrong or empty results:

1. Multiple patterns for the same field (e.g., two
from_patterns) were joined into a single string with
spaces and passed as one FTS query + one regex. Both
FTS and regex treated the combined string as an AND
(all terms must match), not OR as documented. Fix by
querying each pattern independently and taking the
set union.

2. The result limit was applied before date filtering.
Since HashSet iteration order is arbitrary, taking
the first N message_ids before checking dates caused
recent emails to be missed when the candidate set
was large.

3. Date filtering used SQL string comparison on
RFC 2822 date strings via only_if(), which produces
lexicographic rather than temporal ordering (e.g.,
"Wed" > "Tue" alphabetically). Fix by selecting the
date column in the FTS query and comparing parsed
DateTime values in the post-filter loop alongside
the regex check.

4. The new date_timestamp column was added to the lore
table schema but existing databases were not migrated.
merge_insert of 11-column batches into a 10-column
table silently failed, and the fallback logic in
insert_lore_emails returned Ok(()) after skipping
individual rows, so commit SHAs were recorded as
indexed while no email data was stored. Add a
migration step that creates the column via
add_columns and reconciles lore_indexed_commits by
purging SHAs whose emails were never persisted.

5. The vector search path (vlore_similar_emails) used
get_column() for date_timestamp, which returns an
error when the column is absent. semcode-mcp runs
the schema migration in a background task, so a
query arriving before migration completes hits
this path on pre-migration databases. Treat the
column as optional and fall back to parsing the
RFC 2822 date string, matching the approach used
in the non-vector query path.
The vector-based lore search (vlore) applied the result
limit before date filtering: intersection_ids was truncated
to `limit` entries before passing them to fetch_emails_by_ids
where date checks occurred. Because intersection order is
arbitrary, recent emails could be excluded when the candidate
set was large, producing short result sets.

Push date filtering into query_lore_fields_intersection,
matching the approach already used in the non-vector lore
search path (connection.rs). Each per-field FTS query now
selects the "date" column and filters candidates by parsed
DateTime comparison in the post-processing loop, so the
intersection set is already date-bounded before the limit
is applied.

Remove the dead date_filter SQL string construction that
was built but never used in any only_if() clause.

Fix clippy warnings: replace map_or(false, ...) with
is_some_and() in both connection.rs and search.rs, and
suppress too_many_arguments on the intersection functions
whose signatures grew with the new date parameters.
Every lore indexing run dropped and rebuilt all five FTS indices
from scratch, even when only a handful of new emails were inserted.
On large archives the rebuild dominated the total indexing time.

LanceDB's native FTS engine (lance >= 0.19.2) supports incremental
updates: newly-inserted rows are served via a brute-force fallback
at query time, then merged into the inverted index structure by
OptimizeAction::Index.  The bug where OptimizeAction::Index
destroyed FTS index references was fixed in January 2025 and
lance 3.0.1 is well past that fix.

Split the indexing path into ensure + optimize:
ensure_lore_fts_indices() creates the five indices only when they
are absent (first run or after --clear), and
optimize_lore_fts_indices() merges unindexed rows into the
existing indices.  The full drop-and-rebuild path remains
available for schema migrations.

Remove the lore-table skip in optimize_single_table() since
OptimizeAction::Index no longer destroys FTS references.
Signed-off-by: Chris Mason <clm@meta.com>
Signed-off-by: Venkatesh Srinivas <venkateshs@chromium.org>
NomicBertModel is missing get_input_embeddings/set_input_embeddings,
which are required by model2vec's distilation pipeline. Patch in the
two methods.

Before:
...
HuggingFace tokenizer defines a pad_token, but the Skeletoken model does not. Setting it to '<pad>'.

Full traceback of the error:
Traceback (most recent call last):
  File "/home/vsrinivas/WORK/semcode/./scripts/nomic2vec.py", line 501, in main
    m2v = distill_from_model(
        model=model,
    ...<3 lines>...
        device=args.device
    )
  File "/home/vsrinivas/semcode-vectors2/lib/python3.13/site-packages/model2vec/distill/distillation.py", line 107, in distill_from_model
    model = reshape_embeddings(model, original_tokenizer_model)
  File "/home/vsrinivas/semcode-vectors2/lib/python3.13/site-packages/skeletoken/external/transformers.py", line 58, in reshape_embeddings
    embedding = model.get_input_embeddings()
  File "/home/vsrinivas/semcode-vectors2/lib/python3.13/site-packages/transformers/modeling_utils.py", line 1036, in get_input_embeddings
    raise NotImplementedError(
        f"`get_input_embeddings` not auto‑handled for {self.__class__.__name__}; please override in the subclass."
    )
NotImplementedError: `get_input_embeddings` not auto‑handled for NomicBertModel; please override in the subclass.

After:
HuggingFace tokenizer defines a pad_token, but the Skeletoken model does not. Setting it to '<pad>'.
Encoding tokens: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 249999/249999 [2:12:08<00:00, 31.53 tokens/s]
✓ Saved Model2Vec static model to /home/vsrinivas/WORK/semcode/nomic_v2_m2v
Fixing tokenizer configuration for semcode compatibility...
  Adding [UNK] token to vocabulary
  Set unk_id to 1 for [UNK] token
  ✓ Updated tokenizer configuration
  ✓ Updated tokenizer_config.json
⚠ Verification warning: Number of tokens (250000) does not match number of vectors (249999). Please provide a token mapping or ensure the number of tokens matches the number of vectors.
  The model was saved successfully but may need additional configuration for some tools

All done. The static model is ready for high‑throughput CPU embedding.

Assisted-by: Claude:gemma-4-26B-A4B
Signed-off-by: Venkatesh Srinivas <venkateshs@chromium.org>
Add SEMCODE_GIT_REPO environment variable support for specifying the git
repository path in semcode-mcp and semcode. This allows the MCP host
process to provide the repository location via the environment when
command line arguments cannot be easily modified.

The priority order is:
1. --git-repo command line argument
2. SEMCODE_GIT_REPO environment variable
3. Current directory (default)

Enable the env feature for clap in Cargo.toml to support the env
attribute in the Args struct.

Add test code to verify that the environment variable is correctly
supported and respects the priority order.

Signed-off-by: Guenter Roeck <linux@roeck-us.net>
"cargo fmt" adjusted a bit of code that was added by commit
37f4b7a ("switch LSP server from tower-lsp 0.20 to
tower-lsp-server 0.23").
Commit 8ac9f79 ("lore: Use incremental FTS index updates
instead of full rebuilds") removed an early-return guard in
optimize_single_table() that previously skipped the lore table
entirely. The guard had been documented as protecting FTS index
references, and that protection was no longer needed once
ensure_lore_fts_indices() + optimize_lore_fts_indices() became
the canonical FTS update path. Removing the guard exposed a
different, previously-dormant issue: compaction of the 290k-row
lore table now runs on every --lore invocation.

lance/index/append.rs:merge_indices() opens every delta index
fragment for a column before merging any of them, and for the
scalar/FTS path indices_merged is hard-coded to 1, so the
num_indices_to_merge option has no effect on how many fragments
are touched per call. On a host with enough memory the cost is
acceptable. On a 6GB system the resident set grows linearly
with the per-column fragment count, bleeds into swap, and the
OOM killer eventually terminates semcode-index. The run then
leaves behind fresh delta fragments that the next run will also
have to walk, so the problem is monotonic.

Two paths now reach the expensive merge_indices walk:

 1. compact_lore_tables() -> optimize_single_table("lore"),
    which runs Compact + Prune + Index (step 3 is the index
    optimize that walks all fragments).

 2. optimize_lore_fts_indices() called directly from the
    --lore pipeline after compact_lore_tables() returns.

Restore the early-return skip in optimize_single_table() for the
lore table so path (1) is a no-op again, and guard
optimize_lore_fts_indices() with a _indices/ fragment-count
threshold so path (2) bails out cleanly when the backlog is
already pathologically large. Query correctness is preserved
in both cases: LanceDB's native FTS engine serves unindexed
rows via a brute-force fallback, so searches still return
correct results while compaction is deferred to a host with
enough memory to complete it.

Fixes: 8ac9f79 ("lore: Use incremental FTS index updates instead of full rebuilds")
After inserting emails, the --lore handler called
optimize_database() which runs compact_and_cleanup() across
every table in the database — functions, types, 16 content
shards, and several metadata tables.  When a database
already contains a code index, those tables carry thousands
of fragments with full function bodies and type definitions.
Compacting them loads hundreds of megabytes of data that the
lore run never modified, and on a 6 GB system the combined
working set triggers the OOM killer.

Add compact_lore_tables() which processes only the lore and
lore_indexed_commits tables, sequentially, and call it from
both --lore code paths instead of optimize_database().  Peak
memory during post-pipeline cleanup is now proportional to
the lore data alone.
Adds the model2vec [distill] extra to scripts/requirements.txt so
nomic2vec.py's distillation imports resolve.

Signed-off-by: Chris Mason <clm@meta.com>
Monkey-patches get_input_embeddings/set_input_embeddings onto
NomicBertModel so model2vec's distill_from_model pipeline runs.

Signed-off-by: Chris Mason <clm@meta.com>
Adds SEMCODE_GIT_REPO env var support to semcode and semcode-mcp via
clap's env attribute, with arg > env > default priority.

Signed-off-by: Chris Mason <clm@meta.com>
The three #[test] fns introduced for SEMCODE_GIT_REPO each mutate the
process-wide env var. cargo runs tests in a binary in parallel by
default, so the three tests interleave their set_var/remove_var calls
and 2 of 3 fail under `cargo test`:

    test tests::test_git_repo_default ... FAILED
    test tests::test_git_repo_env_var ... FAILED
    test tests::test_git_repo_priority ... ok

Combine the three cases into a single test fn so all env-var mutation
happens on one thread, restoring `cargo test` (and the pre-commit /
pre-push hooks that wrap it).

Signed-off-by: Chris Mason <clm@meta.com>
clippy under -D warnings flags `clippy::items-after-test-module` because
the #[cfg(test)] mod tests block introduced for SEMCODE_GIT_REPO sits
mid-file with non-test items below it:

    error: items after a test module
       --> src/bin/query.rs:86:1

Move the tests module to the bottom of the file. No functional change
intended.

Signed-off-by: Chris Mason <clm@meta.com>
`cargo fmt --check` flags this multi-line method-chain call in
SemcodeLspBackend::initialize as malformed; rustfmt collapses it onto a
single line. Apply the formatting so the pre-commit hook's
`cargo fmt --check` passes. No functional change intended.

Signed-off-by: Chris Mason <clm@meta.com>
Limits --lore post-pipeline compaction to the lore tables and adds
fragment-count guards that skip the compaction paths whose peak memory
OOM-kills semcode-index on small hosts.

Signed-off-by: Chris Mason <clm@meta.com>
Refresh Cargo.lock with latest compatible versions of all dependencies.
Build, clippy, and tests all pass after the update.

Signed-off-by: Chris Mason <clm@meta.com>
Signed-off-by: Chris Mason <clm@meta.com>
dtor added 7 commits July 22, 2026 18:41
… config

When unit tests in `src/indexer.rs` instantiate temporary Git
repositories via `std::process::Command::new("git")`, global or system
Git configurations (such as custom ref templates or hooks) can bleed into
test execution and produce unexpected ref structures like `.invalid`.

Set `GIT_CONFIG_GLOBAL=/dev/null` and `GIT_CONFIG_SYSTEM=/dev/null` in the
`git()` test helper command invocation to ensure test execution is fully
isolated from the host environment.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Repository paths referenced in database schemas must be consistent across
operating systems and independent of individual user home directory
mount points (e.g. /home/user vs /usr/local/google/home/user).

Introduce `normalize_repo_path()` in `src/git.rs` to:
- Convert backslashes (`\`) to forward slashes (`/`) for cross-platform
  OS compatibility.
- Trim `$HOME` directory prefixes to `~` when the path resides under the
  user's home directory.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Linked git worktrees created via `git worktree add` share the same
commit history and branch ref namespaces as their parent repository. If
worktrees are treated as separate repository paths in database tables,
branches get redundantly indexed for each worktree.

Introduce `get_repo_root()` in `src/git.rs` to extract
`repo.common_dir()` from `gix::Repository`. This ensures standard
checkouts, bare repositories, and linked worktrees all resolve to their
single underlying common Git directory identity key.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
A database can be operated in two modes: Local (embedded in a repo
directory) or Shared/External (e.g. central ~/.cache/semcode.db). To
select between position-independent NULL repo paths (Local) vs scoped
repository paths (Shared), the connection initialization layer must
classify the target database path.

Introduce `determine_db_locality()` in `src/git.rs` to:
- Collect canonical root directories for standard repos, active linked
  worktrees, and bare repositories.
- Handle symlinked database targets and follow symlink targets even when
  the DB file does not exist yet.
- Canonicalize non-existent database paths safely via a multi-level
  ancestor walk loop.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Branch store query routines must distinguish between records belonging to
different repositories in shared database mode while matching NULL
repo_path records when in local database mode.

Update `IndexedBranchStore` in `src/database/branches.rs` and
`DatabaseManager` in `src/database/connection.rs`:
- Add nullable `repo_path: Option<String>` to `IndexedBranchInfo` and
  `IndexedBranchInfoJson`.
- Store `repo_identity` and `is_local_db` inside `IndexedBranchStore`.
- Introduce `build_sql_filter()` to automatically supply the default
  SQL filter for query operations:
  - Local DB mode: `(repo_path = '{repo_identity}' OR repo_path IS NULL)`
  - Shared DB mode: `repo_path = '{repo_identity}'`
- Order branch query results by `indexed_at DESC` so the newest indexed
  record is returned when both a legacy NULL record and a repo-scoped
  record exist.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
In shared database mode, repositories deleted from the local filesystem
leave behind stale `indexed_branches` records matching
`repo_path = '{deleted_path}'`.

Implement `DatabaseManager::garbage_collect()` and `GcStats`:
- Query distinct `repo_path` values where `repo_path IS NOT NULL`.
- Expand `~/` home directory path prefixes using `dirs::home_dir()`.
- Purge stale records from deleted repositories via LanceDB
  `table.delete()`.
- Purge un-scoped legacy `repo_path IS NULL` records when the database
  directory resides outside all Git repositories on disk (standalone
  database mode).
- Invoke `garbage_collect()` automatically at the end of indexing in
  `src/bin/index.rs`.
- Return `GcStats` with count of deleted branch records.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Document Semcode's shared database architecture, detailing multi-repository
co-indexing, global content deduplication vs. repo-scoped branch isolation,
database locality detection (local vs. shared), Git worktree consolidation,
and automatic garbage collection.

Update docs/schema.md to include the repo_path column and BTree index on the
indexed_branches table.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants