Skip to content

Commit ea31ea9

Browse files
authored
Merge pull request #22 from closedloop-ai/campaign-prd-739-20260915-c7
C7 — Remove fork-only index archives (PLN-2027 PR 7, FR15)
2 parents 4f910fb + 7d4ccc5 commit ea31ea9

8 files changed

Lines changed: 14 additions & 1123 deletions

File tree

docs/planning/open-code-intel-plan.md

Lines changed: 8 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -444,54 +444,13 @@ that would read as "this code has no duplicates".
444444

445445
## F7. Index export / import
446446

447-
### Delivery
448-
449-
New `src/lemoncrow/infra/code_intel/portable.py`, CLI subcommands under the
450-
existing `lc code` group (`gateway/cli/commands/code.py`, live source):
451-
452-
```
453-
lc code export [--out .lemoncrow/index.tar.zst] [--tier best|fast]
454-
lc code import [--from .lemoncrow/index.tar.zst]
455-
```
456-
457-
- `VACUUM INTO` each of the five DBs into a temp dir (compacts, drops WAL)
458-
- tar + zstd; two tiers — `best` (zstd 9, drop derived indexes) on explicit
459-
export, `fast` (zstd 3) for incremental refresh
460-
- manifest: engine `index_version`, `indexer_semantics_version`, LemonCrow
461-
version, repo HEAD sha, row counts, sidecar `schema_version`
462-
- **import refuses on version mismatch** rather than producing a subtly wrong
463-
graph. The engine owns those numbers; we cannot migrate its data.
464-
- import bootstraps into an empty workspace, then the engine's normal
465-
incremental pass fills the local diff
466-
467-
`zstandard` is a new dependency — put it behind an extra, not the base install.
468-
469-
### Tests
470-
471-
`tests/infra/code_intel/test_portable.py` — round-trip fidelity (row counts and
472-
a sampled query match), version-mismatch refusal, corrupt-archive handling.
473-
474-
**Effort:** 5-8 days. **Risk:** low-medium. Deliberately does **not** commit the
475-
artifact to git by default.
476-
477-
**Shipped** as `8235fdaf``infra/code_intel/portable.py`, plus `lc code
478-
export` / `lc code import`. Two deviations:
479-
480-
> **`zstandard` is an accelerator, not a requirement.** It sits behind a new
481-
> `portable` extra as planned, but export falls back to stdlib lzma when it is
482-
> absent rather than failing. The manifest names the codec and import reads it,
483-
> so the feature works on a base install and gets smaller archives with the
484-
> extra.
485-
>
486-
> **The `best` tier does not drop derived indexes.** The engine's DDL is closed;
487-
> an index dropped on export is one open code cannot recreate, so the import
488-
> would hand back a database the engine expects to be complete. The two tiers
489-
> differ by compression level only.
490-
491-
One addition the plan did not call for: the archive is treated as untrusted
492-
input. Members must be regular files whose names are on a fixed allow-list, so a
493-
traversal path or a symlink is refused outright. A tar file is a format someone
494-
else can write, and "a teammate sent it" is not provenance.
447+
**Removed.** Shipped as `8235fdaf``infra/code_intel/portable.py` plus two
448+
`lc code` subcommands that packed a workspace's index into an archive and
449+
restored it — and removed again under PRD-739 FR15 (PLN-2027 PR 7): the fork
450+
added the surface, upstream never shipped it, and nothing consumed it. The
451+
code, including the archive's untrusted-input handling, is at `8235fdaf`; the
452+
delivery notes and the two deviations recorded against them are at
453+
`git show 8d77d599:docs/planning/open-code-intel-plan.md`.
495454

496455
---
497456

@@ -811,7 +770,7 @@ superseded generation just multiplies the blast radius of the defect.
811770
| F11 | `8ea1eb15` | `infra/code_intel/freshness.py` |
812771
| F2 | `f866112d` | `code_changes` tool |
813772
| F5 | `e832fc70` | `code_query` tool |
814-
| F7 | `8235fdaf` | `lc code export` / `import` |
773+
| F7 | `8235fdaf` | index export / import (removed, PRD-739 FR15) |
815774
| F12 | `67d56649` | routing + completeness contract (below) |
816775

817776
Deviations are recorded against each item above. The whole `tests/gateway/`

docs/planning/phase-b-review-handoff.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,6 @@ exclusive) and re-query — expect an `IndexRebuilding` error, not `[]`.
217217
any workspace without the optional tree-sitter `parsers` extra, and treating it
218218
as a rebuild made every code tool fail forever on a perfectly valid workspace.
219219
220-
5. **`zstandard` is not installed**, so `lc code export` uses an lzma fallback.
221-
Functional, larger archives. Not a bug.
222-
223220
---
224221
225222
## Also worth knowing

