lorewiki 0.3.0__tar.gz → 0.3.2__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.
- {lorewiki-0.3.0 → lorewiki-0.3.2}/.gitignore +4 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/PKG-INFO +1 -1
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/__init__.py +1 -1
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/data/skill_template/SKILL.md +44 -1
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/utils/skill_installer.py +55 -19
- {lorewiki-0.3.0 → lorewiki-0.3.2}/pyproject.toml +1 -1
- {lorewiki-0.3.0 → lorewiki-0.3.2}/LICENSE +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/README.md +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/__main__.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/cli/__init__.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/cli/add.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/cli/apps.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/cli/commands.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/cli/config_cmds.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/cli/helpers.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/cli/install_cmd.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/cli/topic_cmds.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/config.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/db/__init__.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/db/connection.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/db/models.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/db/schema.sql +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/indexer/__init__.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/indexer/chunker.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/indexer/cleaning.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/indexer/indexer.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/indexer/parser.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/llm/__init__.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/llm/client.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/llm/generator.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/py.typed +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/retriever/__init__.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/retriever/base.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/retriever/bm25.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/retriever/fusion.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/retriever/hierarchy.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/retriever/search.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/retriever/vector.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/topic.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/utils/__init__.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/utils/logger.py +0 -0
- {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/utils/topic_shared.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lorewiki
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Local-first knowledge base for LLM-assisted coding, with hybrid retrieval (BM25 + hierarchy + optional vector) over SQLite FTS5.
|
|
5
5
|
Project-URL: Documentation, https://github.com/JochenYang/Lore-wiki
|
|
6
6
|
Project-URL: Source, https://github.com/JochenYang/Lore-wiki
|
|
@@ -145,6 +145,47 @@ descriptions, the command exits with code 1 and prints a panel that
|
|
|
145
145
|
tells the user to name the topic by hand. The agent should fall
|
|
146
146
|
back to asking the user explicitly in that case.
|
|
147
147
|
|
|
148
|
+
### Topic Selection Before Writing (the "ask, don't surprise" rule)
|
|
149
|
+
|
|
150
|
+
If the user says "save this to my wiki" / "记到 wiki 里" / "メモして"
|
|
151
|
+
**without naming a topic**, the LLM must **not** auto-create a topic
|
|
152
|
+
on the user's behalf. The wiki is a long-lived knowledge asset —
|
|
153
|
+
silently dropping a doc into a freshly-invented `general` vault is
|
|
154
|
+
surprising and pollutes the user's mental model.
|
|
155
|
+
|
|
156
|
+
Follow this tree strictly, and **ask the user at the leaves**:
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
User: "save X to my wiki" (no topic named)
|
|
160
|
+
│
|
|
161
|
+
├── 1. Is there an active topic? (~/lorewiki/current exists)
|
|
162
|
+
│ ├── YES → use it. No question needed.
|
|
163
|
+
│ └── NO → go to step 2.
|
|
164
|
+
│
|
|
165
|
+
├── 2. How many topics exist? (lorewiki topic list --raw)
|
|
166
|
+
│ ├── 1 → "I see one topic '<name>'. Use it?" (one confirmation)
|
|
167
|
+
│ ├── 2-3 → list them, ask "which one?" (free text or pick)
|
|
168
|
+
│ └── 4+ → ask "which one?" (don't auto-pick)
|
|
169
|
+
│
|
|
170
|
+
└── 3. Zero topics exist
|
|
171
|
+
├── Run `lorewiki topic suggest "<X summary>"` for English
|
|
172
|
+
│ descriptions, OR have the LLM propose 1-3 names itself
|
|
173
|
+
│ for CJK descriptions.
|
|
174
|
+
└── Show candidates to the user:
|
|
175
|
+
"I can create topic 'wechat-mp', 'wechat-miniprogram',
|
|
176
|
+
or 'mp'. Which? (Or pick your own — lowercase, digits,
|
|
177
|
+
hyphens, 1-64 chars.)"
|
|
178
|
+
User picks → lorewiki topic create <name>
|
|
179
|
+
User rejects all → ask for a name by hand.
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Why this matters**: a user who has spent a year curating
|
|
183
|
+
`~/lorewiki/topics/react/`, `~/lorewiki/topics/api/`, etc., does
|
|
184
|
+
**not** want their work silently merged into a `general` vault just
|
|
185
|
+
because they said "wiki" without naming a topic. The cost of asking
|
|
186
|
+
once is one round-trip; the cost of surprising the user is having
|
|
187
|
+
to undo the merger later.
|
|
188
|
+
|
|
148
189
|
**Path resolution priority** (later wins):
|
|
149
190
|
1. `--topic` flag
|
|
150
191
|
2. `LOREWIKI_TOPIC` env var
|
|
@@ -546,7 +587,9 @@ lorewiki clean --path "<WIKI>" [--dry-run] [--no-backup]
|
|
|
546
587
|
| "what's in the wiki / what modules exist" | `lorewiki tree` |
|
|
547
588
|
| "does the wiki explain how X is implemented?" | `lorewiki ask "how is X implemented?" --raw` |
|
|
548
589
|
| "what modules does the wiki have?" | `lorewiki tree --depth 2` |
|
|
549
|
-
| "remember this / save this / take a note about X"
|
|
590
|
+
| "remember this / save this / take a note about X" (topic named) | `lorewiki topic use <name> && lorewiki add --title "X" --body "..."` |
|
|
591
|
+
| "remember this / save this to my wiki" (no topic) | Follow the **Topic Selection Tree** (see "ask, don't surprise") |
|
|
592
|
+
| "save this URL / doc to my wiki" | Fetch content (webfetch / Read tool) → `lorewiki add --title X --body "<fetched>"` |
|
|
550
593
|
| "store these N pages / scrape this site" | Drop the dir under the topic, then `lorewiki index` |
|
|
551
594
|
| "import my entire <some-tool> docs folder" | `lorewiki topic create <name> --source <docs-folder>` |
|
|
552
595
|
| "why does this query return no results?" | Re-run with `--mode bm25` to inspect scores |
|
|
@@ -29,6 +29,7 @@ Differences from ``skills/install.py``:
|
|
|
29
29
|
"""
|
|
30
30
|
from __future__ import annotations
|
|
31
31
|
|
|
32
|
+
import contextlib
|
|
32
33
|
import os
|
|
33
34
|
import re
|
|
34
35
|
import sys
|
|
@@ -120,35 +121,35 @@ TOOLS: Final[tuple[Tool, ...]] = (
|
|
|
120
121
|
Tool(
|
|
121
122
|
id="opencode",
|
|
122
123
|
label="opencode",
|
|
123
|
-
primary="$XDG_CONFIG_HOME/opencode/skills/<name
|
|
124
|
+
primary="$XDG_CONFIG_HOME/opencode/skills/<name>/SKILL.md",
|
|
124
125
|
),
|
|
125
126
|
Tool(
|
|
126
127
|
id="claude",
|
|
127
128
|
label="Claude Code",
|
|
128
|
-
primary="~/.claude/skills/<name
|
|
129
|
+
primary="~/.claude/skills/<name>/SKILL.md",
|
|
129
130
|
),
|
|
130
131
|
Tool(
|
|
131
132
|
id="codex",
|
|
132
133
|
label="Codex CLI",
|
|
133
|
-
primary="$CODEX_HOME/skills/<name
|
|
134
|
+
primary="$CODEX_HOME/skills/<name>/SKILL.md",
|
|
134
135
|
),
|
|
135
136
|
Tool(
|
|
136
137
|
id="cursor",
|
|
137
138
|
label="Cursor",
|
|
138
|
-
primary="~/.cursor/skills/<name
|
|
139
|
+
primary="~/.cursor/skills/<name>/SKILL.md",
|
|
139
140
|
# Cursor also auto-discovers ~/.agents/skills/ (interop path).
|
|
140
|
-
aliases=("~/.agents/skills/<name
|
|
141
|
+
aliases=("~/.agents/skills/<name>/SKILL.md",),
|
|
141
142
|
),
|
|
142
143
|
Tool(
|
|
143
144
|
id="gemini",
|
|
144
145
|
label="Gemini CLI",
|
|
145
|
-
primary="$GEMINI_HOME/skills/<name
|
|
146
|
-
aliases=("~/.agents/skills/<name
|
|
146
|
+
primary="$GEMINI_HOME/skills/<name>/SKILL.md",
|
|
147
|
+
aliases=("~/.agents/skills/<name>/SKILL.md",),
|
|
147
148
|
),
|
|
148
149
|
Tool(
|
|
149
150
|
id="antigravity",
|
|
150
151
|
label="Google Antigravity",
|
|
151
|
-
primary="~/.gemini/antigravity/skills/<name
|
|
152
|
+
primary="~/.gemini/antigravity/skills/<name>/SKILL.md",
|
|
152
153
|
),
|
|
153
154
|
)
|
|
154
155
|
|
|
@@ -159,15 +160,15 @@ TOOLS: Final[tuple[Tool, ...]] = (
|
|
|
159
160
|
|
|
160
161
|
|
|
161
162
|
def _parent_exists(path: Path) -> bool:
|
|
162
|
-
"""True if the tool's *config root* exists (
|
|
163
|
+
"""True if the tool's *config root* exists (three levels above the SKILL.md).
|
|
163
164
|
|
|
164
|
-
For ``~/.config/opencode/skills/lorewiki`` we check
|
|
165
|
+
For ``~/.config/opencode/skills/lorewiki/SKILL.md`` we check
|
|
165
166
|
``~/.config/opencode/`` — the directory the tool itself creates
|
|
166
167
|
on first launch. Going further up would land on ``$HOME`` /
|
|
167
168
|
``C:\\`` which always exist and would mark every tool as
|
|
168
169
|
installed.
|
|
169
170
|
"""
|
|
170
|
-
return path.parent.parent.exists()
|
|
171
|
+
return path.parent.parent.parent.exists()
|
|
171
172
|
|
|
172
173
|
|
|
173
174
|
def detect_installed_tools() -> list[Tool]:
|
|
@@ -278,6 +279,18 @@ def install_skill(
|
|
|
278
279
|
``write_text``. We don't want one locked path to abort the
|
|
279
280
|
rest of the install — we surface the failure as a ``[skip]``
|
|
280
281
|
line and move on.
|
|
282
|
+
|
|
283
|
+
The install target is the file ``<root>/<name>/SKILL.md``
|
|
284
|
+
(not the directory itself). The path templates in :data:`TOOLS`
|
|
285
|
+
already include the ``/SKILL.md`` suffix. An earlier version
|
|
286
|
+
of this function wrote to the directory path, which on Windows
|
|
287
|
+
surfaces as ``PermissionError`` when the dir already exists,
|
|
288
|
+
and on POSIX silently creates a *file* at the dir path —
|
|
289
|
+
leaving an orphan single-file ``~/.agents/skills/<name>``
|
|
290
|
+
(31821 bytes) instead of the expected ``<root>/<name>/SKILL.md``
|
|
291
|
+
file. The current implementation also cleans up the orphan
|
|
292
|
+
single-file layout (a file where the dir should be) at the
|
|
293
|
+
alias path so the new ``mkdir`` doesn't fail.
|
|
281
294
|
"""
|
|
282
295
|
if skill_text is None:
|
|
283
296
|
skill_text = _read_skill_template()
|
|
@@ -299,6 +312,21 @@ def install_skill(
|
|
|
299
312
|
alias = tool.resolve(alias_tmpl)
|
|
300
313
|
if alias == primary:
|
|
301
314
|
continue
|
|
315
|
+
# Clean up the legacy single-file layout (e.g.
|
|
316
|
+
# ``~/.agents/skills/lorewiki`` as a file, not a dir) so
|
|
317
|
+
# the new ``mkdir`` doesn't fail because a parent path
|
|
318
|
+
# component is a file.
|
|
319
|
+
if alias.parent.exists() and not alias.parent.is_dir():
|
|
320
|
+
try:
|
|
321
|
+
alias.parent.unlink()
|
|
322
|
+
actions.append(
|
|
323
|
+
f"[rm] {alias.parent} (legacy single-file layout)"
|
|
324
|
+
)
|
|
325
|
+
except OSError as exc:
|
|
326
|
+
actions.append(
|
|
327
|
+
f"[skip] {alias.parent} (legacy unlink failed: {exc})"
|
|
328
|
+
)
|
|
329
|
+
continue
|
|
302
330
|
alias.parent.mkdir(parents=True, exist_ok=True)
|
|
303
331
|
if alias.exists() and not overwrite:
|
|
304
332
|
actions.append(
|
|
@@ -322,19 +350,27 @@ def uninstall_skill(tool: Tool) -> list[str]:
|
|
|
322
350
|
Each target is wrapped in its own ``try/except OSError`` for the
|
|
323
351
|
same reason as :func:`install_skill`: a locked directory or
|
|
324
352
|
read-only mount should not abort the rest of the uninstall.
|
|
353
|
+
|
|
354
|
+
Also handles the legacy single-file layout (e.g. an alias at
|
|
355
|
+
``~/.agents/skills/<name>`` written as a file by 0.2.5-0.2.9)
|
|
356
|
+
by unlinking that file too, so ``lorewiki install --uninstall``
|
|
357
|
+
leaves a clean slate.
|
|
325
358
|
"""
|
|
326
359
|
actions: list[str] = []
|
|
327
360
|
for tmpl in (tool.primary, *tool.aliases):
|
|
328
361
|
target = tool.resolve(tmpl)
|
|
329
|
-
if
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
actions.append(f"[skip] {target} (unlink failed: {exc})")
|
|
336
|
-
else:
|
|
362
|
+
if target.exists() or target.is_symlink():
|
|
363
|
+
try:
|
|
364
|
+
target.unlink()
|
|
365
|
+
except OSError as exc:
|
|
366
|
+
actions.append(f"[skip] {target} (unlink failed: {exc})")
|
|
367
|
+
continue
|
|
337
368
|
actions.append(f"[rm] {target}")
|
|
369
|
+
# Clean up the now-empty parent dir if we can.
|
|
370
|
+
with contextlib.suppress(OSError):
|
|
371
|
+
target.parent.rmdir()
|
|
372
|
+
else:
|
|
373
|
+
actions.append(f"[skip] {target} (not present)")
|
|
338
374
|
return actions
|
|
339
375
|
|
|
340
376
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lorewiki"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
description = "Local-first knowledge base for LLM-assisted coding, with hybrid retrieval (BM25 + hierarchy + optional vector) over SQLite FTS5."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
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
|