lorekeep 0.3.0__tar.gz → 0.3.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.
- lorekeep-0.3.1/.release-please-manifest.json +3 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/AGENTS.md +3 -3
- {lorekeep-0.3.0 → lorekeep-0.3.1}/CHANGELOG.md +7 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/PKG-INFO +1 -1
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/evaluation.md +1 -1
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/guides/getting-started.md +4 -4
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/guides/serve.md +2 -2
- {lorekeep-0.3.0 → lorekeep-0.3.1}/pyproject.toml +1 -1
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/__init__.py +1 -1
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/cli.py +19 -42
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/defaults.py +1 -1
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/importer/claude.py +15 -0
- lorekeep-0.3.1/tests/conftest.py +57 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_agent_cli.py +6 -12
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_compile_cli.py +1 -2
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_import_claude.py +55 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_import_cursor.py +1 -3
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_init_chain.py +4 -6
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_watch_e2e.py +3 -4
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_watch_sessions.py +0 -2
- lorekeep-0.3.0/.release-please-manifest.json +0 -3
- lorekeep-0.3.0/tests/conftest.py +0 -7
- {lorekeep-0.3.0 → lorekeep-0.3.1}/.commitlintrc.json +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/.github/workflows/auto-merge-release-please.yml +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/.github/workflows/ci.yml +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/.github/workflows/lint-commits.yml +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/.github/workflows/release-please.yml +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/.gitignore +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/.lorekeep/config.yaml.example +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/.lorekeep/schema.json +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/.pre-commit-config.yaml +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/CLAUDE.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/LICENSE +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/README.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/SECURITY.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/cover.jpeg +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/README.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/agent.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/data-model.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/journal.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/overview.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/permission.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/pipeline.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/serve-mcp.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/temporal.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/guides/backup.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/guides/compile.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/guides/data-home.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/guides/import.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/superpowers/plans/2026-06-26-lorekeep-dotdir-backup.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/superpowers/specs/2026-06-26-lorekeep-dotdir-layout-design.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/release-please-config.json +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/scripts/check-conventional-commit.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/agent.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/backup.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/compile/__init__.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/compile/extract.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/compile/ingest.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/compile/providers.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/compile/resolve.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/compile/writer.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/config.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/eval/__init__.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/eval/construction.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/eval/gold.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/eval/retrieval.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/facts_io.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/importer/__init__.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/importer/cursor.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/integrations/__init__.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/integrations/claude_code.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/integrations/codex.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/integrations/common.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/integrations/cursor.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/integrations/detect.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/integrations/opencode.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/journal.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/mcp_server.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/models.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/paths.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/perm/__init__.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/perm/ns.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/pipeline.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/schema_io.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/store/__init__.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/store/fts.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/store/graph.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/fixtures/claude-session/memory/MEMORY.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/fixtures/claude-session/memory/lorekeep-design.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/fixtures/claude-session/memory/lorekeep-project-state.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/fixtures/claude-session/test.jsonl +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/fixtures/gold/payments.facts.jsonl +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/fixtures/raw/backend/payments.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/fixtures/retrieval/questions.json +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/fixtures/schema.json +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_agent_detect.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_backup.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_backup_cli.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_config.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_construction_eval.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_defaults.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_determinism.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_doctor_cli.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_eval_cli.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_extract.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_fts.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_graph_store.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_ingest.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_init_cli.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_integrations.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_mcp_add_cli.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_mcp_reload.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_mcp_server.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_mcp_write_tools.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_merge_journals.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_models.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_paths.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_perm.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_pipeline.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_providers.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_resolve.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_retrieval_eval.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_schema_io.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_serve_cli.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_smoke.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_writer.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.3.1}/uv.lock +0 -0
|
@@ -35,7 +35,7 @@ uv run lorekeep <command> # run the CLI in dev mode
|
|
|
35
35
|
| `backup [--init <remote-url>]` | Commit + push `.lorekeep/` to your private backup git repo |
|
|
36
36
|
| `version` | Print version |
|
|
37
37
|
|
|
38
|
-
**Offline / no-LLM mode:**
|
|
38
|
+
**Offline / no-LLM mode:** tests inject `FakeProvider` via monkeypatch (`patch_make_provider` / `patch_make_import_provider` fixtures in `conftest.py`). **All CLI/compile/import tests use this** — no API key or real model required.
|
|
39
39
|
|
|
40
40
|
## Architecture: two strictly separated phases
|
|
41
41
|
|
|
@@ -83,7 +83,7 @@ Pure (no I/O), 4-tier precedence high→low: explicit `LOREKEEP_RAW/OUT/CACHE/SC
|
|
|
83
83
|
|
|
84
84
|
## Tests
|
|
85
85
|
|
|
86
|
-
`~140` tests, no network. Gold corpus in `tests/fixtures/gold/`, raw fixtures in `tests/fixtures/raw/`. Compile/serve/import tests
|
|
86
|
+
`~140` tests, no network. Gold corpus in `tests/fixtures/gold/`, raw fixtures in `tests/fixtures/raw/`. Compile/serve/import tests inject `FakeProvider` via `patch_make_provider` / `patch_make_import_provider` conftest fixtures to pin paths and avoid the LLM — follow this pattern for any new CLI test rather than hitting a real provider.
|
|
87
87
|
|
|
88
88
|
## Cursor Cloud specific instructions
|
|
89
89
|
|
|
@@ -91,7 +91,7 @@ The startup update script already runs `uv sync`; the toolchain (Python 3.11+ vi
|
|
|
91
91
|
|
|
92
92
|
Non-obvious caveats for running/testing here:
|
|
93
93
|
|
|
94
|
-
- **No API key needed for
|
|
94
|
+
- **No API key needed for tests.** Tests inject `FakeProvider` via `patch_make_provider` / `patch_make_import_provider` conftest fixtures. For manual smoke testing, configure a real provider in `config.yaml`. To avoid polluting the repo's `.lorekeep/` data home, run demos against a throwaway data home: `LOREKEEP_HOME=/tmp/lk uv run lorekeep <cmd>`.
|
|
95
95
|
- **End-to-end smoke flow** (offline): `init` → drop a markdown file under `.lorekeep/raw/<ns>/` → `compile` → `check`/`doctor`. Then query the graph by calling the `mcp_server.py` tools directly after `ms.configure(graph_dir=..., allowed_ns=[...], schema_path=...)` — the read tools (`search`, `get_node`, `neighbors`, `at_time`, `history`, `list_namespaces`) are plain functions, no MCP transport required. `search` returns a list of node-id strings; `get_node` returns a dict; `neighbors`/`at_time`/`changes` return `{"nodes": [...], "edges": [...]}`.
|
|
96
96
|
- **`lorekeep serve` blocks on stdio** waiting for an MCP client — it won't return on its own. To just confirm it boots, run it under `timeout` with stdin closed (`timeout 3 uv run lorekeep serve --transport stdio </dev/null`); a clean timeout with no error output means success.
|
|
97
97
|
- **`tests/test_mcp_reload.py::test_lazy_reload_on_facts_change` is timing-flaky** — lazy-reload triggers off `facts.jsonl` mtime, and the test can rewrite the file within one mtime tick on fast filesystems, so it intermittently fails then passes on re-run. Treat an isolated failure of just this test as a flake, not a regression.
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.1](https://github.com/manhhailua/lorekeep/compare/v0.3.0...v0.3.1) (2026-06-28)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* add manifest dedup to deep-mode session import ([#61](https://github.com/manhhailua/lorekeep/issues/61)) ([a9a074f](https://github.com/manhhailua/lorekeep/commit/a9a074f901c9a8f8e4cd2e3f906fdf4be7a773d6))
|
|
9
|
+
|
|
3
10
|
## [0.3.0](https://github.com/manhhailua/lorekeep/compare/v0.2.1...v0.3.0) (2026-06-28)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
- **Golden tests:** snapshot `facts.jsonl` for a fixture corpus; diffs catch regressions.
|
|
14
14
|
- Compile-only ⇒ no concurrency to test.
|
|
15
15
|
|
|
16
|
-
All compile/serve/import tests
|
|
16
|
+
All compile/serve/import tests inject `FakeProvider` via monkeypatch to avoid a real LLM — no API key required.
|
|
17
17
|
|
|
18
18
|
## Evaluation
|
|
19
19
|
|
|
@@ -108,8 +108,9 @@ provider:
|
|
|
108
108
|
Prefer an env var instead? Set `api_key_env: DEEPSEEK_API_KEY` (and
|
|
109
109
|
`api_key: null`), then `export DEEPSEEK_API_KEY=sk-...`. Both work.
|
|
110
110
|
|
|
111
|
-
> **
|
|
112
|
-
>
|
|
111
|
+
> **No provider yet?** `init` wires agents and imports memory files
|
|
112
|
+
> regardless. Compile is skipped until you add an API key — the graph
|
|
113
|
+
> will be empty but the MCP tools are wired and ready.
|
|
113
114
|
|
|
114
115
|
## 5. Compile
|
|
115
116
|
|
|
@@ -188,8 +189,7 @@ recovery — is in [Backing up the data home](backup.md).
|
|
|
188
189
|
- **`lorekeep serve` hangs** — it blocks on stdio waiting for an MCP client.
|
|
189
190
|
To just confirm it boots, run it under a timeout with stdin closed:
|
|
190
191
|
`timeout 3 uvx lorekeep serve --transport stdio </dev/null`.
|
|
191
|
-
- **Compile gave 0 nodes** — the provider wasn't reached.
|
|
192
|
-
`export LOREKEEP_PROVIDER=fake` for an offline test, or check
|
|
192
|
+
- **Compile gave 0 nodes** — the provider wasn't reached. Check
|
|
193
193
|
`api_key_env` / `api_base` in `config.yaml`. If you switched provider or
|
|
194
194
|
model after a first compile, delete `<data-home>/cache.json` (the cache key
|
|
195
195
|
doesn't include the model) and recompile.
|
|
@@ -5,9 +5,9 @@ Path resolution (env > `LOREKEEP_HOME` > dev mode > XDG) is covered in [data-hom
|
|
|
5
5
|
## Installed use (recommended)
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
uvx lorekeep init # bootstrap
|
|
8
|
+
uvx lorekeep init # bootstrap + wire agents + compile + start daemon
|
|
9
9
|
# add your docs under ~/.local/share/lorekeep/raw/<ns>/
|
|
10
|
-
|
|
10
|
+
uvx lorekeep compile # requires a real provider in config.yaml
|
|
11
11
|
uvx lorekeep mcp add --agent claude --ns <ns>
|
|
12
12
|
uvx lorekeep doctor
|
|
13
13
|
|
|
@@ -8,7 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
import typer
|
|
9
9
|
|
|
10
10
|
from lorekeep import __version__
|
|
11
|
-
from lorekeep.compile.providers import
|
|
11
|
+
from lorekeep.compile.providers import LiteLLMProvider
|
|
12
12
|
from lorekeep.config import Config, load_config
|
|
13
13
|
from lorekeep.pipeline import compile_graph
|
|
14
14
|
from lorekeep.paths import resolve_paths
|
|
@@ -39,34 +39,24 @@ def _build_provider(config: Config) -> LiteLLMProvider:
|
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"props": {"title": "payments-api adopts internal signing"}},
|
|
50
|
-
],
|
|
51
|
-
"edges": [
|
|
52
|
-
{"type": "depends_on", "from": "svc:payments-api", "to": "svc:auth",
|
|
53
|
-
"valid_from": "2024-01-15", "valid_to": "2025-03-01"},
|
|
54
|
-
{"type": "decided_by", "from": "dec:adr-007", "to": "team:backend"},
|
|
55
|
-
],
|
|
56
|
-
"aliases": {},
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def _make_provider(config: Config) -> FakeProvider | LiteLLMProvider:
|
|
61
|
-
"""Create provider honoring LOREKEEP_PROVIDER=fake for offline/test use.
|
|
62
|
-
|
|
63
|
-
Shared by standalone compile, daemon auto-compile, and import.
|
|
64
|
-
"""
|
|
65
|
-
if os.environ.get("LOREKEEP_PROVIDER") == "fake":
|
|
66
|
-
return FakeProvider(responses=[_FAKE_CANNED])
|
|
42
|
+
def _make_provider(config: Config) -> LiteLLMProvider:
|
|
43
|
+
"""Create provider for compile. Tests monkeypatch this to inject FakeProvider."""
|
|
44
|
+
return _build_provider(config)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _make_import_provider(config: Config) -> LiteLLMProvider:
|
|
48
|
+
"""Create provider for import deep mode. Tests monkeypatch this."""
|
|
67
49
|
return _build_provider(config)
|
|
68
50
|
|
|
69
51
|
|
|
52
|
+
def _has_provider(config: Config) -> bool:
|
|
53
|
+
"""Check if a provider API key is available. Tests monkeypatch this."""
|
|
54
|
+
return bool(
|
|
55
|
+
(config.provider.api_key_env and os.environ.get(config.provider.api_key_env))
|
|
56
|
+
or config.provider.api_key
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
70
60
|
@app.command()
|
|
71
61
|
def version() -> None:
|
|
72
62
|
"""Print the Lorekeep version."""
|
|
@@ -512,11 +502,7 @@ def _auto_import_and_compile(p: dict) -> None:
|
|
|
512
502
|
schema = load_schema(p["schema"])
|
|
513
503
|
config = load_config(p["config"])
|
|
514
504
|
|
|
515
|
-
has_key = (
|
|
516
|
-
os.environ.get("LOREKEEP_PROVIDER") == "fake"
|
|
517
|
-
or (config.provider.api_key_env and os.environ.get(config.provider.api_key_env))
|
|
518
|
-
or config.provider.api_key
|
|
519
|
-
)
|
|
505
|
+
has_key = _has_provider(config)
|
|
520
506
|
|
|
521
507
|
if not has_key:
|
|
522
508
|
if imported:
|
|
@@ -640,15 +626,6 @@ def import_cmd(
|
|
|
640
626
|
p = resolve_paths()
|
|
641
627
|
config = load_config(p["config"])
|
|
642
628
|
|
|
643
|
-
def _build_import_provider():
|
|
644
|
-
"""Deep-mode provider (fake under LOREKEEP_PROVIDER=fake, else config)."""
|
|
645
|
-
if os.environ.get("LOREKEEP_PROVIDER") == "fake":
|
|
646
|
-
from lorekeep.compile.providers import FakeProvider
|
|
647
|
-
# Provide enough canned responses for deep mode batches
|
|
648
|
-
canned = "# Knowledge Summary\n\n## Decisions\n- No real session data imported (fake provider).\n"
|
|
649
|
-
return FakeProvider(responses=[canned] * 50)
|
|
650
|
-
return _build_provider(config)
|
|
651
|
-
|
|
652
629
|
# --- Cursor: global composer conversations, deep-only ------------------
|
|
653
630
|
if from_source == "cursor":
|
|
654
631
|
if quick:
|
|
@@ -673,7 +650,7 @@ def import_cmd(
|
|
|
673
650
|
ns = session_ns or "cursor-session"
|
|
674
651
|
result = import_cursor(
|
|
675
652
|
raw_root=p["raw"], db_path=db, namespace=ns,
|
|
676
|
-
provider=
|
|
653
|
+
provider=_make_import_provider(config), dry_run=dry_run,
|
|
677
654
|
)
|
|
678
655
|
ses_count = len(result.get("session", []))
|
|
679
656
|
if dry_run:
|
|
@@ -697,7 +674,7 @@ def import_cmd(
|
|
|
697
674
|
"Run Claude Code in this project first.")
|
|
698
675
|
raise typer.Exit(code=1)
|
|
699
676
|
|
|
700
|
-
provider = None if quick else
|
|
677
|
+
provider = None if quick else _make_import_provider(config)
|
|
701
678
|
|
|
702
679
|
from lorekeep.importer.claude import import_claude
|
|
703
680
|
result = import_claude(
|
|
@@ -73,7 +73,7 @@ PROVIDER_PRESETS: dict[str, dict] = {
|
|
|
73
73
|
"api_key_env": None,
|
|
74
74
|
},
|
|
75
75
|
"5": {
|
|
76
|
-
"label": "Skip —
|
|
76
|
+
"label": "Skip — configure provider later (edit config.yaml)",
|
|
77
77
|
"backend": "openai",
|
|
78
78
|
"model": "openai/gpt-4o-mini",
|
|
79
79
|
"api_base": None,
|
|
@@ -342,6 +342,17 @@ def import_session_deep(
|
|
|
342
342
|
|
|
343
343
|
jsonl = transcripts[0]
|
|
344
344
|
session_id = session_dir.name
|
|
345
|
+
|
|
346
|
+
# Dedup: skip if transcript content unchanged since last import
|
|
347
|
+
transcript_hash = hashlib.sha256(
|
|
348
|
+
jsonl.read_bytes()
|
|
349
|
+
).hexdigest()
|
|
350
|
+
manifest = load_import_manifest(raw_root, namespace)
|
|
351
|
+
manifest_key = str(jsonl)
|
|
352
|
+
|
|
353
|
+
if not dry_run and manifest.get(manifest_key) == transcript_hash:
|
|
354
|
+
return []
|
|
355
|
+
|
|
345
356
|
turns = parse_transcript(jsonl)
|
|
346
357
|
if not turns:
|
|
347
358
|
return []
|
|
@@ -374,6 +385,10 @@ def import_session_deep(
|
|
|
374
385
|
previous_summary = md[:1000] # carry short context forward
|
|
375
386
|
written.append(dest)
|
|
376
387
|
|
|
388
|
+
if not dry_run and written:
|
|
389
|
+
manifest[manifest_key] = transcript_hash
|
|
390
|
+
save_import_manifest(raw_root, namespace, manifest)
|
|
391
|
+
|
|
377
392
|
return written
|
|
378
393
|
|
|
379
394
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import json
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from lorekeep.compile.providers import FakeProvider
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.fixture
|
|
9
|
+
def fixtures() -> Path:
|
|
10
|
+
return Path(__file__).parent / "fixtures"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
CANNED_EXTRACTION = json.dumps({
|
|
14
|
+
"nodes": [
|
|
15
|
+
{"id": "svc:payments-api", "type": "service", "name": "payments-api",
|
|
16
|
+
"props": {"lang": "go"}, "valid_from": "2024-01-15"},
|
|
17
|
+
{"id": "svc:auth", "type": "service", "name": "auth"},
|
|
18
|
+
{"id": "team:backend", "type": "team", "name": "team-backend"},
|
|
19
|
+
{"id": "dec:adr-007", "type": "decision",
|
|
20
|
+
"props": {"title": "payments-api adopts internal signing"}},
|
|
21
|
+
],
|
|
22
|
+
"edges": [
|
|
23
|
+
{"type": "depends_on", "from": "svc:payments-api", "to": "svc:auth",
|
|
24
|
+
"valid_from": "2024-01-15", "valid_to": "2025-03-01"},
|
|
25
|
+
{"type": "decided_by", "from": "dec:adr-007", "to": "team:backend"},
|
|
26
|
+
],
|
|
27
|
+
"aliases": {},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def fake_provider():
|
|
33
|
+
"""FakeProvider with canned extraction responses for compile/ingest tests."""
|
|
34
|
+
return FakeProvider(responses=[CANNED_EXTRACTION] * 50)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def fake_extraction():
|
|
39
|
+
"""Canned extraction JSON string for tests that build their own FakeProvider."""
|
|
40
|
+
return CANNED_EXTRACTION
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.fixture
|
|
44
|
+
def patch_make_provider(monkeypatch, fake_provider):
|
|
45
|
+
"""Monkeypatch cli._make_provider + _has_provider to return a FakeProvider."""
|
|
46
|
+
monkeypatch.setattr("lorekeep.cli._make_provider", lambda config: fake_provider)
|
|
47
|
+
monkeypatch.setattr("lorekeep.cli._has_provider", lambda config: True)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.fixture
|
|
51
|
+
def patch_make_import_provider(monkeypatch):
|
|
52
|
+
"""Monkeypatch cli._make_import_provider to return a FakeProvider for import deep mode."""
|
|
53
|
+
canned = "# Knowledge Summary\n\n## Decisions\n- Test import summary.\n"
|
|
54
|
+
monkeypatch.setattr(
|
|
55
|
+
"lorekeep.cli._make_import_provider",
|
|
56
|
+
lambda config: FakeProvider(responses=[canned] * 50),
|
|
57
|
+
)
|
|
@@ -6,14 +6,13 @@ from lorekeep.cli import app
|
|
|
6
6
|
runner = CliRunner()
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def test_agent_ingest_yes_flag(monkeypatch, tmp_path: Path, fixtures: Path):
|
|
9
|
+
def test_agent_ingest_yes_flag(patch_make_provider, monkeypatch, tmp_path: Path, fixtures: Path):
|
|
10
10
|
"""agent ingest --yes: approve all facts, journal is created."""
|
|
11
11
|
monkeypatch.setenv("LOREKEEP_RAW", str(tmp_path / "raw"))
|
|
12
12
|
monkeypatch.setenv("LOREKEEP_OUT", str(tmp_path / "graph"))
|
|
13
13
|
monkeypatch.setenv("LOREKEEP_CACHE", str(tmp_path / "cache.json"))
|
|
14
14
|
monkeypatch.setenv("LOREKEEP_SCHEMA", str(fixtures / "schema.json"))
|
|
15
15
|
monkeypatch.setenv("LOREKEEP_PENDING", str(tmp_path / "pending"))
|
|
16
|
-
monkeypatch.setenv("LOREKEEP_PROVIDER", "fake")
|
|
17
16
|
|
|
18
17
|
raw_file = tmp_path / "raw" / "backend" / "payments.md"
|
|
19
18
|
raw_file.parent.mkdir(parents=True)
|
|
@@ -35,14 +34,13 @@ def test_agent_ingest_yes_flag(monkeypatch, tmp_path: Path, fixtures: Path):
|
|
|
35
34
|
assert entry["status"] == "pending"
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
def test_agent_ingest_interactive_approve_all(monkeypatch, tmp_path: Path, fixtures: Path):
|
|
37
|
+
def test_agent_ingest_interactive_approve_all(patch_make_provider, monkeypatch, tmp_path: Path, fixtures: Path):
|
|
39
38
|
"""agent ingest: interactive mode, approve all nodes and edges."""
|
|
40
39
|
monkeypatch.setenv("LOREKEEP_RAW", str(tmp_path / "raw"))
|
|
41
40
|
monkeypatch.setenv("LOREKEEP_OUT", str(tmp_path / "graph"))
|
|
42
41
|
monkeypatch.setenv("LOREKEEP_CACHE", str(tmp_path / "cache.json"))
|
|
43
42
|
monkeypatch.setenv("LOREKEEP_SCHEMA", str(fixtures / "schema.json"))
|
|
44
43
|
monkeypatch.setenv("LOREKEEP_PENDING", str(tmp_path / "pending"))
|
|
45
|
-
monkeypatch.setenv("LOREKEEP_PROVIDER", "fake")
|
|
46
44
|
|
|
47
45
|
raw_file = tmp_path / "raw" / "backend" / "payments.md"
|
|
48
46
|
raw_file.parent.mkdir(parents=True)
|
|
@@ -61,14 +59,13 @@ def test_agent_ingest_interactive_approve_all(monkeypatch, tmp_path: Path, fixtu
|
|
|
61
59
|
assert len(lines) == 6 # 4 nodes + 2 edges
|
|
62
60
|
|
|
63
61
|
|
|
64
|
-
def test_agent_ingest_interactive_reject_all(monkeypatch, tmp_path: Path, fixtures: Path):
|
|
62
|
+
def test_agent_ingest_interactive_reject_all(patch_make_provider, monkeypatch, tmp_path: Path, fixtures: Path):
|
|
65
63
|
"""agent ingest: interactive mode, reject all → no journal entries."""
|
|
66
64
|
monkeypatch.setenv("LOREKEEP_RAW", str(tmp_path / "raw"))
|
|
67
65
|
monkeypatch.setenv("LOREKEEP_OUT", str(tmp_path / "graph"))
|
|
68
66
|
monkeypatch.setenv("LOREKEEP_CACHE", str(tmp_path / "cache.json"))
|
|
69
67
|
monkeypatch.setenv("LOREKEEP_SCHEMA", str(fixtures / "schema.json"))
|
|
70
68
|
monkeypatch.setenv("LOREKEEP_PENDING", str(tmp_path / "pending"))
|
|
71
|
-
monkeypatch.setenv("LOREKEEP_PROVIDER", "fake")
|
|
72
69
|
|
|
73
70
|
raw_file = tmp_path / "raw" / "backend" / "payments.md"
|
|
74
71
|
raw_file.parent.mkdir(parents=True)
|
|
@@ -82,14 +79,13 @@ def test_agent_ingest_interactive_reject_all(monkeypatch, tmp_path: Path, fixtur
|
|
|
82
79
|
assert "nothing approved" in result.stdout
|
|
83
80
|
|
|
84
81
|
|
|
85
|
-
def test_agent_ingest_interactive_review_individual(monkeypatch, tmp_path: Path, fixtures: Path):
|
|
82
|
+
def test_agent_ingest_interactive_review_individual(patch_make_provider, monkeypatch, tmp_path: Path, fixtures: Path):
|
|
86
83
|
"""agent ingest: interactive mode, reject all-at-once but approve individually."""
|
|
87
84
|
monkeypatch.setenv("LOREKEEP_RAW", str(tmp_path / "raw"))
|
|
88
85
|
monkeypatch.setenv("LOREKEEP_OUT", str(tmp_path / "graph"))
|
|
89
86
|
monkeypatch.setenv("LOREKEEP_CACHE", str(tmp_path / "cache.json"))
|
|
90
87
|
monkeypatch.setenv("LOREKEEP_SCHEMA", str(fixtures / "schema.json"))
|
|
91
88
|
monkeypatch.setenv("LOREKEEP_PENDING", str(tmp_path / "pending"))
|
|
92
|
-
monkeypatch.setenv("LOREKEEP_PROVIDER", "fake")
|
|
93
89
|
|
|
94
90
|
raw_file = tmp_path / "raw" / "backend" / "payments.md"
|
|
95
91
|
raw_file.parent.mkdir(parents=True)
|
|
@@ -111,12 +107,11 @@ def test_agent_ingest_interactive_review_individual(monkeypatch, tmp_path: Path,
|
|
|
111
107
|
assert len(lines) == 4 # 4 nodes approved, 0 edges
|
|
112
108
|
|
|
113
109
|
|
|
114
|
-
def test_agent_ingest_missing_source(monkeypatch, tmp_path: Path, fixtures: Path):
|
|
110
|
+
def test_agent_ingest_missing_source(patch_make_provider, monkeypatch, tmp_path: Path, fixtures: Path):
|
|
115
111
|
"""agent ingest: missing source file errors out."""
|
|
116
112
|
monkeypatch.setenv("LOREKEEP_RAW", str(tmp_path / "raw"))
|
|
117
113
|
monkeypatch.setenv("LOREKEEP_SCHEMA", str(fixtures / "schema.json"))
|
|
118
114
|
monkeypatch.setenv("LOREKEEP_PENDING", str(tmp_path / "pending"))
|
|
119
|
-
monkeypatch.setenv("LOREKEEP_PROVIDER", "fake")
|
|
120
115
|
|
|
121
116
|
raw_root = tmp_path / "raw"
|
|
122
117
|
raw_root.mkdir(parents=True)
|
|
@@ -126,12 +121,11 @@ def test_agent_ingest_missing_source(monkeypatch, tmp_path: Path, fixtures: Path
|
|
|
126
121
|
assert "not found" in result.stdout
|
|
127
122
|
|
|
128
123
|
|
|
129
|
-
def test_agent_ingest_source_outside_raw(monkeypatch, tmp_path: Path, fixtures: Path):
|
|
124
|
+
def test_agent_ingest_source_outside_raw(patch_make_provider, monkeypatch, tmp_path: Path, fixtures: Path):
|
|
130
125
|
"""agent ingest: source outside raw/ errors out."""
|
|
131
126
|
monkeypatch.setenv("LOREKEEP_RAW", str(tmp_path / "raw"))
|
|
132
127
|
monkeypatch.setenv("LOREKEEP_SCHEMA", str(fixtures / "schema.json"))
|
|
133
128
|
monkeypatch.setenv("LOREKEEP_PENDING", str(tmp_path / "pending"))
|
|
134
|
-
monkeypatch.setenv("LOREKEEP_PROVIDER", "fake")
|
|
135
129
|
|
|
136
130
|
raw_root = tmp_path / "raw"
|
|
137
131
|
raw_root.mkdir(parents=True)
|
|
@@ -6,13 +6,12 @@ from lorekeep.cli import app
|
|
|
6
6
|
runner = CliRunner()
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def test_compile_command_uses_config_provider(monkeypatch, tmp_path: Path, fixtures: Path):
|
|
9
|
+
def test_compile_command_uses_config_provider(patch_make_provider, monkeypatch, tmp_path: Path, fixtures: Path):
|
|
10
10
|
# point the CLI at temp dirs via env
|
|
11
11
|
monkeypatch.setenv("LOREKEEP_RAW", str(tmp_path / "raw"))
|
|
12
12
|
monkeypatch.setenv("LOREKEEP_OUT", str(tmp_path / "graph"))
|
|
13
13
|
monkeypatch.setenv("LOREKEEP_CACHE", str(tmp_path / "cache.json"))
|
|
14
14
|
monkeypatch.setenv("LOREKEEP_SCHEMA", str(fixtures / "schema.json"))
|
|
15
|
-
monkeypatch.setenv("LOREKEEP_PROVIDER", "fake")
|
|
16
15
|
|
|
17
16
|
raw = tmp_path / "raw/backend/payments.md"
|
|
18
17
|
raw.parent.mkdir(parents=True)
|
|
@@ -193,6 +193,61 @@ def test_import_session_deep_handles_empty_transcript(tmp_path: Path):
|
|
|
193
193
|
assert result == []
|
|
194
194
|
|
|
195
195
|
|
|
196
|
+
def test_import_session_deep_is_idempotent(tmp_path: Path, fixtures: Path):
|
|
197
|
+
"""Re-importing the same transcript skips LLM summarization."""
|
|
198
|
+
session_dir = fixtures / "claude-session"
|
|
199
|
+
raw_root = tmp_path / "raw"
|
|
200
|
+
provider = FakeProvider(responses=["# Summary\n\n## Decisions\n- Use FastAPI\n"] * 50)
|
|
201
|
+
|
|
202
|
+
first = import_session_deep(session_dir, raw_root, namespace="test-session",
|
|
203
|
+
provider=provider)
|
|
204
|
+
assert len(first) >= 1
|
|
205
|
+
assert len(provider.calls) >= 1
|
|
206
|
+
|
|
207
|
+
# Second call: same transcript, should skip entirely
|
|
208
|
+
call_before = len(provider.calls)
|
|
209
|
+
second = import_session_deep(session_dir, raw_root, namespace="test-session",
|
|
210
|
+
provider=provider)
|
|
211
|
+
assert second == []
|
|
212
|
+
assert len(provider.calls) == call_before # no new LLM calls
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def test_import_session_deep_re_imports_on_change(tmp_path: Path):
|
|
216
|
+
"""Transcript changed → re-import triggers LLM call."""
|
|
217
|
+
session_dir = tmp_path / "session"
|
|
218
|
+
session_dir.mkdir()
|
|
219
|
+
transcript = session_dir / "sess.jsonl"
|
|
220
|
+
transcript.write_text('{"role":"user","message":{"content":"what is FastAPI?"}}\n'
|
|
221
|
+
'{"role":"assistant","message":{"content":[{"type":"text","text":"a web framework"}]}}\n')
|
|
222
|
+
|
|
223
|
+
raw_root = tmp_path / "raw"
|
|
224
|
+
provider = FakeProvider(responses=["# Summary\n\n## Decisions\n- Use FastAPI\n"] * 50)
|
|
225
|
+
|
|
226
|
+
first = import_session_deep(session_dir, raw_root, namespace="ns", provider=provider)
|
|
227
|
+
assert len(first) >= 1
|
|
228
|
+
|
|
229
|
+
# Modify transcript
|
|
230
|
+
transcript.write_text('{"role":"user","message":{"content":"what is Flask?"}}\n'
|
|
231
|
+
'{"role":"assistant","message":{"content":[{"type":"text","text":"a web framework"}]}}\n')
|
|
232
|
+
|
|
233
|
+
call_before = len(provider.calls)
|
|
234
|
+
second = import_session_deep(session_dir, raw_root, namespace="ns", provider=provider)
|
|
235
|
+
assert len(second) >= 1
|
|
236
|
+
assert len(provider.calls) > call_before # LLM called again
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def test_import_session_deep_dry_run_skips_manifest(tmp_path: Path, fixtures: Path):
|
|
240
|
+
"""Dry run should not write manifest (so real import still processes)."""
|
|
241
|
+
session_dir = fixtures / "claude-session"
|
|
242
|
+
raw_root = tmp_path / "raw"
|
|
243
|
+
provider = FakeProvider(responses=["dummy"] * 50)
|
|
244
|
+
|
|
245
|
+
import_session_deep(session_dir, raw_root, namespace="ns",
|
|
246
|
+
provider=provider, dry_run=True)
|
|
247
|
+
manifest_path = raw_root / "ns" / ".import-manifest.json"
|
|
248
|
+
assert not manifest_path.exists()
|
|
249
|
+
|
|
250
|
+
|
|
196
251
|
# ---------------------------------------------------------------------------
|
|
197
252
|
# orchestrator
|
|
198
253
|
# ---------------------------------------------------------------------------
|
|
@@ -191,11 +191,10 @@ def test_import_cursor_missing_db_errors(tmp_path: Path):
|
|
|
191
191
|
# ---------------------------------------------------------------------------
|
|
192
192
|
|
|
193
193
|
|
|
194
|
-
def test_cli_import_cursor_runs(monkeypatch, tmp_path: Path, state_db: Path):
|
|
194
|
+
def test_cli_import_cursor_runs(patch_make_import_provider, monkeypatch, tmp_path: Path, state_db: Path):
|
|
195
195
|
monkeypatch.setenv("LOREKEEP_RAW", str(tmp_path / "raw"))
|
|
196
196
|
monkeypatch.setenv("LOREKEEP_OUT", str(tmp_path / "graph"))
|
|
197
197
|
monkeypatch.setenv("LOREKEEP_CACHE", str(tmp_path / "cache.json"))
|
|
198
|
-
monkeypatch.setenv("LOREKEEP_PROVIDER", "fake")
|
|
199
198
|
monkeypatch.setenv("CURSOR_STATE_DB", str(state_db))
|
|
200
199
|
|
|
201
200
|
result = runner.invoke(app, ["import", "--from", "cursor"])
|
|
@@ -205,7 +204,6 @@ def test_cli_import_cursor_runs(monkeypatch, tmp_path: Path, state_db: Path):
|
|
|
205
204
|
|
|
206
205
|
def test_cli_import_cursor_rejects_quick(monkeypatch, tmp_path: Path, state_db: Path):
|
|
207
206
|
monkeypatch.setenv("CURSOR_STATE_DB", str(state_db))
|
|
208
|
-
monkeypatch.setenv("LOREKEEP_PROVIDER", "fake")
|
|
209
207
|
result = runner.invoke(app, ["import", "--from", "cursor", "--quick"])
|
|
210
208
|
assert result.exit_code == 1
|
|
211
209
|
assert "deep-only" in result.stdout
|
|
@@ -10,7 +10,7 @@ from lorekeep.cli import app
|
|
|
10
10
|
runner = CliRunner()
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def _setup_env(tmp_path: Path, monkeypatch
|
|
13
|
+
def _setup_env(tmp_path: Path, monkeypatch):
|
|
14
14
|
home = tmp_path / "home"
|
|
15
15
|
project = tmp_path / "project"
|
|
16
16
|
fake_home = tmp_path / "fakehome"
|
|
@@ -24,16 +24,14 @@ def _setup_env(tmp_path: Path, monkeypatch, fake_provider=True):
|
|
|
24
24
|
monkeypatch.setattr("lorekeep.integrations.detect.shutil.which", lambda _: None)
|
|
25
25
|
monkeypatch.delenv("OPENCODE", raising=False)
|
|
26
26
|
monkeypatch.delenv("CLAUDECODE", raising=False)
|
|
27
|
-
if fake_provider:
|
|
28
|
-
monkeypatch.setenv("LOREKEEP_PROVIDER", "fake")
|
|
29
27
|
return home, project
|
|
30
28
|
|
|
31
29
|
|
|
32
30
|
# ── Init chains into compile ──────────────────────────────────────────────
|
|
33
31
|
|
|
34
32
|
|
|
35
|
-
def
|
|
36
|
-
"""init --yes with
|
|
33
|
+
def test_init_compiles_with_provider(patch_make_provider, tmp_path: Path, monkeypatch):
|
|
34
|
+
"""init --yes with patched provider should auto-compile facts.jsonl."""
|
|
37
35
|
home, project = _setup_env(tmp_path, monkeypatch)
|
|
38
36
|
|
|
39
37
|
result = runner.invoke(app, ["init", "--yes", "--no-watch"])
|
|
@@ -47,7 +45,7 @@ def test_init_compiles_with_fake_provider(tmp_path: Path, monkeypatch):
|
|
|
47
45
|
|
|
48
46
|
def test_init_skips_compile_without_api_key(tmp_path: Path, monkeypatch):
|
|
49
47
|
"""init --yes without provider key should skip compile gracefully."""
|
|
50
|
-
home, project = _setup_env(tmp_path, monkeypatch
|
|
48
|
+
home, project = _setup_env(tmp_path, monkeypatch)
|
|
51
49
|
|
|
52
50
|
result = runner.invoke(app, ["init", "--yes", "--no-watch"])
|
|
53
51
|
assert result.exit_code == 0, result.stdout
|
|
@@ -26,7 +26,6 @@ def _bootstrap_home(tmp_path: Path):
|
|
|
26
26
|
def _set_env(home: Path):
|
|
27
27
|
return {
|
|
28
28
|
"LOREKEEP_HOME": str(home),
|
|
29
|
-
"LOREKEEP_PROVIDER": "fake",
|
|
30
29
|
"LOREKEEP_DEV": "0",
|
|
31
30
|
}
|
|
32
31
|
|
|
@@ -34,7 +33,7 @@ def _set_env(home: Path):
|
|
|
34
33
|
# ── Compile → MCP visibility ──────────────────────────────────────────────
|
|
35
34
|
|
|
36
35
|
|
|
37
|
-
def test_raw_change_compiled_then_visible_in_mcp(tmp_path: Path, fixtures: Path, monkeypatch):
|
|
36
|
+
def test_raw_change_compiled_then_visible_in_mcp(patch_make_provider, tmp_path: Path, fixtures: Path, monkeypatch):
|
|
38
37
|
"""Write markdown to raw/ → compile (fake) → facts.jsonl → MCP search finds new nodes."""
|
|
39
38
|
home = _bootstrap_home(tmp_path)
|
|
40
39
|
shutil.copy(fixtures / "schema.json", home / "schema.json")
|
|
@@ -105,7 +104,7 @@ def test_pending_journal_resolved_then_visible_in_mcp(tmp_path: Path, fixtures:
|
|
|
105
104
|
# ── Standalone compile preserves pending journals (PR1 fix) ───────────────
|
|
106
105
|
|
|
107
106
|
|
|
108
|
-
def test_standalone_compile_preserves_pending_journals(tmp_path: Path, fixtures: Path, monkeypatch):
|
|
107
|
+
def test_standalone_compile_preserves_pending_journals(patch_make_provider, tmp_path: Path, fixtures: Path, monkeypatch):
|
|
109
108
|
"""lorekeep compile re-merges pending journals after regenerating facts.jsonl."""
|
|
110
109
|
home = _bootstrap_home(tmp_path)
|
|
111
110
|
shutil.copy(fixtures / "schema.json", home / "schema.json")
|
|
@@ -150,7 +149,7 @@ def test_standalone_compile_preserves_pending_journals(tmp_path: Path, fixtures:
|
|
|
150
149
|
# ── Full loop: raw write + journal → compile + resolve → MCP ──────────────
|
|
151
150
|
|
|
152
151
|
|
|
153
|
-
def test_full_loop_compile_and_resolve_visible_in_mcp(tmp_path: Path, fixtures: Path, monkeypatch):
|
|
152
|
+
def test_full_loop_compile_and_resolve_visible_in_mcp(patch_make_provider, tmp_path: Path, fixtures: Path, monkeypatch):
|
|
154
153
|
"""Raw markdown + pending journal → compile → both visible through MCP search."""
|
|
155
154
|
home = _bootstrap_home(tmp_path)
|
|
156
155
|
shutil.copy(fixtures / "schema.json", home / "schema.json")
|
|
@@ -130,7 +130,6 @@ def test_watch_boots_and_shuts_down_cleanly(monkeypatch, tmp_path: Path):
|
|
|
130
130
|
monkeypatch.setenv("LOREKEEP_OUT", str(tmp_path / "graph"))
|
|
131
131
|
monkeypatch.setenv("LOREKEEP_CACHE", str(tmp_path / "cache.json"))
|
|
132
132
|
monkeypatch.setenv("LOREKEEP_PENDING", str(tmp_path / "pending"))
|
|
133
|
-
monkeypatch.setenv("LOREKEEP_PROVIDER", "fake")
|
|
134
133
|
monkeypatch.setenv("LOREKEEP_DEV", "1")
|
|
135
134
|
|
|
136
135
|
(tmp_path / "raw").mkdir()
|
|
@@ -150,7 +149,6 @@ def test_watch_boots_and_shuts_down_cleanly(monkeypatch, tmp_path: Path):
|
|
|
150
149
|
"LOREKEEP_OUT": str(tmp_path / "graph"),
|
|
151
150
|
"LOREKEEP_CACHE": str(tmp_path / "cache.json"),
|
|
152
151
|
"LOREKEEP_PENDING": str(tmp_path / "pending"),
|
|
153
|
-
"LOREKEEP_PROVIDER": "fake",
|
|
154
152
|
"LOREKEEP_DEV": "1",
|
|
155
153
|
"PYTHONPATH": str(Path(__file__).parent.parent / "src"),
|
|
156
154
|
},
|
lorekeep-0.3.0/tests/conftest.py
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lorekeep-0.3.0 → lorekeep-0.3.1}/docs/superpowers/plans/2026-06-26-lorekeep-dotdir-backup.md
RENAMED
|
File without changes
|
{lorekeep-0.3.0 → lorekeep-0.3.1}/docs/superpowers/specs/2026-06-26-lorekeep-dotdir-layout-design.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lorekeep-0.3.0 → lorekeep-0.3.1}/tests/fixtures/claude-session/memory/lorekeep-project-state.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|