lociaction 0.1.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.
- lociaction-0.1.0/.gitignore +28 -0
- lociaction-0.1.0/CHANGELOG.md +154 -0
- lociaction-0.1.0/LICENSE +21 -0
- lociaction-0.1.0/Makefile +25 -0
- lociaction-0.1.0/PKG-INFO +225 -0
- lociaction-0.1.0/README.ja.md +186 -0
- lociaction-0.1.0/README.md +187 -0
- lociaction-0.1.0/assets/icon.svg +38 -0
- lociaction-0.1.0/pyproject.toml +105 -0
- lociaction-0.1.0/src/lociaction/__init__.py +3 -0
- lociaction-0.1.0/src/lociaction/__main__.py +5 -0
- lociaction-0.1.0/src/lociaction/adapters/harness/__init__.py +0 -0
- lociaction-0.1.0/src/lociaction/adapters/harness/claude.py +222 -0
- lociaction-0.1.0/src/lociaction/adapters/harness/codex.py +134 -0
- lociaction-0.1.0/src/lociaction/adapters/harness/grok.py +191 -0
- lociaction-0.1.0/src/lociaction/adapters/harness/hook_writers.py +172 -0
- lociaction-0.1.0/src/lociaction/adapters/harness/hooks.py +342 -0
- lociaction-0.1.0/src/lociaction/adapters/harness/jsonl_source.py +193 -0
- lociaction-0.1.0/src/lociaction/adapters/harness/lifecycle.py +51 -0
- lociaction-0.1.0/src/lociaction/adapters/harness/omp_pi.py +383 -0
- lociaction-0.1.0/src/lociaction/adapters/harness/opencode.py +133 -0
- lociaction-0.1.0/src/lociaction/adapters/harness/registry.py +66 -0
- lociaction-0.1.0/src/lociaction/adapters/harness/unified_diff.py +64 -0
- lociaction-0.1.0/src/lociaction/adapters/model/__init__.py +29 -0
- lociaction-0.1.0/src/lociaction/adapters/model/registry.py +419 -0
- lociaction-0.1.0/src/lociaction/adapters/model/types.py +43 -0
- lociaction-0.1.0/src/lociaction/cli/__init__.py +601 -0
- lociaction-0.1.0/src/lociaction/cli/distill_cmd.py +205 -0
- lociaction-0.1.0/src/lociaction/cli/eval_cmd.py +207 -0
- lociaction-0.1.0/src/lociaction/cli/gc_cmd.py +36 -0
- lociaction-0.1.0/src/lociaction/cli/hook_cmd.py +55 -0
- lociaction-0.1.0/src/lociaction/cli/index_cmd.py +240 -0
- lociaction-0.1.0/src/lociaction/cli/prime_cmd.py +144 -0
- lociaction-0.1.0/src/lociaction/cli/recall_cmd.py +258 -0
- lociaction-0.1.0/src/lociaction/cli/search_cmd.py +468 -0
- lociaction-0.1.0/src/lociaction/cli/server_cmd.py +132 -0
- lociaction-0.1.0/src/lociaction/cli/show_cmd.py +168 -0
- lociaction-0.1.0/src/lociaction/cli/status_cmd.py +121 -0
- lociaction-0.1.0/src/lociaction/code_touches.py +374 -0
- lociaction-0.1.0/src/lociaction/config.py +195 -0
- lociaction-0.1.0/src/lociaction/context_lookup.py +498 -0
- lociaction-0.1.0/src/lociaction/core/__init__.py +1 -0
- lociaction-0.1.0/src/lociaction/core/ingest.py +455 -0
- lociaction-0.1.0/src/lociaction/core/models.py +87 -0
- lociaction-0.1.0/src/lociaction/core/ports.py +34 -0
- lociaction-0.1.0/src/lociaction/db.py +1455 -0
- lociaction-0.1.0/src/lociaction/distiller.py +502 -0
- lociaction-0.1.0/src/lociaction/embedder.py +188 -0
- lociaction-0.1.0/src/lociaction/embedder_server.py +208 -0
- lociaction-0.1.0/src/lociaction/eval/RESULTS.md +94 -0
- lociaction-0.1.0/src/lociaction/eval/__init__.py +0 -0
- lociaction-0.1.0/src/lociaction/eval/adapters/__init__.py +0 -0
- lociaction-0.1.0/src/lociaction/eval/adapters/base.py +24 -0
- lociaction-0.1.0/src/lociaction/eval/adapters/symbol.py +39 -0
- lociaction-0.1.0/src/lociaction/eval/baseline.json +16 -0
- lociaction-0.1.0/src/lociaction/eval/datasets/__init__.py +0 -0
- lociaction-0.1.0/src/lociaction/eval/datasets/schema.py +104 -0
- lociaction-0.1.0/src/lociaction/eval/datasets/symbol-recall.v0.jsonl +43 -0
- lociaction-0.1.0/src/lociaction/eval/fixture.py +114 -0
- lociaction-0.1.0/src/lociaction/eval/gate.py +63 -0
- lociaction-0.1.0/src/lociaction/eval/gen/__init__.py +0 -0
- lociaction-0.1.0/src/lociaction/eval/gen/gen_symbol_recall.py +285 -0
- lociaction-0.1.0/src/lociaction/eval/metrics.py +35 -0
- lociaction-0.1.0/src/lociaction/eval/report.py +88 -0
- lociaction-0.1.0/src/lociaction/eval/runner.py +62 -0
- lociaction-0.1.0/src/lociaction/file_renames.py +97 -0
- lociaction-0.1.0/src/lociaction/hooks.py +345 -0
- lociaction-0.1.0/src/lociaction/ignore.py +138 -0
- lociaction-0.1.0/src/lociaction/indexer.py +1416 -0
- lociaction-0.1.0/src/lociaction/json_utils.py +91 -0
- lociaction-0.1.0/src/lociaction/llm.py +816 -0
- lociaction-0.1.0/src/lociaction/maintenance.py +170 -0
- lociaction-0.1.0/src/lociaction/models.py +127 -0
- lociaction-0.1.0/src/lociaction/paths.py +180 -0
- lociaction-0.1.0/src/lociaction/py.typed +0 -0
- lociaction-0.1.0/src/lociaction/resolver.py +768 -0
- lociaction-0.1.0/src/lociaction/search.py +465 -0
- lociaction-0.1.0/src/lociaction/utils.py +24 -0
- lociaction-0.1.0/tests/__init__.py +0 -0
- lociaction-0.1.0/tests/conftest.py +75 -0
- lociaction-0.1.0/tests/fixtures/harness_logs/README.md +148 -0
- lociaction-0.1.0/tests/fixtures/harness_logs/claude.jsonl +7 -0
- lociaction-0.1.0/tests/fixtures/harness_logs/codex.jsonl +7 -0
- lociaction-0.1.0/tests/fixtures/harness_logs/grok.jsonl +13 -0
- lociaction-0.1.0/tests/fixtures/harness_logs/omp_pi.jsonl +21 -0
- lociaction-0.1.0/tests/fixtures/harness_logs/opencode.json +159 -0
- lociaction-0.1.0/tests/test_adapters_harness_claude.py +281 -0
- lociaction-0.1.0/tests/test_adapters_harness_codex.py +65 -0
- lociaction-0.1.0/tests/test_adapters_harness_grok.py +117 -0
- lociaction-0.1.0/tests/test_adapters_harness_hook_writers.py +286 -0
- lociaction-0.1.0/tests/test_adapters_harness_hooks_for.py +64 -0
- lociaction-0.1.0/tests/test_adapters_harness_lifecycle.py +59 -0
- lociaction-0.1.0/tests/test_adapters_harness_omp_pi.py +186 -0
- lociaction-0.1.0/tests/test_adapters_harness_opencode.py +58 -0
- lociaction-0.1.0/tests/test_adapters_harness_unified_diff.py +52 -0
- lociaction-0.1.0/tests/test_code_touches.py +467 -0
- lociaction-0.1.0/tests/test_codex_indexer.py +86 -0
- lociaction-0.1.0/tests/test_config.py +428 -0
- lociaction-0.1.0/tests/test_context_lookup.py +637 -0
- lociaction-0.1.0/tests/test_core_ingest.py +463 -0
- lociaction-0.1.0/tests/test_db.py +2329 -0
- lociaction-0.1.0/tests/test_distill_cmd.py +210 -0
- lociaction-0.1.0/tests/test_distiller.py +1125 -0
- lociaction-0.1.0/tests/test_embedder.py +169 -0
- lociaction-0.1.0/tests/test_embedder_server.py +214 -0
- lociaction-0.1.0/tests/test_eval_adapters.py +83 -0
- lociaction-0.1.0/tests/test_eval_cmd.py +188 -0
- lociaction-0.1.0/tests/test_eval_gate.py +246 -0
- lociaction-0.1.0/tests/test_eval_gen_symbol_recall.py +217 -0
- lociaction-0.1.0/tests/test_eval_metrics.py +50 -0
- lociaction-0.1.0/tests/test_eval_runner_report.py +149 -0
- lociaction-0.1.0/tests/test_eval_schema.py +65 -0
- lociaction-0.1.0/tests/test_file_renames.py +168 -0
- lociaction-0.1.0/tests/test_gc_cmd.py +213 -0
- lociaction-0.1.0/tests/test_git_hook_env_isolation.py +146 -0
- lociaction-0.1.0/tests/test_grok_indexer.py +76 -0
- lociaction-0.1.0/tests/test_harness_log_fixtures.py +47 -0
- lociaction-0.1.0/tests/test_ignore.py +82 -0
- lociaction-0.1.0/tests/test_index_cmd.py +276 -0
- lociaction-0.1.0/tests/test_indexer.py +1086 -0
- lociaction-0.1.0/tests/test_init.py +1095 -0
- lociaction-0.1.0/tests/test_json_utils.py +83 -0
- lociaction-0.1.0/tests/test_jsonl_source.py +154 -0
- lociaction-0.1.0/tests/test_llm.py +1410 -0
- lociaction-0.1.0/tests/test_model_registry.py +595 -0
- lociaction-0.1.0/tests/test_omp_pi_indexer.py +162 -0
- lociaction-0.1.0/tests/test_opencode_indexer.py +254 -0
- lociaction-0.1.0/tests/test_opencode_ingest_robustness.py +672 -0
- lociaction-0.1.0/tests/test_paths.py +302 -0
- lociaction-0.1.0/tests/test_prime_cmd.py +159 -0
- lociaction-0.1.0/tests/test_recall_cmd.py +346 -0
- lociaction-0.1.0/tests/test_resolve_symbol.py +265 -0
- lociaction-0.1.0/tests/test_resolver.py +580 -0
- lociaction-0.1.0/tests/test_search_cmd.py +560 -0
- lociaction-0.1.0/tests/test_search_phase2.py +597 -0
- lociaction-0.1.0/tests/test_security.py +200 -0
- lociaction-0.1.0/tests/test_server_cmd.py +170 -0
- lociaction-0.1.0/tests/test_sessions.py +66 -0
- lociaction-0.1.0/tests/test_show_dump.py +296 -0
- lociaction-0.1.0/tests/test_status_hook.py +1004 -0
- lociaction-0.1.0/tests/test_utils.py +36 -0
- lociaction-0.1.0/tests/test_version.py +20 -0
- lociaction-0.1.0/uv.lock +1427 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
|
|
8
|
+
# Virtual environment
|
|
9
|
+
.venv/
|
|
10
|
+
|
|
11
|
+
# Test & lint caches
|
|
12
|
+
.pytest_cache/
|
|
13
|
+
.ruff_cache/
|
|
14
|
+
|
|
15
|
+
# Tool data (local only)
|
|
16
|
+
.lociaction/
|
|
17
|
+
.logosyncx/
|
|
18
|
+
|
|
19
|
+
# Claude Code local settings
|
|
20
|
+
.claude/
|
|
21
|
+
|
|
22
|
+
# Internal docs (local only, not part of the public OSS repo)
|
|
23
|
+
docs/internal/
|
|
24
|
+
docs/design/
|
|
25
|
+
.strata/
|
|
26
|
+
|
|
27
|
+
# OS
|
|
28
|
+
.DS_Store
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `loci eval gate` (issue #37) is a CI regression gate for symbol-recall.
|
|
7
|
+
It builds a tiny synthetic git+`code_edges` fixture (no network, no
|
|
8
|
+
embeddings, no dogfood corpus) and fails if MRR@10 drops more than an
|
|
9
|
+
absolute 0.01 against committed `src/lociaction/eval/baseline.json`.
|
|
10
|
+
Keyword-recall (BM25/HNSW/RRF) remains out of scope.
|
|
11
|
+
|
|
12
|
+
- `loci status` now surfaces the most recent distill per-row failure
|
|
13
|
+
(`exchange_id`, message, timestamp) from `meta` when one has been
|
|
14
|
+
recorded; the field/section is omitted when nothing failed.
|
|
15
|
+
|
|
16
|
+
- `loci recall --file X --branch Y --json` (issue #33) is a session-start
|
|
17
|
+
warmup that merges code-anchored `context` lookup with `search_combined`
|
|
18
|
+
into one deduplicated response (`exchange_core` / `specific_context` /
|
|
19
|
+
`verbatim_ref`). `--file` and `--branch` are independent AND filters.
|
|
20
|
+
Ranking applies an opt-in exponential recency decay
|
|
21
|
+
(`search_combined(..., recency_half_life_days=)`, default half-life 14 days
|
|
22
|
+
on `loci recall` only; existing `search()`/`context()` ranking is unchanged)
|
|
23
|
+
using `code_edges.ts` with `conversations.started_at` as fallback.
|
|
24
|
+
|
|
25
|
+
- `loci gc` (issue #30) snapshots the database, removes only orphaned
|
|
26
|
+
palace/vector and exchange/session records, retains bounded `.bak` archives,
|
|
27
|
+
and compacts the database with `VACUUM`.
|
|
28
|
+
|
|
29
|
+
- `loci hook install`/`uninstall --harness omp-pi|opencode|grok` now write real
|
|
30
|
+
native hooks (issue #40): `OmpPiHooks`/`OpenCodeHooks` generate a marker-owned
|
|
31
|
+
`~/.omp/agent/extensions/lociaction.ts` / `~/.config/opencode/plugins/lociaction.ts`
|
|
32
|
+
plugin file (`DedicatedFileWriter`), and `GrokHooks` merges into a dedicated
|
|
33
|
+
`~/.grok/hooks/lociaction.json` (`MergedJsonHookWriter`, shared with the new
|
|
34
|
+
`CodexHooks`). All three previously always failed via `FallbackHooks`, which
|
|
35
|
+
remains the safety net for unrecognized harnesses.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
- Database schema v14 removes unused `vec_exchanges`, obsolete
|
|
39
|
+
`code_touches.symbol_name`/`resolved_by`, and duplicate `symbols` storage.
|
|
40
|
+
Existing live legacy symbol relations migrate to `code_symbols` plus
|
|
41
|
+
`code_edges` before the old table is dropped.
|
|
42
|
+
|
|
43
|
+
- Lifecycle event → loci command mapping (`Stop`→`index`, `SessionStart`→
|
|
44
|
+
`server start`/`distill`/`prime`, compact→`prime`) is now a single source of
|
|
45
|
+
truth: `lociaction.adapters.harness.lifecycle.lifecycle_commands(harness,
|
|
46
|
+
batch_limit)`. `ClaudeHooks`/`CodexHooks`/`GrokHooks`/`OmpPiHooks`/
|
|
47
|
+
`OpenCodeHooks` all derive their commands from it instead of re-deriving
|
|
48
|
+
the mapping per harness (`lociaction.hooks.install_hooks`/`uninstall_hooks`
|
|
49
|
+
keep their Claude-specific JSON-merge/idempotency logic, only the command
|
|
50
|
+
strings themselves are now sourced from the shared helper). One observable
|
|
51
|
+
side effect: Claude's `Stop` hook now runs `loci index --harness claude`
|
|
52
|
+
instead of the previous bare `loci index` (which implicitly swept every
|
|
53
|
+
detected harness on each Claude turn), matching the scoping Codex already had.
|
|
54
|
+
- `DedicatedFileWriter` uninstall only ever deletes files carrying its own
|
|
55
|
+
`LOCIACTION_HOOK_MARKER`; files without the marker (other tools' extensions/
|
|
56
|
+
plugins sharing the same auto-discovered directory) are left untouched on
|
|
57
|
+
both install (no clobber) and uninstall (no delete).
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- `loci search`'s KNN→filter ordering and branch matching (issue #18):
|
|
62
|
+
- `search_hnsw_palace` cut the sqlite-vec KNN candidate pool to exactly
|
|
63
|
+
`limit` *before* applying the `min_exchanges`/`branch` filters, so a
|
|
64
|
+
branch- or activity-filtered query could silently drop to zero results
|
|
65
|
+
even when relevant matches existed just outside the initial top-K. The
|
|
66
|
+
candidate pool is now widened adaptively: it starts at `limit * 5`, and
|
|
67
|
+
if filtering leaves fewer than `limit` results while unexplored candidates
|
|
68
|
+
remain in `vec_palace`, the pool doubles and the query retries (bounded by
|
|
69
|
+
a `2000`-candidate hard cap so worst-case ANN cost stays finite even under
|
|
70
|
+
a highly selective filter). The final result count is still enforced with
|
|
71
|
+
an outer `LIMIT`.
|
|
72
|
+
- `search_bm25`/`search_hnsw_palace`'s `branch` filter interpolated the
|
|
73
|
+
user-supplied branch string into a `LIKE '%...%'` pattern unescaped, so
|
|
74
|
+
literal `%`/`_` in the query were interpreted as SQL wildcards (e.g.
|
|
75
|
+
`main` incorrectly matching `maintenance`/`feat/main-x`). Wildcard
|
|
76
|
+
characters are now escaped (new `lociaction.utils.escape_like`) and the
|
|
77
|
+
clause carries an explicit `ESCAPE '\\'`; substring matching on
|
|
78
|
+
non-wildcard input is unchanged.
|
|
79
|
+
- Embedding server lifecycle races (issue #16): `loci server start` now serializes
|
|
80
|
+
the check→spawn→ready-wait sequence behind a process-wide `server.lock`
|
|
81
|
+
(`fcntl.flock`), eliminating double-spawn/orphaning under concurrent sessions.
|
|
82
|
+
`run_server` pings an existing socket before binding and refuses to clobber a
|
|
83
|
+
live server. `loci server status` is now strictly read-only and never deletes
|
|
84
|
+
a busy-but-unresponsive socket. `Embedder` serializes `model.encode` calls
|
|
85
|
+
across threads (`SentenceTransformer` inference is not thread-safe). The
|
|
86
|
+
embedder server now removes its PID file on idle-timeout/stop, not just the
|
|
87
|
+
socket.
|
|
88
|
+
|
|
89
|
+
## [0.3.0] - 2026-06-12
|
|
90
|
+
|
|
91
|
+
### Added
|
|
92
|
+
|
|
93
|
+
- Branch linking: exchanges are linked to their git branch at index time.
|
|
94
|
+
- `loci search "query" --branch NAME` — branch-filtered semantic search.
|
|
95
|
+
- `loci context --branch NAME` — reverse lookup from a git branch to past conversations (includes undistilled exchanges).
|
|
96
|
+
- `loci context --full` flag; the default output is now lighter.
|
|
97
|
+
- `loci hook uninstall` — remove lociaction hooks from `settings.json`.
|
|
98
|
+
- SQLite hardening: WAL mode, `busy_timeout`, and a `user_version`-based migration framework.
|
|
99
|
+
- Distillation transactions with `distill_status` and version tracking, plus new indexes.
|
|
100
|
+
|
|
101
|
+
### Changed
|
|
102
|
+
|
|
103
|
+
- `loci prime` output rewritten around agent-action triggers with concrete examples.
|
|
104
|
+
- Distillation uses a flock-based lock; embeddings are serialized with `tobytes`.
|
|
105
|
+
- Hook registration writes `settings.json` atomically.
|
|
106
|
+
- Indexing is incremental and reports config errors explicitly.
|
|
107
|
+
|
|
108
|
+
### Fixed
|
|
109
|
+
|
|
110
|
+
- Silent data loss in the distillation pipeline; code reverse-lookup works again.
|
|
111
|
+
- Embedding server can no longer double-start; socket protocol hardened and connection leaks fixed.
|
|
112
|
+
- Ply coordinate drift and WAL sidecar file permissions.
|
|
113
|
+
- `loci prime` exits silently when `.lociaction/` is absent; resolving `.lociaction/` from a parent directory now notifies on stderr.
|
|
114
|
+
|
|
115
|
+
## [0.2.0] - 2026-04-21
|
|
116
|
+
|
|
117
|
+
### Added
|
|
118
|
+
|
|
119
|
+
- `loci init --no-hooks` flag to skip automatic Claude Code hook registration.
|
|
120
|
+
- `EmbedderSetupError` exception for environment-level embedder failures (distinguishes them from per-row errors).
|
|
121
|
+
- SVG banner at the top of the README (`assets/banner.svg`, generated via Freeze).
|
|
122
|
+
- `scripts/generate-banner.sh` to regenerate the banner from the live CLI output.
|
|
123
|
+
|
|
124
|
+
### Changed
|
|
125
|
+
|
|
126
|
+
- `loci init` now registers Claude Code hooks automatically at the end of setup — previously required a separate `loci hook install` step. Use `--no-hooks` to opt out.
|
|
127
|
+
- Interactive prompts re-prompt on invalid input instead of silently falling back to a default. The "run distillation now?" prompt now accepts `y`/`n`/`yes`/`no` in addition to `1`/`2`.
|
|
128
|
+
- Custom exchange counts are range-validated (`1..total`) and custom `min_chars` requires `>= 0`.
|
|
129
|
+
- Startup banner uses the pagga half-block font with a blue vertical gradient.
|
|
130
|
+
|
|
131
|
+
### Fixed
|
|
132
|
+
|
|
133
|
+
- `loci init` cleans up `.lociaction/` automatically if the execution phase fails or is interrupted (`KeyboardInterrupt`), so re-running is safe.
|
|
134
|
+
- A single corrupt `.jsonl` no longer aborts the whole indexing loop — it logs a warning and continues.
|
|
135
|
+
- `git_root()` catches `FileNotFoundError` when the `git` binary is missing.
|
|
136
|
+
- `parse_exchanges` returns `[]` for missing files instead of raising.
|
|
137
|
+
- Distillation failures from `sentence_transformers` import issues (e.g. numpy/pyarrow binary mismatch) now print a single friendly message with remediation hints instead of a full traceback followed by per-row error spam.
|
|
138
|
+
|
|
139
|
+
## [0.1.0] - 2026-03-31
|
|
140
|
+
|
|
141
|
+
### Added
|
|
142
|
+
|
|
143
|
+
- `loci init` — initialize `.lociaction/` in project root
|
|
144
|
+
- `loci index` — parse `.jsonl` session logs, split into exchanges, embed with multilingual-e5-small
|
|
145
|
+
- `loci distill` — distill exchanges via `claude --print` into palace objects (exchange_core, specific_context, room_assignments)
|
|
146
|
+
- `loci search` — cross-layer RRF fusion search (BM25 verbatim + HNSW distilled)
|
|
147
|
+
- `loci context` — reverse lookup: code symbol → past conversations
|
|
148
|
+
- `loci show` — fetch verbatim exchange by ref
|
|
149
|
+
- `loci status` — show index state
|
|
150
|
+
- `loci server start/stop/status` — Unix socket embedding server for <0.2s search
|
|
151
|
+
- `loci hook install` — register Claude Code SessionStart/Stop hooks
|
|
152
|
+
- `config.toml` support for distill model and batch limit
|
|
153
|
+
- tree-sitter symbol resolution (Python, TypeScript, Go)
|
|
154
|
+
- Bilingual support (Japanese + English) via multilingual-e5-small
|
lociaction-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 senna-lang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
VENV := .venv/bin
|
|
2
|
+
|
|
3
|
+
# ruff: prefer the project venv, fall back to `uvx ruff` when no venv is present
|
|
4
|
+
RUFF := $(shell [ -x .venv/bin/ruff ] && echo .venv/bin/ruff || echo "uvx ruff")
|
|
5
|
+
|
|
6
|
+
.PHONY: test lint fmt typecheck check hooks
|
|
7
|
+
|
|
8
|
+
test:
|
|
9
|
+
$(VENV)/pytest tests/ -v
|
|
10
|
+
|
|
11
|
+
lint:
|
|
12
|
+
$(RUFF) check src/ tests/
|
|
13
|
+
|
|
14
|
+
fmt:
|
|
15
|
+
$(RUFF) format src/ tests/
|
|
16
|
+
|
|
17
|
+
typecheck:
|
|
18
|
+
$(VENV)/pyright src/
|
|
19
|
+
|
|
20
|
+
check: lint typecheck test
|
|
21
|
+
|
|
22
|
+
hooks:
|
|
23
|
+
@echo '#!/bin/sh\nmake check' > .git/hooks/pre-commit
|
|
24
|
+
@chmod +x .git/hooks/pre-commit
|
|
25
|
+
@echo "pre-commit hook installed: runs make check before every commit"
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: lociaction
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Memory palace for AI coding agents — index sessions, recall code context in <0.2s
|
|
5
|
+
Project-URL: Homepage, https://github.com/senna-lang/lociaction
|
|
6
|
+
Project-URL: Repository, https://github.com/senna-lang/lociaction
|
|
7
|
+
Project-URL: Issues, https://github.com/senna-lang/lociaction/issues
|
|
8
|
+
Author-email: senna-lang <senna-lang@users.noreply.github.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai,claude,cli,code-context,coding-agent,conversation-history,developer-tools,memory,semantic-search
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: sentence-transformers>=3.0.0
|
|
22
|
+
Requires-Dist: sqlite-vec>=0.1.0
|
|
23
|
+
Requires-Dist: tomli-w>=1.0.0
|
|
24
|
+
Requires-Dist: tree-sitter-c-sharp==0.23.0
|
|
25
|
+
Requires-Dist: tree-sitter-go<0.24.0,>=0.23.0
|
|
26
|
+
Requires-Dist: tree-sitter-java<0.24.0,>=0.23.0
|
|
27
|
+
Requires-Dist: tree-sitter-python<0.24.0,>=0.23.0
|
|
28
|
+
Requires-Dist: tree-sitter-ruby<0.24.0,>=0.23.0
|
|
29
|
+
Requires-Dist: tree-sitter-rust<0.23.3,>=0.23.0
|
|
30
|
+
Requires-Dist: tree-sitter-typescript<0.24.0,>=0.23.0
|
|
31
|
+
Requires-Dist: tree-sitter<0.24.0,>=0.23.0
|
|
32
|
+
Requires-Dist: typer[all]>=0.12.0
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pyright>=1.1.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# Lociaction
|
|
40
|
+
|
|
41
|
+
<p align="center">
|
|
42
|
+
<a href="https://github.com/senna-lang/lociaction/actions/workflows/ci.yml"><img src="https://github.com/senna-lang/lociaction/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
43
|
+
<a href="https://pypi.org/project/lociaction/"><img src="https://img.shields.io/pypi/v/lociaction" alt="PyPI"></a>
|
|
44
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
45
|
+
</p>
|
|
46
|
+
|
|
47
|
+
<p align="center">English · <a href="README.ja.md">日本語</a></p>
|
|
48
|
+
|
|
49
|
+
An AI coding agent recalls everything it has done through two recall primitives — `loci search` and `loci context` — plus `loci recall`, a session-start convenience command that fuses both. The agent reaches for the right call without hesitation, and restores past decisions, conversations, and exact code locations in under 0.2 seconds.
|
|
50
|
+
|
|
51
|
+
The CLI command `loci` is designed to be **called by the agent itself** — running `loci search "..." --json` from within a prompt. *(The name comes from the [Method of Loci](https://en.wikipedia.org/wiki/Method_of_loci) — the memory-palace technique. Under the hood, conversations are distilled into "palace objects"; see [How It Works](#how-it-works). The architecture extends the conversational memory model from [arXiv:2603.13017](https://arxiv.org/abs/2603.13017) for coding agents.)*
|
|
52
|
+
|
|
53
|
+
> **Harnesses:** Claude Code, Codex CLI, Oh My Pi, OpenCode, and Grok session logs are indexed into the same exchange, code-touch, symbol, search, context, and `show` contracts. Distillation is a separate, independent choice — `loci distill` runs the same configured client (`claude-cli`, `codex-cli`, `gemini-cli`, `grok-cli`, `opencode-cli`, `omp-cli`, a local Ollama model, or any OpenAI-compatible endpoint) against every undistilled exchange regardless of which harness produced it.
|
|
54
|
+
|
|
55
|
+
## Minimal Interface
|
|
56
|
+
|
|
57
|
+
The recall interface is built from two primitives, plus one composite:
|
|
58
|
+
|
|
59
|
+
- **`loci search "query"`** — semantic search over past conversations
|
|
60
|
+
- **`loci context`** — reverse lookup, by code symbol (`--symbol "name"`) or git branch (`--branch "name"`)
|
|
61
|
+
- tree-sitter symbol resolution (Python / TypeScript / Go / Rust / Java / C# / Ruby) lets agents understand implementation intent before editing
|
|
62
|
+
- `--branch "name"` recalls what was done and discussed on a specific git branch (also available as `loci search "query" --branch "name"`)
|
|
63
|
+
- **`loci recall --file PATH --branch NAME`** — session-start warmup: merges `context` + `search`, recency-ranked, with `--file`/`--branch` combinable as AND filters
|
|
64
|
+
|
|
65
|
+
That's deliberate. The user here is the agent, and an agent handed a 50-tool palette hesitates, mis-picks, and burns tokens just deciding which to call. With a surface this small — and no MCP tool schemas sitting resident in the context window — the agent reaches for the right call the first time, every time. *(When the full transcript is needed, `loci show "<exchange-id>"` expands a search result to its stored verbatim source.)*
|
|
66
|
+
|
|
67
|
+
Touching a symbol means recalling what was decided about it — `loci context` reverse-looks-up the exact code location, signature, and the conversation behind it.
|
|
68
|
+
|
|
69
|
+
## How It Works
|
|
70
|
+
|
|
71
|
+
1. **Index** — Splits agent session logs into exchanges (user utterance + agent response pairs) and indexes them with FTS5 for keyword search
|
|
72
|
+
2. **Distill** — The configured distill client (default `claude --print` with `claude-haiku-4-5`; see [Configuration](#configuration) for the other five CLI backends and local-model options) summarizes each exchange into a palace object: `exchange_core` (what was done), `specific_context` (concrete details), `room_assignments` (topic tags). tree-sitter resolves touched files to symbol level (function/class/method + file + line + signature)
|
|
73
|
+
3. **Search** — Cross-layer search fusing BM25 on verbatim text with HNSW on distilled embeddings via RRF
|
|
74
|
+
|
|
75
|
+
Raw conversations are not embedded — only the condensed distilled text is embedded with `multilingual-e5-small` (384-dim), balancing semantic search quality with embedding cost. The embedding model runs as a **Unix socket server**, keeping search latency **under 0.2 seconds** after the first load.
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pipx install lociaction
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Requires Python 3.11+.
|
|
84
|
+
|
|
85
|
+
## Quick Start
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Initialize in project root. This creates `.lociaction/` and adds the shared
|
|
89
|
+
# agent reminder to AGENTS.md.
|
|
90
|
+
loci init
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`loci init` creates the project-local database, writes the common `AGENTS.md` instruction section, and installs Claude Code hooks unless `--no-hooks` is supplied. Every other supported harness (Codex, Grok, Oh My Pi, OpenCode) also has full native lifecycle hook support — register it explicitly with `loci hook install --harness <name>`. If init fails partway through, `.lociaction/` is cleaned up automatically so re-running is safe.
|
|
94
|
+
|
|
95
|
+
When running `loci init`, if past session logs are detected, you'll be prompted with:
|
|
96
|
+
|
|
97
|
+
> [!IMPORTANT]
|
|
98
|
+
> When adopting this tool mid-project, a large number of exchanges may already exist. Distilling all of them consumes real tokens against whichever distill client you choose (Claude Haiku by default — see step 4 below for the other options). We recommend starting with `Skip all` or `Distill last 50`.
|
|
99
|
+
|
|
100
|
+
1. **Min chars threshold** — Minimum character filter applied at index time (default: 50). Shorter exchanges are skipped entirely, which also shrinks the pool of distillation candidates. Higher values exclude short conversations and reduce token usage; lower values include nearly everything. (Distillation applies a separate `min_chars` of 100 — see [Configuration](#configuration).)
|
|
101
|
+
2. **Handling existing exchanges** — Choose how much past history to distill:
|
|
102
|
+
- Skip all (no past session distillation)
|
|
103
|
+
- Distill last 50 (recent history only)
|
|
104
|
+
- Distill all (everything — high token cost)
|
|
105
|
+
- Custom (specify a number)
|
|
106
|
+
3. **Run distillation now?** — Accepts `1`/`2`/`y`/`n`/`yes`/`no`. Choose No to defer to the next session start.
|
|
107
|
+
|
|
108
|
+
`loci init` also asks once, regardless of past session history:
|
|
109
|
+
|
|
110
|
+
4. **Distill client selection** — If [`qwen2.5-7b-memory-distiller`](https://huggingface.co/sennaLLMLearner/qwen2.5-7b-memory-distiller) (a Qwen2.5-7B fine-tuned specifically for this task, SFT + ORPO on WildChat-1M) isn't pulled yet, `loci init` first offers to `ollama pull` it (~4.7GB, requires [Ollama](https://ollama.com)). It then lists every *Ready* distill client actually detected on the machine — `claude-cli`, `codex-cli`, `gemini-cli`, `grok-cli`, `opencode-cli`, `omp-cli`, plus the just-pulled `ollama-ft` — and prompts you to pick one (`claude-cli` is recommended by default when present). Only CLIs actually on `PATH` show up; none of this depends on which harness you're currently working in — see the [Configuration](#configuration) note on that. Pass `--no-local-distiller` to skip the Ollama pull offer, or `--distill-client <id>` to select non-interactively (exits with an error if that client isn't Ready — it never silently falls back to another one). If no client is Ready, distillation is left unconfigured; run `loci distill --setup` later.
|
|
111
|
+
|
|
112
|
+
Invalid input on any prompt re-prompts instead of silently falling back to a default.
|
|
113
|
+
|
|
114
|
+
## Agent Instructions
|
|
115
|
+
|
|
116
|
+
`loci init` installs the marker section (`<!-- BEGIN LOCIACTION -->...<!-- END LOCIACTION -->`) in **`AGENTS.md`**, the common instruction source for every supported harness. `loci prime` injects full command usage into a session context when native lifecycle support is available.
|
|
117
|
+
|
|
118
|
+
## CLI Commands
|
|
119
|
+
|
|
120
|
+
| Command | Description |
|
|
121
|
+
|---------|-------------|
|
|
122
|
+
| `loci init [--distill-client ID]` | Initialize `.lociaction/`, write common `AGENTS.md` instructions, and install Claude hooks (`--no-hooks` to skip, `--no-local-distiller` to skip the Ollama pull offer, `--distill-client` to pick the distill client non-interactively) |
|
|
123
|
+
| `loci index [--harness all\|claude\|codex\|opencode\|omp-pi\|grok]` | Index new session logs; the default indexes every detected harness |
|
|
124
|
+
| `loci distill [--limit N] [--setup]` | Distill undistilled exchanges via the configured client; `--setup` re-runs discover/select and saves the choice |
|
|
125
|
+
| `loci gc` | Snapshot `memory.db` to `.bak`, remove only orphaned palace/vector/session records, retain the current backup plus three archives, then run `VACUUM` |
|
|
126
|
+
| `loci search "query" --json` | Semantic search (agent-facing); add `--branch NAME` to filter by git branch |
|
|
127
|
+
| `loci context --symbol "name" --json` | Code symbol → past conversations (lightweight; add `--full` for verbatim text) |
|
|
128
|
+
| `loci context --branch "name" --json` | Git branch → past conversations (includes undistilled exchanges) |
|
|
129
|
+
| `loci recall --file PATH --branch NAME --json` | Session-start warmup: merge context+search, recency-ranked; `--file`/`--branch` AND-combinable |
|
|
130
|
+
| `loci show "<exchange-id>" --json` | Retrieve a stored exchange by its primary ID |
|
|
131
|
+
| `loci status` | Show index state |
|
|
132
|
+
| `loci prime` | Inject command usage into the session context |
|
|
133
|
+
| `loci server start/stop/status` | Embedding server management |
|
|
134
|
+
| `loci hook install --harness NAME` | Install native lifecycle hooks for one of the five supported harnesses |
|
|
135
|
+
| `loci hook uninstall --harness NAME` | Remove native lociaction lifecycle hooks |
|
|
136
|
+
|
|
137
|
+
## Harness Lifecycle
|
|
138
|
+
|
|
139
|
+
| Harness | Transcript source | Native lifecycle |
|
|
140
|
+
|---------|-------------------|-------------------|
|
|
141
|
+
| Claude Code | Project JSONL | `~/.claude/settings.json` |
|
|
142
|
+
| Codex CLI | Global rollout JSONL filtered by recorded cwd | `~/.codex/hooks.json` |
|
|
143
|
+
| Grok | Project streaming JSONL | `~/.grok/hooks/lociaction.json` |
|
|
144
|
+
| Oh My Pi | Project JSONL | `~/.omp/agent/extensions/lociaction.ts` |
|
|
145
|
+
| OpenCode | Local session SQLite | `~/.config/opencode/plugins/lociaction.ts` |
|
|
146
|
+
|
|
147
|
+
Every supported harness has full native lifecycle integration — no fallback/manual-instructions path exists for any of these five. Turn end maps to `loci index`, session start to `loci server start` + `loci distill` + `loci prime`. Compact handling differs slightly: Claude Code and Codex CLI fold compact into the same session-start trio (their matcher includes `compact`); Grok and OpenCode run only `loci prime` on compact; Oh My Pi has no compact-equivalent event to hook into. `loci hook install/uninstall --harness NAME` manages any of these and never touches another harness's settings.
|
|
148
|
+
|
|
149
|
+
## Search Output
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
[
|
|
153
|
+
{
|
|
154
|
+
"exchange_core": "Added connection pool with pool_size=5",
|
|
155
|
+
"specific_context": "pool_size=5, max_overflow=10",
|
|
156
|
+
"rooms": [
|
|
157
|
+
{ "room_type": "concept", "room_key": "db-pool", "room_label": "DB connection pooling" }
|
|
158
|
+
],
|
|
159
|
+
"symbols": [
|
|
160
|
+
{ "name": "create_pool", "file": "src/db.py", "line": 42, "signature": "def create_pool(...)" }
|
|
161
|
+
],
|
|
162
|
+
"verbatim_ref": "~/.claude/projects/.../session.jsonl:ply=42",
|
|
163
|
+
"git_branch": "feature/db-pool"
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Configuration
|
|
169
|
+
|
|
170
|
+
`.lociaction/config.toml` (generated by `loci init`):
|
|
171
|
+
|
|
172
|
+
```toml
|
|
173
|
+
[distill]
|
|
174
|
+
client = "claude-cli" # Distillation backend — see the full id list below
|
|
175
|
+
model = "claude-haiku-4-5-20251001" # Model for distillation (client-specific default if omitted)
|
|
176
|
+
batch_limit = 20 # Max distillations per hook run
|
|
177
|
+
min_chars = 100 # Skip distillation for exchanges shorter than this
|
|
178
|
+
|
|
179
|
+
[index]
|
|
180
|
+
min_chars = 50 # Skip indexing exchanges shorter than this
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
There are two `min_chars` settings: `[index] min_chars` controls what gets indexed at all, while `[distill] min_chars` further skips distillation (the LLM cost) for short exchanges that were already indexed.
|
|
184
|
+
|
|
185
|
+
`client` is independent of the harness you're actually working in — `loci distill` runs the one configured client against every undistilled exchange regardless of whether it came from Claude Code, Codex, Grok, OpenCode, or Oh My Pi (see [Harness Lifecycle](#harness-lifecycle)). Valid ids: `claude-cli`, `codex-cli`, `gemini-cli`, `grok-cli`, `opencode-cli`, `omp-cli`, `ollama-ft`, `openai-compat`. The legacy `provider = "claude" | "openai"` + `base_url` form is still read for backward compatibility, but `client` is what `loci init` and `loci distill --setup` write and is the recommended way to configure this by hand too.
|
|
186
|
+
|
|
187
|
+
### Distilling with a local LLM
|
|
188
|
+
|
|
189
|
+
Distillation is a small per-exchange structured-extraction task, so a local model is usually good enough. Any OpenAI-compatible endpoint (Ollama, LM Studio, llama.cpp-server, vLLM) works by setting `client = "openai-compat"` with `model` and `base_url` — no new dependencies, no API key (the `Authorization` header is never sent, so this is local-only):
|
|
190
|
+
|
|
191
|
+
```toml
|
|
192
|
+
[distill]
|
|
193
|
+
client = "openai-compat"
|
|
194
|
+
model = "qwen2.5:7b"
|
|
195
|
+
base_url = "http://localhost:11434/v1" # Ollama
|
|
196
|
+
# base_url = "http://localhost:1234/v1" # LM Studio
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`openai-compat` requires both `model` and `base_url` to be set — resolving fails otherwise. (If you're pointing this at Ollama's default port with the bundled fine-tuned model, use `client = "ollama-ft"` instead — it already knows the model and endpoint, see [`loci init`](#quick-start).)
|
|
200
|
+
|
|
201
|
+
`loci init` offers to set this up for you automatically with [`qwen2.5-7b-memory-distiller`](https://huggingface.co/sennaLLMLearner/qwen2.5-7b-memory-distiller), a model fine-tuned specifically for this task (see the prompt above) — no manual config needed if you accept it.
|
|
202
|
+
|
|
203
|
+
### Distilling with another coding-agent CLI
|
|
204
|
+
|
|
205
|
+
If you already have [Codex CLI](https://developers.openai.com/codex/cli), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Grok CLI](https://x.ai), [OpenCode](https://opencode.ai), or [Oh My Pi](https://github.com/can1357/oh-my-pi) installed and authenticated, any of them can run distillation instead of `claude --print` — no extra config beyond selecting the client:
|
|
206
|
+
|
|
207
|
+
```toml
|
|
208
|
+
[distill]
|
|
209
|
+
client = "codex-cli" # or "gemini-cli", "grok-cli", "opencode-cli", "omp-cli"
|
|
210
|
+
# model = "gpt-5-codex" # optional override; omit to use the CLI's own configured default
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`codex exec --output-schema` and `grok -p --json-schema` constrain the response to the palace-object schema directly (`structuredOutput` unwraps in one step for grok, no wrapper at all for codex). `gemini --prompt --output-format json`, `opencode run --format json`, and `omp -p --mode json` have no schema-constrained mode, so the palace object is parsed out of their free-text/event-stream output the same way `claude --print`'s `result` field is unwrapped. `loci distill --setup` detects all five automatically (PATH presence only, like `claude-cli`) and lists them alongside the other ready clients.
|
|
214
|
+
|
|
215
|
+
## Acknowledgments
|
|
216
|
+
|
|
217
|
+
The palace object model, room-based topic grouping, and BM25+HNSW fusion search are based on:
|
|
218
|
+
|
|
219
|
+
> *Structured Distillation for Personalized Agent Memory*
|
|
220
|
+
> (arXiv:2603.13017)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
## License
|
|
224
|
+
|
|
225
|
+
MIT
|