lorekeep 0.3.0__tar.gz → 0.4.0__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.4.0/.release-please-manifest.json +3 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/AGENTS.md +3 -3
- {lorekeep-0.3.0 → lorekeep-0.4.0}/CHANGELOG.md +14 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/PKG-INFO +1 -1
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/architecture/evaluation.md +1 -1
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/guides/getting-started.md +4 -4
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/guides/serve.md +2 -2
- {lorekeep-0.3.0 → lorekeep-0.4.0}/pyproject.toml +1 -1
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/__init__.py +1 -1
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/cli.py +68 -42
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/defaults.py +1 -1
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/importer/claude.py +15 -0
- lorekeep-0.4.0/src/lorekeep/integrations/claude_code.py +50 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/integrations/common.py +10 -7
- lorekeep-0.4.0/src/lorekeep/integrations/cursor.py +45 -0
- lorekeep-0.4.0/tests/conftest.py +57 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_agent_cli.py +6 -12
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_compile_cli.py +1 -2
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_import_claude.py +55 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_import_cursor.py +1 -3
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_init_chain.py +4 -6
- lorekeep-0.4.0/tests/test_session_hook.py +246 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_watch_e2e.py +3 -4
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_watch_sessions.py +0 -2
- lorekeep-0.3.0/.release-please-manifest.json +0 -3
- lorekeep-0.3.0/src/lorekeep/integrations/claude_code.py +0 -19
- lorekeep-0.3.0/src/lorekeep/integrations/cursor.py +0 -21
- lorekeep-0.3.0/tests/conftest.py +0 -7
- {lorekeep-0.3.0 → lorekeep-0.4.0}/.commitlintrc.json +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/.github/workflows/auto-merge-release-please.yml +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/.github/workflows/ci.yml +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/.github/workflows/lint-commits.yml +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/.github/workflows/release-please.yml +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/.gitignore +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/.lorekeep/config.yaml.example +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/.lorekeep/schema.json +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/.pre-commit-config.yaml +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/CLAUDE.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/LICENSE +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/README.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/SECURITY.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/cover.jpeg +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/README.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/architecture/agent.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/architecture/data-model.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/architecture/journal.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/architecture/overview.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/architecture/permission.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/architecture/pipeline.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/architecture/serve-mcp.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/architecture/temporal.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/guides/backup.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/guides/compile.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/guides/data-home.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/guides/import.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/superpowers/plans/2026-06-26-lorekeep-dotdir-backup.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/docs/superpowers/specs/2026-06-26-lorekeep-dotdir-layout-design.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/release-please-config.json +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/scripts/check-conventional-commit.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/agent.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/backup.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/compile/__init__.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/compile/extract.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/compile/ingest.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/compile/providers.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/compile/resolve.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/compile/writer.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/config.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/eval/__init__.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/eval/construction.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/eval/gold.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/eval/retrieval.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/facts_io.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/importer/__init__.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/importer/cursor.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/integrations/__init__.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/integrations/codex.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/integrations/detect.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/integrations/opencode.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/journal.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/mcp_server.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/models.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/paths.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/perm/__init__.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/perm/ns.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/pipeline.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/schema_io.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/store/__init__.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/store/fts.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/src/lorekeep/store/graph.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/fixtures/claude-session/memory/MEMORY.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/fixtures/claude-session/memory/lorekeep-design.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/fixtures/claude-session/memory/lorekeep-project-state.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/fixtures/claude-session/test.jsonl +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/fixtures/gold/payments.facts.jsonl +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/fixtures/raw/backend/payments.md +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/fixtures/retrieval/questions.json +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/fixtures/schema.json +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_agent_detect.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_backup.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_backup_cli.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_config.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_construction_eval.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_defaults.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_determinism.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_doctor_cli.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_eval_cli.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_extract.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_fts.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_graph_store.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_ingest.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_init_cli.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_integrations.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_mcp_add_cli.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_mcp_reload.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_mcp_server.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_mcp_write_tools.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_merge_journals.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_models.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_paths.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_perm.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_pipeline.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_providers.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_resolve.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_retrieval_eval.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_schema_io.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_serve_cli.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_smoke.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/tests/test_writer.py +0 -0
- {lorekeep-0.3.0 → lorekeep-0.4.0}/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,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.0](https://github.com/manhhailua/lorekeep/compare/v0.3.1...v0.4.0) (2026-06-28)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* trigger memory import on Claude session end via SessionEnd hook ([#64](https://github.com/manhhailua/lorekeep/issues/64)) ([5914663](https://github.com/manhhailua/lorekeep/commit/5914663a5c02f76e1085a1f9d663066979396d77))
|
|
9
|
+
|
|
10
|
+
## [0.3.1](https://github.com/manhhailua/lorekeep/compare/v0.3.0...v0.3.1) (2026-06-28)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* add manifest dedup to deep-mode session import ([#61](https://github.com/manhhailua/lorekeep/issues/61)) ([a9a074f](https://github.com/manhhailua/lorekeep/commit/a9a074f901c9a8f8e4cd2e3f906fdf4be7a773d6))
|
|
16
|
+
|
|
3
17
|
## [0.3.0](https://github.com/manhhailua/lorekeep/compare/v0.2.1...v0.3.0) (2026-06-28)
|
|
4
18
|
|
|
5
19
|
|
|
@@ -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,40 +39,71 @@ 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."""
|
|
73
63
|
typer.echo(f"lorekeep {__version__}")
|
|
74
64
|
|
|
75
65
|
|
|
66
|
+
@app.command(hidden=True)
|
|
67
|
+
def hook() -> None:
|
|
68
|
+
"""SessionEnd hook: quick-import Claude memory files to raw/.
|
|
69
|
+
|
|
70
|
+
Designed to be registered as a Claude Code SessionEnd hook.
|
|
71
|
+
Reads hook JSON from stdin (transcript_path), imports memory files,
|
|
72
|
+
and lets the daemon pick up the raw/ change for compile.
|
|
73
|
+
"""
|
|
74
|
+
import sys
|
|
75
|
+
|
|
76
|
+
session_dir = None
|
|
77
|
+
try:
|
|
78
|
+
raw_stdin = sys.stdin.read()
|
|
79
|
+
if raw_stdin.strip():
|
|
80
|
+
data = json.loads(raw_stdin)
|
|
81
|
+
transcript_path = data.get("transcript_path", "")
|
|
82
|
+
if transcript_path:
|
|
83
|
+
session_dir = Path(transcript_path).expanduser().parent
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
if session_dir is None:
|
|
88
|
+
try:
|
|
89
|
+
from lorekeep.importer.claude import find_current_session
|
|
90
|
+
session_dir = find_current_session()
|
|
91
|
+
except Exception:
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
if session_dir is None or not (session_dir / "memory").is_dir():
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
p = resolve_paths()
|
|
98
|
+
try:
|
|
99
|
+
from lorekeep.importer.claude import import_memories
|
|
100
|
+
written = import_memories(session_dir, p["raw"], "claude-memory")
|
|
101
|
+
if written:
|
|
102
|
+
typer.echo(f"lorekeep: imported {len(written)} memory file(s)")
|
|
103
|
+
except Exception:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
|
|
76
107
|
@app.command()
|
|
77
108
|
def compile() -> None:
|
|
78
109
|
"""Compile raw/ into graph/facts.jsonl."""
|
|
@@ -249,6 +280,7 @@ def mcp_add(
|
|
|
249
280
|
p = resolve_paths()
|
|
250
281
|
config = load_config(p["config"])
|
|
251
282
|
command, args = resolve_command(config.install_source)
|
|
283
|
+
hook_cmd, hook_args = resolve_command(config.install_source, ["hook"])
|
|
252
284
|
|
|
253
285
|
target = Path.cwd() if scope == "project" else Path.home()
|
|
254
286
|
writers = _agent_writers()
|
|
@@ -257,6 +289,9 @@ def mcp_add(
|
|
|
257
289
|
raise typer.Exit(code=1)
|
|
258
290
|
written = writers[agent].write_config(target, command, args, ns)
|
|
259
291
|
typer.echo(f"wrote {agent} config -> {written}")
|
|
292
|
+
if agent in ("claude", "cursor") and hasattr(writers[agent], "write_hook"):
|
|
293
|
+
hook_path = writers[agent].write_hook(target, hook_cmd, hook_args)
|
|
294
|
+
typer.echo(f"wrote session-end hook -> {hook_path}")
|
|
260
295
|
typer.echo("\n" + agent_memory_snippet())
|
|
261
296
|
|
|
262
297
|
|
|
@@ -462,6 +497,7 @@ def _auto_wire_agents(p: dict, ns: str) -> None:
|
|
|
462
497
|
|
|
463
498
|
config = load_config(p["config"])
|
|
464
499
|
command, args = resolve_command(config.install_source)
|
|
500
|
+
hook_cmd, hook_args = resolve_command(config.install_source, ["hook"])
|
|
465
501
|
|
|
466
502
|
writers = _agent_writers()
|
|
467
503
|
target = Path.cwd()
|
|
@@ -474,6 +510,9 @@ def _auto_wire_agents(p: dict, ns: str) -> None:
|
|
|
474
510
|
try:
|
|
475
511
|
written = writer.write_config(target, command, args, ns)
|
|
476
512
|
typer.echo(f" wired {agent_name} -> {written}")
|
|
513
|
+
if agent_name in ("claude", "cursor") and hasattr(writer, "write_hook"):
|
|
514
|
+
hook_path = writer.write_hook(target, hook_cmd, hook_args)
|
|
515
|
+
typer.echo(f" hooked {agent_name} session-end -> {hook_path}")
|
|
477
516
|
except Exception as exc:
|
|
478
517
|
typer.echo(f" {agent_name}: failed ({exc})")
|
|
479
518
|
|
|
@@ -512,11 +551,7 @@ def _auto_import_and_compile(p: dict) -> None:
|
|
|
512
551
|
schema = load_schema(p["schema"])
|
|
513
552
|
config = load_config(p["config"])
|
|
514
553
|
|
|
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
|
-
)
|
|
554
|
+
has_key = _has_provider(config)
|
|
520
555
|
|
|
521
556
|
if not has_key:
|
|
522
557
|
if imported:
|
|
@@ -640,15 +675,6 @@ def import_cmd(
|
|
|
640
675
|
p = resolve_paths()
|
|
641
676
|
config = load_config(p["config"])
|
|
642
677
|
|
|
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
678
|
# --- Cursor: global composer conversations, deep-only ------------------
|
|
653
679
|
if from_source == "cursor":
|
|
654
680
|
if quick:
|
|
@@ -673,7 +699,7 @@ def import_cmd(
|
|
|
673
699
|
ns = session_ns or "cursor-session"
|
|
674
700
|
result = import_cursor(
|
|
675
701
|
raw_root=p["raw"], db_path=db, namespace=ns,
|
|
676
|
-
provider=
|
|
702
|
+
provider=_make_import_provider(config), dry_run=dry_run,
|
|
677
703
|
)
|
|
678
704
|
ses_count = len(result.get("session", []))
|
|
679
705
|
if dry_run:
|
|
@@ -697,7 +723,7 @@ def import_cmd(
|
|
|
697
723
|
"Run Claude Code in this project first.")
|
|
698
724
|
raise typer.Exit(code=1)
|
|
699
725
|
|
|
700
|
-
provider = None if quick else
|
|
726
|
+
provider = None if quick else _make_import_provider(config)
|
|
701
727
|
|
|
702
728
|
from lorekeep.importer.claude import import_claude
|
|
703
729
|
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,50 @@
|
|
|
1
|
+
"""Claude Code MCP config writer (.mcp.json) + SessionEnd hook writer."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def write_config(target_dir: Path, command: str, args: list[str], ns: str | None) -> Path:
|
|
9
|
+
entry = {"command": command, "args": args}
|
|
10
|
+
if ns:
|
|
11
|
+
entry["env"] = {"LOREKEEP_NS": ns}
|
|
12
|
+
path = Path(target_dir) / ".mcp.json"
|
|
13
|
+
existing = {}
|
|
14
|
+
if path.exists():
|
|
15
|
+
existing = json.loads(path.read_text())
|
|
16
|
+
servers = existing.get("mcpServers", {})
|
|
17
|
+
servers["lorekeep"] = entry
|
|
18
|
+
path.write_text(json.dumps({"mcpServers": servers}, indent=2))
|
|
19
|
+
return path
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def write_hook(target_dir: Path, command: str, args: list[str]) -> Path:
|
|
23
|
+
"""Write a SessionEnd hook to .claude/settings.json.
|
|
24
|
+
|
|
25
|
+
The hook calls ``lorekeep hook`` which quick-imports Claude memory
|
|
26
|
+
files into raw/ on session end. The daemon (if running) picks up the
|
|
27
|
+
raw/ change and compiles automatically.
|
|
28
|
+
"""
|
|
29
|
+
settings_path = Path(target_dir) / ".claude" / "settings.json"
|
|
30
|
+
existing = {}
|
|
31
|
+
if settings_path.exists():
|
|
32
|
+
try:
|
|
33
|
+
existing = json.loads(settings_path.read_text())
|
|
34
|
+
except (json.JSONDecodeError, OSError):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
hooks = existing.get("hooks", {})
|
|
38
|
+
hooks["SessionEnd"] = [{
|
|
39
|
+
"hooks": [{
|
|
40
|
+
"type": "command",
|
|
41
|
+
"command": command,
|
|
42
|
+
"args": args,
|
|
43
|
+
"timeout": 30,
|
|
44
|
+
}]
|
|
45
|
+
}]
|
|
46
|
+
existing["hooks"] = hooks
|
|
47
|
+
|
|
48
|
+
settings_path.parent.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
settings_path.write_text(json.dumps(existing, indent=2))
|
|
50
|
+
return settings_path
|
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
def resolve_command(install_source: str | None) -> tuple[str, list[str]]:
|
|
6
|
-
"""Return (command, args) to launch
|
|
7
|
-
|
|
5
|
+
def resolve_command(install_source: str | None, subcommand: list[str] | None = None) -> tuple[str, list[str]]:
|
|
6
|
+
"""Return (command, args) to launch a lorekeep subcommand.
|
|
7
|
+
|
|
8
|
+
Defaults to ``serve --transport stdio``. Pass ``subcommand`` for others
|
|
9
|
+
(e.g. ``["hook"]``).
|
|
10
|
+
"""
|
|
11
|
+
cmd_args = subcommand or ["serve", "--transport", "stdio"]
|
|
8
12
|
if not install_source or install_source == "pypi":
|
|
9
|
-
return ("uvx", ["lorekeep", *
|
|
13
|
+
return ("uvx", ["lorekeep", *cmd_args])
|
|
10
14
|
if install_source == "local":
|
|
11
|
-
return ("lorekeep",
|
|
12
|
-
|
|
13
|
-
return ("uvx", ["--from", install_source, "lorekeep", *serve_args])
|
|
15
|
+
return ("lorekeep", cmd_args)
|
|
16
|
+
return ("uvx", ["--from", install_source, "lorekeep", *cmd_args])
|
|
14
17
|
|
|
15
18
|
|
|
16
19
|
def agent_memory_snippet() -> str:
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Cursor MCP config writer (.cursor/mcp.json) + sessionEnd hook writer."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def write_config(target_dir: Path, command: str, args: list[str], ns: str | None) -> Path:
|
|
9
|
+
entry = {"command": command, "args": args}
|
|
10
|
+
if ns:
|
|
11
|
+
entry["env"] = {"LOREKEEP_NS": ns}
|
|
12
|
+
d = Path(target_dir) / ".cursor"
|
|
13
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
14
|
+
path = d / "mcp.json"
|
|
15
|
+
existing = {}
|
|
16
|
+
if path.exists():
|
|
17
|
+
existing = json.loads(path.read_text())
|
|
18
|
+
servers = existing.get("mcpServers", {})
|
|
19
|
+
servers["lorekeep"] = entry
|
|
20
|
+
path.write_text(json.dumps({"mcpServers": servers}, indent=2))
|
|
21
|
+
return path
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def write_hook(target_dir: Path, command: str, args: list[str]) -> Path:
|
|
25
|
+
"""Write a sessionEnd hook to .cursor/hooks.json.
|
|
26
|
+
|
|
27
|
+
Cursor's hook format uses a single command string (shell form).
|
|
28
|
+
"""
|
|
29
|
+
cmd_str = " ".join([command, *args])
|
|
30
|
+
path = Path(target_dir) / ".cursor" / "hooks.json"
|
|
31
|
+
existing = {}
|
|
32
|
+
if path.exists():
|
|
33
|
+
try:
|
|
34
|
+
existing = json.loads(path.read_text())
|
|
35
|
+
except (json.JSONDecodeError, OSError):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
hooks = existing.get("hooks", {})
|
|
39
|
+
hooks["sessionEnd"] = [{"command": cmd_str, "timeout": 30}]
|
|
40
|
+
existing["version"] = existing.get("version", 1)
|
|
41
|
+
existing["hooks"] = hooks
|
|
42
|
+
|
|
43
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
path.write_text(json.dumps(existing, indent=2))
|
|
45
|
+
return path
|
|
@@ -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)
|