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.
Files changed (127) hide show
  1. lorekeep-0.3.1/.release-please-manifest.json +3 -0
  2. {lorekeep-0.3.0 → lorekeep-0.3.1}/AGENTS.md +3 -3
  3. {lorekeep-0.3.0 → lorekeep-0.3.1}/CHANGELOG.md +7 -0
  4. {lorekeep-0.3.0 → lorekeep-0.3.1}/PKG-INFO +1 -1
  5. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/evaluation.md +1 -1
  6. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/guides/getting-started.md +4 -4
  7. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/guides/serve.md +2 -2
  8. {lorekeep-0.3.0 → lorekeep-0.3.1}/pyproject.toml +1 -1
  9. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/__init__.py +1 -1
  10. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/cli.py +19 -42
  11. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/defaults.py +1 -1
  12. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/importer/claude.py +15 -0
  13. lorekeep-0.3.1/tests/conftest.py +57 -0
  14. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_agent_cli.py +6 -12
  15. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_compile_cli.py +1 -2
  16. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_import_claude.py +55 -0
  17. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_import_cursor.py +1 -3
  18. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_init_chain.py +4 -6
  19. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_watch_e2e.py +3 -4
  20. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_watch_sessions.py +0 -2
  21. lorekeep-0.3.0/.release-please-manifest.json +0 -3
  22. lorekeep-0.3.0/tests/conftest.py +0 -7
  23. {lorekeep-0.3.0 → lorekeep-0.3.1}/.commitlintrc.json +0 -0
  24. {lorekeep-0.3.0 → lorekeep-0.3.1}/.github/workflows/auto-merge-release-please.yml +0 -0
  25. {lorekeep-0.3.0 → lorekeep-0.3.1}/.github/workflows/ci.yml +0 -0
  26. {lorekeep-0.3.0 → lorekeep-0.3.1}/.github/workflows/lint-commits.yml +0 -0
  27. {lorekeep-0.3.0 → lorekeep-0.3.1}/.github/workflows/release-please.yml +0 -0
  28. {lorekeep-0.3.0 → lorekeep-0.3.1}/.gitignore +0 -0
  29. {lorekeep-0.3.0 → lorekeep-0.3.1}/.lorekeep/config.yaml.example +0 -0
  30. {lorekeep-0.3.0 → lorekeep-0.3.1}/.lorekeep/schema.json +0 -0
  31. {lorekeep-0.3.0 → lorekeep-0.3.1}/.pre-commit-config.yaml +0 -0
  32. {lorekeep-0.3.0 → lorekeep-0.3.1}/CLAUDE.md +0 -0
  33. {lorekeep-0.3.0 → lorekeep-0.3.1}/LICENSE +0 -0
  34. {lorekeep-0.3.0 → lorekeep-0.3.1}/README.md +0 -0
  35. {lorekeep-0.3.0 → lorekeep-0.3.1}/SECURITY.md +0 -0
  36. {lorekeep-0.3.0 → lorekeep-0.3.1}/cover.jpeg +0 -0
  37. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/README.md +0 -0
  38. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/agent.md +0 -0
  39. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/data-model.md +0 -0
  40. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/journal.md +0 -0
  41. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/overview.md +0 -0
  42. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/permission.md +0 -0
  43. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/pipeline.md +0 -0
  44. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/serve-mcp.md +0 -0
  45. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/architecture/temporal.md +0 -0
  46. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/guides/backup.md +0 -0
  47. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/guides/compile.md +0 -0
  48. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/guides/data-home.md +0 -0
  49. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/guides/import.md +0 -0
  50. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/superpowers/plans/2026-06-26-lorekeep-dotdir-backup.md +0 -0
  51. {lorekeep-0.3.0 → lorekeep-0.3.1}/docs/superpowers/specs/2026-06-26-lorekeep-dotdir-layout-design.md +0 -0
  52. {lorekeep-0.3.0 → lorekeep-0.3.1}/release-please-config.json +0 -0
  53. {lorekeep-0.3.0 → lorekeep-0.3.1}/scripts/check-conventional-commit.py +0 -0
  54. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/agent.py +0 -0
  55. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/backup.py +0 -0
  56. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/compile/__init__.py +0 -0
  57. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/compile/extract.py +0 -0
  58. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/compile/ingest.py +0 -0
  59. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/compile/providers.py +0 -0
  60. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/compile/resolve.py +0 -0
  61. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/compile/writer.py +0 -0
  62. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/config.py +0 -0
  63. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/eval/__init__.py +0 -0
  64. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/eval/construction.py +0 -0
  65. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/eval/gold.py +0 -0
  66. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/eval/retrieval.py +0 -0
  67. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/facts_io.py +0 -0
  68. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/importer/__init__.py +0 -0
  69. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/importer/cursor.py +0 -0
  70. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/integrations/__init__.py +0 -0
  71. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/integrations/claude_code.py +0 -0
  72. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/integrations/codex.py +0 -0
  73. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/integrations/common.py +0 -0
  74. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/integrations/cursor.py +0 -0
  75. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/integrations/detect.py +0 -0
  76. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/integrations/opencode.py +0 -0
  77. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/journal.py +0 -0
  78. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/mcp_server.py +0 -0
  79. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/models.py +0 -0
  80. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/paths.py +0 -0
  81. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/perm/__init__.py +0 -0
  82. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/perm/ns.py +0 -0
  83. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/pipeline.py +0 -0
  84. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/schema_io.py +0 -0
  85. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/store/__init__.py +0 -0
  86. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/store/fts.py +0 -0
  87. {lorekeep-0.3.0 → lorekeep-0.3.1}/src/lorekeep/store/graph.py +0 -0
  88. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/fixtures/claude-session/memory/MEMORY.md +0 -0
  89. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/fixtures/claude-session/memory/lorekeep-design.md +0 -0
  90. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/fixtures/claude-session/memory/lorekeep-project-state.md +0 -0
  91. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/fixtures/claude-session/test.jsonl +0 -0
  92. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/fixtures/gold/payments.facts.jsonl +0 -0
  93. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/fixtures/raw/backend/payments.md +0 -0
  94. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/fixtures/retrieval/questions.json +0 -0
  95. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/fixtures/schema.json +0 -0
  96. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_agent_detect.py +0 -0
  97. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_backup.py +0 -0
  98. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_backup_cli.py +0 -0
  99. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_config.py +0 -0
  100. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_construction_eval.py +0 -0
  101. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_defaults.py +0 -0
  102. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_determinism.py +0 -0
  103. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_doctor_cli.py +0 -0
  104. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_eval_cli.py +0 -0
  105. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_extract.py +0 -0
  106. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_fts.py +0 -0
  107. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_graph_store.py +0 -0
  108. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_ingest.py +0 -0
  109. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_init_cli.py +0 -0
  110. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_integrations.py +0 -0
  111. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_mcp_add_cli.py +0 -0
  112. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_mcp_reload.py +0 -0
  113. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_mcp_server.py +0 -0
  114. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_mcp_write_tools.py +0 -0
  115. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_merge_journals.py +0 -0
  116. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_models.py +0 -0
  117. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_paths.py +0 -0
  118. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_perm.py +0 -0
  119. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_pipeline.py +0 -0
  120. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_providers.py +0 -0
  121. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_resolve.py +0 -0
  122. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_retrieval_eval.py +0 -0
  123. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_schema_io.py +0 -0
  124. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_serve_cli.py +0 -0
  125. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_smoke.py +0 -0
  126. {lorekeep-0.3.0 → lorekeep-0.3.1}/tests/test_writer.py +0 -0
  127. {lorekeep-0.3.0 → lorekeep-0.3.1}/uv.lock +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.3.1"
