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.
Files changed (42) hide show
  1. {lorewiki-0.3.0 → lorewiki-0.3.2}/.gitignore +4 -0
  2. {lorewiki-0.3.0 → lorewiki-0.3.2}/PKG-INFO +1 -1
  3. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/__init__.py +1 -1
  4. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/data/skill_template/SKILL.md +44 -1
  5. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/utils/skill_installer.py +55 -19
  6. {lorewiki-0.3.0 → lorewiki-0.3.2}/pyproject.toml +1 -1
  7. {lorewiki-0.3.0 → lorewiki-0.3.2}/LICENSE +0 -0
  8. {lorewiki-0.3.0 → lorewiki-0.3.2}/README.md +0 -0
  9. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/__main__.py +0 -0
  10. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/cli/__init__.py +0 -0
  11. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/cli/add.py +0 -0
  12. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/cli/apps.py +0 -0
  13. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/cli/commands.py +0 -0
  14. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/cli/config_cmds.py +0 -0
  15. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/cli/helpers.py +0 -0
  16. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/cli/install_cmd.py +0 -0
  17. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/cli/topic_cmds.py +0 -0
  18. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/config.py +0 -0
  19. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/db/__init__.py +0 -0
  20. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/db/connection.py +0 -0
  21. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/db/models.py +0 -0
  22. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/db/schema.sql +0 -0
  23. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/indexer/__init__.py +0 -0
  24. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/indexer/chunker.py +0 -0
  25. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/indexer/cleaning.py +0 -0
  26. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/indexer/indexer.py +0 -0
  27. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/indexer/parser.py +0 -0
  28. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/llm/__init__.py +0 -0
  29. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/llm/client.py +0 -0
  30. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/llm/generator.py +0 -0
  31. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/py.typed +0 -0
  32. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/retriever/__init__.py +0 -0
  33. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/retriever/base.py +0 -0
  34. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/retriever/bm25.py +0 -0
  35. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/retriever/fusion.py +0 -0
  36. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/retriever/hierarchy.py +0 -0
  37. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/retriever/search.py +0 -0
  38. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/retriever/vector.py +0 -0
  39. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/topic.py +0 -0
  40. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/utils/__init__.py +0 -0
  41. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/utils/logger.py +0 -0
  42. {lorewiki-0.3.0 → lorewiki-0.3.2}/lorewiki/utils/topic_shared.py +0 -0
@@ -57,3 +57,7 @@ uv.lock
57
57
  !.env.example
58
58
  *.local.toml
59
59
 
60
+ # Sharing scratch — written for copy-paste to community channels,
61
+ # never intended to be part of the repo.
62
+ SHARE.md
63
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lorewiki
3
- Version: 0.3.0
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
@@ -1,4 +1,4 @@
1
1
  """LoreWiki - Local-first knowledge base for LLM-assisted coding."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.3.2"
4
4
  __all__ = ["__version__"]
@@ -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" | `lorewiki add --title "X" --body "..."` |
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 (two levels above the skill dir).
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 not (target.exists() or target.is_symlink()):
330
- actions.append(f"[skip] {target} (not present)")
331
- continue
332
- try:
333
- target.unlink()
334
- except OSError as exc:
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.0"
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