pyproject.toml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,6 @@ semantic = [
104104
parsers = [
105105
"tree-sitter-languages>=1.10; python_version < '3.13'",
106106
]
107-
# Portable code-index archives (`lc code export` / `import`). zstd beats the
108-
# stdlib codecs on both ratio and speed; without it export/import still work and
109-
# fall back to lzma, so this is an accelerator, not a requirement.
110-
portable = ["zstandard>=0.22"]
111107
rename = ["rope>=0.23"]
112108
ortools = ["ortools>=9.10"]
113109
litellm = ["litellm>=1.83.14"]

src/lemoncrow/gateway/cli/commands/code.py

Lines changed: 4 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
from lemoncrow.gateway.cli.commands._shared import _emit, require_pro
1515
from lemoncrow.gateway.integrations.openmemory_lifecycle import project_root as _project_root
16-
from lemoncrow.infra.code_intel.portable import TIERS as _PORTABLE_TIERS
1716

1817

1918
@click.group("zoekt")
@@ -823,76 +822,14 @@ def _entry_age_days(entry: Path, now: float) -> float:
823822
return max(0.0, (now - newest) / 86_400.0)
824823

825824

826-
def _portable_repo_root(repo_root: str | None) -> Path:
825+
def _resolve_repo_root(repo_root: str | None) -> Path:
827826
if repo_root is not None:
828827
return Path(repo_root).expanduser().resolve()
829828
from lemoncrow.core.foundation.paths import resolve_workspace_root
830829

831830
return Path(resolve_workspace_root()).resolve()
832831

833832

834-
@code_group.command("export")
835-
@click.option(
836-
"--out", default=None, type=click.Path(path_type=Path), help="Archive path (default: .lemoncrow/index.tar.*)."
837-
)
838-
@click.option(
839-
"--tier",
840-
type=click.Choice(sorted(_PORTABLE_TIERS)),
841-
default="best",
842-
show_default=True,
843-
help="best: smaller archive, slower. fast: quicker, for incremental refresh.",
844-
)
845-
@click.option("--repo-root", default=None, help="Repository root (default: the resolved workspace root).")
846-
@click.option("--json", "as_json", is_flag=True)
847-
def code_export_cmd(out: Path | None, tier: str, repo_root: str | None, as_json: bool) -> None:
848-
"""Pack this workspace's code index into a portable archive.
849-
850-
Compacts each database, records the engine's index and semantics versions
851-
in a manifest, and compresses the bundle. The archive is deliberately not
852-
committed anywhere by default -- it is a build artifact, not source.
853-
"""
854-
from lemoncrow.infra.code_intel.portable import PortableIndexError, export_index
855-
856-
try:
857-
result = export_index(repo_root=_portable_repo_root(repo_root), out=out, tier=tier)
858-
except PortableIndexError as exc:
859-
raise click.ClickException(str(exc)) from exc
860-
if as_json:
861-
_emit(result.to_dict(), as_json=True)
862-
return
863-
click.echo(f"Wrote {result.path} ({result.size_bytes:,} bytes, {result.codec}, tier={result.tier})")
864-
click.echo(f" databases: {', '.join(result.databases)}")
865-
click.echo(f" engine index_version: {result.manifest['engine_index_version']}")
866-
867-
868-
@code_group.command("import")
869-
@click.option("--from", "source", required=True, type=click.Path(path_type=Path), help="Archive to import.")
870-
@click.option("--repo-root", default=None, help="Repository root (default: the resolved workspace root).")
871-
@click.option("--force", is_flag=True, help="Replace an index this workspace already holds.")
872-
@click.option("--json", "as_json", is_flag=True)
873-
def code_import_cmd(source: Path, repo_root: str | None, force: bool, as_json: bool) -> None:
874-
"""Restore a code index from an archive built by `lc code export`.
875-
876-
Refuses on an indexer-semantics mismatch. The engine owns that number and
877-
open code cannot migrate its data, so importing across it would produce a
878-
graph whose edges mean something else -- confident and wrong. Re-index
879-
instead. --force overrides only the already-populated check.
880-
"""
881-
from lemoncrow.infra.code_intel.portable import PortableIndexError, import_index
882-
883-
try:
884-
result = import_index(archive=source, repo_root=_portable_repo_root(repo_root), force=force)
885-
except PortableIndexError as exc:
886-
raise click.ClickException(str(exc)) from exc
887-
if as_json:
888-
_emit(result.to_dict(), as_json=True)
889-
return
890-
click.echo(f"Restored {len(result.restored)} database(s) into {result.workspace}")
891-
click.echo(f" from: {result.archive}")
892-
click.echo(f" verified against: {result.verified_against}")
893-
click.echo("Run `lc code index` to fill in the local diff.")
894-
895-
896833
@code_group.command("clones")
897834
@click.option(
898835
"--threshold",
@@ -938,14 +875,13 @@ def code_clones_cmd(
938875

939876
try:
940877
report = build_clones(
941-
repo_root=_portable_repo_root(repo_root),
878+
repo_root=_resolve_repo_root(repo_root),
942879
threshold=DEFAULT_THRESHOLD if threshold is None else threshold,
943880
min_tokens=MIN_TOKENS if min_tokens is None else min_tokens,
944881
)
945882
except (CodeIntelUnavailable, IndexRebuilding) as exc:
946-
# Matches `lc code export` / `import` above: an unindexed workspace, or
947-
# one mid-reindex, is a thing the user can act on, so it gets a one-line
948-
# message rather than a traceback.
883+
# An unindexed workspace, or one mid-reindex, is a thing the user can act
884+
# on, so it gets a one-line message rather than a traceback.
949885
raise click.ClickException(str(exc)) from exc
950886
if as_json:
951887
_emit(report.as_dict(), as_json=True)

0 commit comments

Comments
 (0)