lorekeep 0.2.1__tar.gz → 0.3.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.3.0/.release-please-manifest.json +3 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/CHANGELOG.md +8 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/PKG-INFO +1 -1
- {lorekeep-0.2.1 → lorekeep-0.3.0}/pyproject.toml +1 -1
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/__init__.py +1 -1
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/cli.py +101 -5
- lorekeep-0.3.0/tests/test_init_chain.py +178 -0
- lorekeep-0.2.1/.release-please-manifest.json +0 -3
- {lorekeep-0.2.1 → lorekeep-0.3.0}/.commitlintrc.json +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/.github/workflows/auto-merge-release-please.yml +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/.github/workflows/ci.yml +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/.github/workflows/lint-commits.yml +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/.github/workflows/release-please.yml +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/.gitignore +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/.lorekeep/config.yaml.example +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/.lorekeep/schema.json +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/.pre-commit-config.yaml +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/AGENTS.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/CLAUDE.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/LICENSE +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/README.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/SECURITY.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/cover.jpeg +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/README.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/architecture/agent.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/architecture/data-model.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/architecture/evaluation.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/architecture/journal.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/architecture/overview.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/architecture/permission.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/architecture/pipeline.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/architecture/serve-mcp.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/architecture/temporal.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/guides/backup.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/guides/compile.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/guides/data-home.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/guides/getting-started.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/guides/import.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/guides/serve.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/superpowers/plans/2026-06-26-lorekeep-dotdir-backup.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/docs/superpowers/specs/2026-06-26-lorekeep-dotdir-layout-design.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/release-please-config.json +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/scripts/check-conventional-commit.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/agent.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/backup.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/compile/__init__.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/compile/extract.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/compile/ingest.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/compile/providers.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/compile/resolve.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/compile/writer.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/config.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/defaults.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/eval/__init__.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/eval/construction.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/eval/gold.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/eval/retrieval.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/facts_io.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/importer/__init__.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/importer/claude.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/importer/cursor.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/integrations/__init__.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/integrations/claude_code.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/integrations/codex.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/integrations/common.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/integrations/cursor.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/integrations/detect.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/integrations/opencode.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/journal.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/mcp_server.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/models.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/paths.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/perm/__init__.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/perm/ns.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/pipeline.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/schema_io.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/store/__init__.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/store/fts.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/src/lorekeep/store/graph.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/conftest.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/fixtures/claude-session/memory/MEMORY.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/fixtures/claude-session/memory/lorekeep-design.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/fixtures/claude-session/memory/lorekeep-project-state.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/fixtures/claude-session/test.jsonl +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/fixtures/gold/payments.facts.jsonl +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/fixtures/raw/backend/payments.md +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/fixtures/retrieval/questions.json +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/fixtures/schema.json +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_agent_cli.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_agent_detect.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_backup.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_backup_cli.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_compile_cli.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_config.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_construction_eval.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_defaults.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_determinism.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_doctor_cli.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_eval_cli.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_extract.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_fts.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_graph_store.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_import_claude.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_import_cursor.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_ingest.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_init_cli.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_integrations.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_mcp_add_cli.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_mcp_reload.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_mcp_server.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_mcp_write_tools.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_merge_journals.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_models.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_paths.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_perm.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_pipeline.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_providers.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_resolve.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_retrieval_eval.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_schema_io.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_serve_cli.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_smoke.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_watch_e2e.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_watch_sessions.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/tests/test_writer.py +0 -0
- {lorekeep-0.2.1 → lorekeep-0.3.0}/uv.lock +0 -0
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0](https://github.com/manhhailua/lorekeep/compare/v0.2.1...v0.3.0) (2026-06-28)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* zero-friction init — wire, import, compile, daemon in one command ([#55](https://github.com/manhhailua/lorekeep/issues/55)) ([26e27ad](https://github.com/manhhailua/lorekeep/commit/26e27ad70e9eae90d4ac490694b891eda01b563b))
|
|
9
|
+
|
|
3
10
|
## [0.2.1](https://github.com/manhhailua/lorekeep/compare/v0.2.0...v0.2.1) (2026-06-28)
|
|
4
11
|
|
|
5
12
|
|
|
6
13
|
### Bug Fixes
|
|
7
14
|
|
|
8
15
|
* repair daemon auto-compile provider construction and re-merge journals after standalone compile ([#48](https://github.com/manhhailua/lorekeep/issues/48)) ([fb05b29](https://github.com/manhhailua/lorekeep/commit/fb05b290c5c673cc36891790d62036b8b6f65c7e))
|
|
16
|
+
* resolve doc contradictions and false cadence claims ([#53](https://github.com/manhhailua/lorekeep/issues/53)) ([10fc794](https://github.com/manhhailua/lorekeep/commit/10fc79406b02d1fa5735274b3dd89c62653ed3ba))
|
|
9
17
|
|
|
10
18
|
|
|
11
19
|
### Documentation
|
|
@@ -319,8 +319,12 @@ def init(
|
|
|
319
319
|
False, "--yes", "-y",
|
|
320
320
|
help="Skip interactive prompts, use defaults",
|
|
321
321
|
),
|
|
322
|
+
watch: bool = typer.Option(
|
|
323
|
+
True, "--watch/--no-watch",
|
|
324
|
+
help="Start the daemon (agent watch) in background after setup",
|
|
325
|
+
),
|
|
322
326
|
) -> None:
|
|
323
|
-
"""Bootstrap the data home
|
|
327
|
+
"""Bootstrap the data home, wire agents, import sessions, compile, and start daemon."""
|
|
324
328
|
p = resolve_paths()
|
|
325
329
|
created = []
|
|
326
330
|
p["config"].parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -348,6 +352,7 @@ def init(
|
|
|
348
352
|
|
|
349
353
|
p["raw"].mkdir(parents=True, exist_ok=True)
|
|
350
354
|
p["out"].mkdir(parents=True, exist_ok=True)
|
|
355
|
+
p["pending"].mkdir(parents=True, exist_ok=True)
|
|
351
356
|
|
|
352
357
|
typer.echo(f"home ready: config={p['config']}")
|
|
353
358
|
typer.echo(f" schema={p['schema']} raw={p['raw']} graph={p['out']}")
|
|
@@ -368,12 +373,20 @@ def init(
|
|
|
368
373
|
(ns_dir / "about.md").write_text(about_md)
|
|
369
374
|
typer.echo(f" wrote: {ns_dir / 'about.md'}")
|
|
370
375
|
|
|
371
|
-
#
|
|
376
|
+
# --- One-click chain: wire → import → compile → daemon -----------------
|
|
372
377
|
if not config_existed:
|
|
373
378
|
_auto_wire_agents(p, ns)
|
|
374
|
-
|
|
375
|
-
typer.echo("
|
|
376
|
-
|
|
379
|
+
_auto_import_and_compile(p)
|
|
380
|
+
typer.echo("\nRestart your agent → lorekeep tools are available.")
|
|
381
|
+
|
|
382
|
+
# Daemon: start on fresh init or revive if dead (regardless of config_existed)
|
|
383
|
+
if watch and _is_interactive():
|
|
384
|
+
_start_daemon(p)
|
|
385
|
+
elif watch and not _is_interactive():
|
|
386
|
+
typer.echo("\n (skipped daemon start in non-interactive mode — run `lorekeep agent watch` manually)")
|
|
387
|
+
|
|
388
|
+
if config_existed:
|
|
389
|
+
typer.echo("\nAlready initialized.")
|
|
377
390
|
|
|
378
391
|
|
|
379
392
|
def _interactive_init(p: dict) -> tuple[str, str, str]:
|
|
@@ -478,6 +491,89 @@ def _agent_writers() -> dict:
|
|
|
478
491
|
}
|
|
479
492
|
|
|
480
493
|
|
|
494
|
+
def _auto_import_and_compile(p: dict) -> None:
|
|
495
|
+
"""Quick-import Claude memory files, then compile if provider is available."""
|
|
496
|
+
imported = 0
|
|
497
|
+
|
|
498
|
+
# --- Quick import: Claude memory files (zero LLM cost) ----------------
|
|
499
|
+
try:
|
|
500
|
+
from lorekeep.importer.claude import find_current_session, import_memories
|
|
501
|
+
session_dir = find_current_session()
|
|
502
|
+
if session_dir is not None and (session_dir / "memory").is_dir():
|
|
503
|
+
written = import_memories(session_dir, p["raw"], "claude-memory")
|
|
504
|
+
imported = len(written)
|
|
505
|
+
if imported:
|
|
506
|
+
typer.echo(f" imported {imported} memory file(s) from Claude session")
|
|
507
|
+
except Exception as exc:
|
|
508
|
+
if os.environ.get("LOREKEEP_DEBUG"):
|
|
509
|
+
typer.echo(f" import error: {exc}")
|
|
510
|
+
|
|
511
|
+
# --- Compile (if provider is usable) ----------------------------------
|
|
512
|
+
schema = load_schema(p["schema"])
|
|
513
|
+
config = load_config(p["config"])
|
|
514
|
+
|
|
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
|
+
)
|
|
520
|
+
|
|
521
|
+
if not has_key:
|
|
522
|
+
if imported:
|
|
523
|
+
typer.echo(" memory files imported to raw/ — compile pending (add API key to config.yaml)")
|
|
524
|
+
else:
|
|
525
|
+
typer.echo(" graph empty — run `lorekeep compile` after adding docs or importing sessions")
|
|
526
|
+
return
|
|
527
|
+
|
|
528
|
+
try:
|
|
529
|
+
provider = _make_provider(config)
|
|
530
|
+
manifest = compile_graph(
|
|
531
|
+
raw_root=p["raw"], out_dir=p["out"], schema=schema,
|
|
532
|
+
provider=provider, cache_path=p["cache"],
|
|
533
|
+
chunk_lines=config.compile.chunk_lines,
|
|
534
|
+
)
|
|
535
|
+
pending_dir = p.get("pending")
|
|
536
|
+
if pending_dir and pending_dir.exists():
|
|
537
|
+
_do_auto_resolve(p["out"], pending_dir)
|
|
538
|
+
typer.echo(f" compiled: {manifest.node_count} nodes, {manifest.edge_count} edges")
|
|
539
|
+
except Exception as exc:
|
|
540
|
+
typer.echo(f" compile skipped: {exc}")
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def _start_daemon(p: dict) -> None:
|
|
544
|
+
"""Start agent watch as a background process with PID + log files."""
|
|
545
|
+
import subprocess
|
|
546
|
+
import sys
|
|
547
|
+
|
|
548
|
+
pid_path = p["home"] / "agent.pid"
|
|
549
|
+
log_path = p["home"] / "agent.log"
|
|
550
|
+
|
|
551
|
+
# Check if already running
|
|
552
|
+
if pid_path.exists():
|
|
553
|
+
old_pid = pid_path.read_text().strip()
|
|
554
|
+
try:
|
|
555
|
+
os.kill(int(old_pid), 0)
|
|
556
|
+
typer.echo(f" daemon already running (pid={old_pid})")
|
|
557
|
+
return
|
|
558
|
+
except (ProcessLookupError, ValueError):
|
|
559
|
+
pass
|
|
560
|
+
|
|
561
|
+
cmd = [sys.executable, "-m", "lorekeep.cli", "agent", "watch", "--interval", "60"]
|
|
562
|
+
log_file = open(log_path, "a")
|
|
563
|
+
try:
|
|
564
|
+
proc = subprocess.Popen(
|
|
565
|
+
cmd,
|
|
566
|
+
stdout=log_file,
|
|
567
|
+
stderr=subprocess.STDOUT,
|
|
568
|
+
stdin=subprocess.DEVNULL,
|
|
569
|
+
start_new_session=True,
|
|
570
|
+
)
|
|
571
|
+
finally:
|
|
572
|
+
log_file.close()
|
|
573
|
+
pid_path.write_text(str(proc.pid))
|
|
574
|
+
typer.echo(f" daemon started (pid={proc.pid}, log={log_path})")
|
|
575
|
+
|
|
576
|
+
|
|
481
577
|
@app.command()
|
|
482
578
|
def backup(
|
|
483
579
|
init_remote: str = typer.Option(
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Tests for the zero-friction init chain: import → compile → daemon."""
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from unittest.mock import patch, MagicMock
|
|
5
|
+
|
|
6
|
+
from typer.testing import CliRunner
|
|
7
|
+
|
|
8
|
+
from lorekeep.cli import app
|
|
9
|
+
|
|
10
|
+
runner = CliRunner()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _setup_env(tmp_path: Path, monkeypatch, fake_provider=True):
|
|
14
|
+
home = tmp_path / "home"
|
|
15
|
+
project = tmp_path / "project"
|
|
16
|
+
fake_home = tmp_path / "fakehome"
|
|
17
|
+
project.mkdir()
|
|
18
|
+
fake_home.mkdir()
|
|
19
|
+
monkeypatch.setenv("LOREKEEP_HOME", str(home))
|
|
20
|
+
monkeypatch.setenv("HOME", str(fake_home))
|
|
21
|
+
monkeypatch.setenv("LOREKEEP_DEV", "0")
|
|
22
|
+
monkeypatch.chdir(project)
|
|
23
|
+
monkeypatch.setattr("pathlib.Path.home", lambda: fake_home)
|
|
24
|
+
monkeypatch.setattr("lorekeep.integrations.detect.shutil.which", lambda _: None)
|
|
25
|
+
monkeypatch.delenv("OPENCODE", raising=False)
|
|
26
|
+
monkeypatch.delenv("CLAUDECODE", raising=False)
|
|
27
|
+
if fake_provider:
|
|
28
|
+
monkeypatch.setenv("LOREKEEP_PROVIDER", "fake")
|
|
29
|
+
return home, project
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ── Init chains into compile ──────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_init_compiles_with_fake_provider(tmp_path: Path, monkeypatch):
|
|
36
|
+
"""init --yes with LOREKEEP_PROVIDER=fake should auto-compile facts.jsonl."""
|
|
37
|
+
home, project = _setup_env(tmp_path, monkeypatch)
|
|
38
|
+
|
|
39
|
+
result = runner.invoke(app, ["init", "--yes", "--no-watch"])
|
|
40
|
+
assert result.exit_code == 0, result.stdout
|
|
41
|
+
|
|
42
|
+
facts = home / "graph" / "facts.jsonl"
|
|
43
|
+
assert facts.exists(), f"facts.jsonl not created: {result.stdout}"
|
|
44
|
+
lines = [json.loads(l) for l in facts.read_text().strip().splitlines() if l.strip()]
|
|
45
|
+
assert any(f["id"] == "svc:payments-api" for f in lines)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_init_skips_compile_without_api_key(tmp_path: Path, monkeypatch):
|
|
49
|
+
"""init --yes without provider key should skip compile gracefully."""
|
|
50
|
+
home, project = _setup_env(tmp_path, monkeypatch, fake_provider=False)
|
|
51
|
+
|
|
52
|
+
result = runner.invoke(app, ["init", "--yes", "--no-watch"])
|
|
53
|
+
assert result.exit_code == 0, result.stdout
|
|
54
|
+
|
|
55
|
+
assert not (home / "graph" / "facts.jsonl").exists()
|
|
56
|
+
assert "compile" in result.stdout.lower() or "empty" in result.stdout.lower()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ── Init auto-imports Claude memory ───────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_init_imports_claude_memory(tmp_path: Path, monkeypatch):
|
|
63
|
+
"""init should quick-import Claude memory files if a session is found."""
|
|
64
|
+
home, project = _setup_env(tmp_path, monkeypatch)
|
|
65
|
+
|
|
66
|
+
fake_session = tmp_path / "session"
|
|
67
|
+
(fake_session / "memory").mkdir(parents=True)
|
|
68
|
+
(fake_session / "memory" / "note.md").write_text("# Important\nSession knowledge.\n")
|
|
69
|
+
|
|
70
|
+
monkeypatch.setattr(
|
|
71
|
+
"lorekeep.importer.claude.find_current_session",
|
|
72
|
+
lambda: fake_session,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
result = runner.invoke(app, ["init", "--yes", "--no-watch"])
|
|
76
|
+
assert result.exit_code == 0, result.stdout
|
|
77
|
+
|
|
78
|
+
imported = home / "raw" / "claude-memory" / "note.md"
|
|
79
|
+
assert imported.exists()
|
|
80
|
+
assert "imported" in result.stdout.lower()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ── Init starts daemon ────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_init_starts_daemon(tmp_path: Path, monkeypatch):
|
|
87
|
+
"""init --watch in interactive mode should spawn agent watch."""
|
|
88
|
+
home, project = _setup_env(tmp_path, monkeypatch)
|
|
89
|
+
monkeypatch.setattr("lorekeep.cli._is_interactive", lambda: True)
|
|
90
|
+
|
|
91
|
+
mock_proc = MagicMock()
|
|
92
|
+
mock_proc.pid = 99999
|
|
93
|
+
mock_popen = MagicMock(return_value=mock_proc)
|
|
94
|
+
monkeypatch.setattr("subprocess.Popen", mock_popen)
|
|
95
|
+
|
|
96
|
+
result = runner.invoke(app, ["init", "--yes"])
|
|
97
|
+
assert result.exit_code == 0, result.stdout
|
|
98
|
+
|
|
99
|
+
mock_popen.assert_called_once()
|
|
100
|
+
cmd = mock_popen.call_args[0][0]
|
|
101
|
+
assert "agent" in cmd
|
|
102
|
+
assert "watch" in cmd
|
|
103
|
+
|
|
104
|
+
pid_file = home / "agent.pid"
|
|
105
|
+
assert pid_file.exists()
|
|
106
|
+
assert pid_file.read_text().strip() == "99999"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_init_skips_daemon_in_noninteractive(tmp_path: Path, monkeypatch):
|
|
110
|
+
"""init --watch in non-interactive mode should not spawn daemon."""
|
|
111
|
+
home, project = _setup_env(tmp_path, monkeypatch)
|
|
112
|
+
# _is_interactive() returns False in CliRunner by default
|
|
113
|
+
|
|
114
|
+
mock_popen = MagicMock()
|
|
115
|
+
monkeypatch.setattr("subprocess.Popen", mock_popen)
|
|
116
|
+
|
|
117
|
+
result = runner.invoke(app, ["init", "--yes"])
|
|
118
|
+
assert result.exit_code == 0, result.stdout
|
|
119
|
+
|
|
120
|
+
mock_popen.assert_not_called()
|
|
121
|
+
assert "non-interactive" in result.stdout.lower()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_init_rerun_revives_dead_daemon(tmp_path: Path, monkeypatch):
|
|
125
|
+
"""Re-running init should start daemon if it's not running, even if config exists."""
|
|
126
|
+
home, project = _setup_env(tmp_path, monkeypatch)
|
|
127
|
+
monkeypatch.setattr("lorekeep.cli._is_interactive", lambda: True)
|
|
128
|
+
|
|
129
|
+
# First init creates config
|
|
130
|
+
runner.invoke(app, ["init", "--yes", "--no-watch"])
|
|
131
|
+
|
|
132
|
+
# Simulate dead daemon: stale PID file pointing to nonexistent process
|
|
133
|
+
pid_path = home / "agent.pid"
|
|
134
|
+
pid_path.write_text("999999")
|
|
135
|
+
|
|
136
|
+
mock_proc = MagicMock()
|
|
137
|
+
mock_proc.pid = 88888
|
|
138
|
+
mock_popen = MagicMock(return_value=mock_proc)
|
|
139
|
+
monkeypatch.setattr("subprocess.Popen", mock_popen)
|
|
140
|
+
|
|
141
|
+
result = runner.invoke(app, ["init", "--yes"])
|
|
142
|
+
assert result.exit_code == 0, result.stdout
|
|
143
|
+
|
|
144
|
+
mock_popen.assert_called_once()
|
|
145
|
+
assert pid_path.read_text().strip() == "88888"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# ── Init creates pending/ dir ─────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_init_creates_pending_dir(tmp_path: Path, monkeypatch):
|
|
152
|
+
"""init should create the pending/ directory for journals."""
|
|
153
|
+
home, project = _setup_env(tmp_path, monkeypatch)
|
|
154
|
+
|
|
155
|
+
result = runner.invoke(app, ["init", "--yes", "--no-watch"])
|
|
156
|
+
assert result.exit_code == 0, result.stdout
|
|
157
|
+
|
|
158
|
+
assert (home / "pending").is_dir()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# ── Init idempotent re-run ────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def test_init_rerun_skips_chain(tmp_path: Path, monkeypatch):
|
|
165
|
+
"""Re-running init on existing config should not re-chain."""
|
|
166
|
+
home, project = _setup_env(tmp_path, monkeypatch)
|
|
167
|
+
|
|
168
|
+
result1 = runner.invoke(app, ["init", "--yes", "--no-watch"])
|
|
169
|
+
assert result1.exit_code == 0
|
|
170
|
+
|
|
171
|
+
mock_popen = MagicMock()
|
|
172
|
+
monkeypatch.setattr("subprocess.Popen", mock_popen)
|
|
173
|
+
|
|
174
|
+
result2 = runner.invoke(app, ["init", "--yes"])
|
|
175
|
+
assert result2.exit_code == 0, result2.stdout
|
|
176
|
+
|
|
177
|
+
mock_popen.assert_not_called()
|
|
178
|
+
assert "already initialized" in result2.stdout.lower()
|
|
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.2.1 → lorekeep-0.3.0}/docs/superpowers/plans/2026-06-26-lorekeep-dotdir-backup.md
RENAMED
|
File without changes
|
{lorekeep-0.2.1 → lorekeep-0.3.0}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lorekeep-0.2.1 → lorekeep-0.3.0}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|