3
+ }
@@ -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:** set `LOREKEEP_PROVIDER=fake` to make `compile` and `import` use `FakeProvider` with canned responses. **All CLI/compile/import tests use this** — no API key or real model required.
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 set `LOREKEEP_*` env vars and `LOREKEEP_PROVIDER=fake` to pin paths and avoid the LLM — follow this pattern for any new CLI test rather than hitting a real provider.
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 any dev work.** Export `LOREKEEP_PROVIDER=fake` to run `compile`/`import` and the full end-to-end flow offline with `FakeProvider` (canned facts). To avoid polluting the repo's `.lorekeep/` data home, run demos against a throwaway data home: `LOREKEEP_HOME=/tmp/lk uv run lorekeep <cmd>`.
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lorekeep
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Temporal knowledge graph for AI agents via MCP
5
5
  Project-URL: Homepage, https://github.com/manhhailua/lorekeep
6
6
  Project-URL: Repository, https://github.com/manhhailua/lorekeep
@@ -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 set `LOREKEEP_PROVIDER=fake` to avoid a real LLM — no API key required.
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
- > **Offline / no key?** `export LOREKEEP_PROVIDER=fake` makes `compile` use a
112
- > canned response — enough to try the rest of the flow with zero API cost.
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. Either
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 ~/.config/lorekeep + ~/.local/share/lorekeep
8
+ uvx lorekeep init # bootstrap + wire agents + compile + start daemon
9
9
  # add your docs under ~/.local/share/lorekeep/raw/<ns>/
10
- LOREKEEP_PROVIDER=fake uvx lorekeep compile # (or set a real provider in config)
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
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lorekeep"
3
- version = "0.3.0"
3
+ version = "0.3.1"
4
4
  description = "Temporal knowledge graph for AI agents via MCP"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -1,3 +1,3 @@
1
1
  """Lorekeep — temporal knowledge graph for AI agents via MCP."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.3.1"
@@ -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 FakeProvider, LiteLLMProvider
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
- _FAKE_CANNED = json.dumps({
43
- "nodes": [
44
- {"id": "svc:payments-api", "type": "service", "name": "payments-api",
45
- "props": {"lang": "go"}, "valid_from": "2024-01-15"},
46
- {"id": "svc:auth", "type": "service", "name": "auth"},
47
- {"id": "team:backend", "type": "team", "name": "team-backend"},
48
- {"id": "dec:adr-007", "type": "decision",
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=_build_import_provider(), dry_run=dry_run,
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 _build_import_provider()
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 — offline/fake mode (set LOREKEEP_PROVIDER=fake to compile)",
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, fake_provider=True):
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 test_init_compiles_with_fake_provider(tmp_path: Path, monkeypatch):
36
- """init --yes with LOREKEEP_PROVIDER=fake should auto-compile facts.jsonl."""
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, fake_provider=False)
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
  },
@@ -1,3 +0,0 @@
1
- {
2
- ".": "0.3.0"
3
- }
@@ -1,7 +0,0 @@
1
- from pathlib import Path
2
- import pytest
3
-
4
-
5
- @pytest.fixture
6
- def fixtures() -> Path:
7
- return Path(__file__).parent / "fixtures"
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