llm-wiki-tool 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.
- llm_wiki_tool-0.1.0/PKG-INFO +134 -0
- llm_wiki_tool-0.1.0/README.md +105 -0
- llm_wiki_tool-0.1.0/pyproject.toml +95 -0
- llm_wiki_tool-0.1.0/setup.cfg +4 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/__init__.py +1 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/archive.py +136 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/caps.py +21 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/claude.py +548 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/cli.py +486 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/config.py +215 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/convert.py +342 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/deep.py +158 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/doctor.py +493 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/errors.py +57 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/gate.py +276 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/gitops.py +313 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/images.py +108 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/index.py +108 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/ingest.py +787 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/ingest_run.py +481 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/init.py +289 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/intake.py +460 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/lint.py +803 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/maint.py +719 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/maint_job.py +360 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/maint_run.py +383 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/manifest.py +159 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/pages.py +213 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/plan.py +739 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/prompts/consolidate.md +54 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/prompts/edit.md +71 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/prompts/fix.md +55 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/prompts/lint_deep.md +31 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/prompts/maint_edit.md +61 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/prompts/plan.md +69 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/registry.py +120 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/schemas.py +296 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/screen.py +354 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/search.py +109 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/session.py +257 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/sheets.py +105 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/skill.py +75 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/status.py +106 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/templates/AGENTS.md +493 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/templates/CLAUDE.md +1 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/templates/claude-pointer.md +8 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/templates/gitignore +5 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/templates/pages/glossary.md +11 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/templates/pages/overview.md +8 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/templates/profiles/general.toml +29 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/templates/profiles/software.toml +39 -0
- llm_wiki_tool-0.1.0/src/llm_wiki/templates/skill-wiki-lookup.md +53 -0
- llm_wiki_tool-0.1.0/src/llm_wiki_tool.egg-info/PKG-INFO +134 -0
- llm_wiki_tool-0.1.0/src/llm_wiki_tool.egg-info/SOURCES.txt +86 -0
- llm_wiki_tool-0.1.0/src/llm_wiki_tool.egg-info/dependency_links.txt +1 -0
- llm_wiki_tool-0.1.0/src/llm_wiki_tool.egg-info/entry_points.txt +2 -0
- llm_wiki_tool-0.1.0/src/llm_wiki_tool.egg-info/requires.txt +19 -0
- llm_wiki_tool-0.1.0/src/llm_wiki_tool.egg-info/top_level.txt +1 -0
- llm_wiki_tool-0.1.0/tests/test_claude.py +958 -0
- llm_wiki_tool-0.1.0/tests/test_cli.py +90 -0
- llm_wiki_tool-0.1.0/tests/test_config.py +201 -0
- llm_wiki_tool-0.1.0/tests/test_consolidate.py +952 -0
- llm_wiki_tool-0.1.0/tests/test_convert.py +281 -0
- llm_wiki_tool-0.1.0/tests/test_deep.py +342 -0
- llm_wiki_tool-0.1.0/tests/test_doctor.py +592 -0
- llm_wiki_tool-0.1.0/tests/test_e2e_m1.py +70 -0
- llm_wiki_tool-0.1.0/tests/test_e2e_m3.py +225 -0
- llm_wiki_tool-0.1.0/tests/test_e2e_m5.py +281 -0
- llm_wiki_tool-0.1.0/tests/test_gate.py +58 -0
- llm_wiki_tool-0.1.0/tests/test_index.py +160 -0
- llm_wiki_tool-0.1.0/tests/test_ingest.py +837 -0
- llm_wiki_tool-0.1.0/tests/test_ingest_changes.py +831 -0
- llm_wiki_tool-0.1.0/tests/test_ingest_run.py +1387 -0
- llm_wiki_tool-0.1.0/tests/test_init.py +561 -0
- llm_wiki_tool-0.1.0/tests/test_intake.py +486 -0
- llm_wiki_tool-0.1.0/tests/test_lint.py +827 -0
- llm_wiki_tool-0.1.0/tests/test_llm_settings.py +121 -0
- llm_wiki_tool-0.1.0/tests/test_maint.py +1072 -0
- llm_wiki_tool-0.1.0/tests/test_maint_run.py +933 -0
- llm_wiki_tool-0.1.0/tests/test_manifest.py +183 -0
- llm_wiki_tool-0.1.0/tests/test_pages.py +213 -0
- llm_wiki_tool-0.1.0/tests/test_plan.py +772 -0
- llm_wiki_tool-0.1.0/tests/test_registry.py +158 -0
- llm_wiki_tool-0.1.0/tests/test_screen.py +340 -0
- llm_wiki_tool-0.1.0/tests/test_search.py +147 -0
- llm_wiki_tool-0.1.0/tests/test_session.py +197 -0
- llm_wiki_tool-0.1.0/tests/test_skill.py +182 -0
- llm_wiki_tool-0.1.0/tests/test_status.py +186 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: llm-wiki-tool
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI that keeps small, capped, LLM-maintained wikis for coding agents
|
|
5
|
+
Author: Leonardo Marziali
|
|
6
|
+
Project-URL: Homepage, https://github.com/leonardo-marziali/llm-wiki
|
|
7
|
+
Project-URL: Changelog, https://github.com/leonardo-marziali/llm-wiki/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: Issues, https://github.com/leonardo-marziali/llm-wiki/issues
|
|
9
|
+
Keywords: wiki,llm,claude,knowledge-base,coding-agents
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: typer~=0.27
|
|
13
|
+
Requires-Dist: pydantic~=2.14
|
|
14
|
+
Requires-Dist: pyyaml~=6.0
|
|
15
|
+
Requires-Dist: rapidfuzz~=3.14
|
|
16
|
+
Requires-Dist: platformdirs~=4.13
|
|
17
|
+
Requires-Dist: markitdown[docx,pdf,pptx,xlsx]~=0.1.9
|
|
18
|
+
Requires-Dist: python-pptx~=1.0
|
|
19
|
+
Requires-Dist: openpyxl~=3.1
|
|
20
|
+
Requires-Dist: pypdf~=6.21
|
|
21
|
+
Requires-Dist: pillow~=13.0
|
|
22
|
+
Requires-Dist: imagehash~=4.3
|
|
23
|
+
Provides-Extra: docling
|
|
24
|
+
Requires-Dist: docling~=2.142; extra == "docling"
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest~=9.1; extra == "dev"
|
|
27
|
+
Requires-Dist: python-docx~=1.2; extra == "dev"
|
|
28
|
+
Requires-Dist: pre-commit~=4.7; extra == "dev"
|
|
29
|
+
|
|
30
|
+
# llm-wiki
|
|
31
|
+
|
|
32
|
+
`wiki` keeps small, capped, LLM-maintained wikis that coding agents read. You
|
|
33
|
+
drop source files (documents, slides, sheets, PDFs, images, notes) into a wiki;
|
|
34
|
+
`wiki ingest` has headless Claude Code plan and write cited pages on a branch,
|
|
35
|
+
and you accept or reject the result at one gate. Agents read the wiki through
|
|
36
|
+
the read-only `wiki-lookup` skill, at no LLM cost.
|
|
37
|
+
|
|
38
|
+
## Requirements
|
|
39
|
+
|
|
40
|
+
- Python 3.12 or later
|
|
41
|
+
- git, with `user.name` and `user.email` configured
|
|
42
|
+
- [Claude Code](https://code.claude.com) logged in with a subscription
|
|
43
|
+
(`claude /login`); `wiki` never uses an API key
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
pipx install llm-wiki-tool # or: pipx install 'llm-wiki-tool[docling]'
|
|
49
|
+
wiki doctor # checks claude, its flags, login, isolation and image input
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The package is `llm-wiki-tool` on PyPI; the command it installs is `wiki`.
|
|
53
|
+
Releases and their notes are in [CHANGELOG.md](CHANGELOG.md).
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
wiki init ~/wikis/payments --description "Payments team conventions and architecture"
|
|
59
|
+
cp design.docx diagram.png ~/wikis/payments/inbox/
|
|
60
|
+
wiki ingest --wiki payments # plan, write on a branch, then accept or reject
|
|
61
|
+
wiki search "refund flow" --wiki payments
|
|
62
|
+
wiki status --all
|
|
63
|
+
wiki skill install # the wiki-lookup skill, for every repo
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
A wiki is either its own git repo or a folder inside an existing repo. Wikis are
|
|
67
|
+
found through `--wiki NAME|PATH`, the current directory, the registry
|
|
68
|
+
(`$LLM_WIKI_HOME/wikis.toml`, written by `wiki init`) or `LLM_WIKI_PATH`, a list
|
|
69
|
+
of wiki directories separated by the platform path separator. Running
|
|
70
|
+
`wiki init` on an existing or cloned wiki only registers it.
|
|
71
|
+
|
|
72
|
+
Claude Code asks once before it runs the skill. Once you approve, the skill's
|
|
73
|
+
own `allowed-tools` cover `wiki status`, `wiki search` and reading pages. To
|
|
74
|
+
skip the prompt, for example in headless `claude -p` runs, add
|
|
75
|
+
`"Skill(wiki-lookup)"` to `permissions.allow` in your Claude Code settings.
|
|
76
|
+
|
|
77
|
+
To make one repo prefer a wiki, run
|
|
78
|
+
`wiki skill install --project <repo> --pointer --wiki <name>`. It writes a short
|
|
79
|
+
marked block into `<repo>/CLAUDE.md`.
|
|
80
|
+
|
|
81
|
+
## Commands
|
|
82
|
+
|
|
83
|
+
| Command | What it does |
|
|
84
|
+
| -------------------- | ---------------------------------------------------------------- |
|
|
85
|
+
| `wiki init` | Create or register a wiki |
|
|
86
|
+
| `wiki ingest` | Ingest the inbox, paths or `--note` text, then one gate |
|
|
87
|
+
| `wiki search` | Ranked page search for agents (`--format json`) |
|
|
88
|
+
| `wiki status` | Usage against caps, stale pages, pending and quarantined |
|
|
89
|
+
| `wiki lint` | Deterministic checks of pages, links, citations and caps |
|
|
90
|
+
| `wiki lint --deep` | Also a sampled LLM pass; writes `.wiki/reports/lint-<date>.md` |
|
|
91
|
+
| `wiki lint --fix` | Repair the findings on a branch, then one gate (`--yes`) |
|
|
92
|
+
| `wiki consolidate` | Split, merge, promote and archive pages, then one gate (`--yes`) |
|
|
93
|
+
| `wiki skill install` | Install the read-only `wiki-lookup` skill |
|
|
94
|
+
| `wiki doctor` | Check `claude`, its flags, login, isolation and read denial |
|
|
95
|
+
|
|
96
|
+
## Caps and consolidate
|
|
97
|
+
|
|
98
|
+
Every wiki has hard caps (500 pages, 100 per section, 100 sources); `wiki.toml`
|
|
99
|
+
may only lower them, under `[caps]`, and each `warn_*` level defaults to 80% of
|
|
100
|
+
its `max_*`. Near a cap, `wiki status` prints a `near cap:` line. At the page
|
|
101
|
+
cap, ingest still updates existing pages but refuses new ones: the source is
|
|
102
|
+
kept in `raw/` as failed, and the message ends with "run `wiki consolidate` or
|
|
103
|
+
start a new wiki". `wiki consolidate` then frees room on a branch, behind the
|
|
104
|
+
same gate: it retargets links to retired pages, merges duplicate titles (one
|
|
105
|
+
page becomes a `superseded` stub; a to-be twin of an as-is page or a retired
|
|
106
|
+
page is left to you), splits pages over 400 lines while the cap leaves room for
|
|
107
|
+
one new page, and promotes to-be pages whose fact is now built (at most 3 a
|
|
108
|
+
run). Retired pages that nothing links to any more move to
|
|
109
|
+
`<section>/_archive/`, and their ids are never reused for topic pages (a source
|
|
110
|
+
page's id follows its raw path, so a raw file re-added at the same path gets its
|
|
111
|
+
`src-…` id back); the summary names each retired page that still waits and the
|
|
112
|
+
files that link it. At the source cap, ingest refuses new sources instead: start
|
|
113
|
+
a new wiki or delete obsolete raw sources. Maintenance never adds evidence or
|
|
114
|
+
refreshes `last_verified`; stale pages and changed sources stay yours to
|
|
115
|
+
re-check. The next `wiki ingest` retries the failed source.
|
|
116
|
+
|
|
117
|
+
A page with a secret or injection finding gets no maintenance job, and its text
|
|
118
|
+
never enters a plan or `--deep` prompt; when `wiki/open-questions.md` has such a
|
|
119
|
+
finding, no edit may change it. Edit sessions work in the wiki root with file
|
|
120
|
+
tools; `claude` denies them reading the flagged files and `.wiki/quarantine/`
|
|
121
|
+
(`Read(./…)` rules passed with `--disallowed-tools`, which also keep those files
|
|
122
|
+
out of Grep), and `wiki doctor` probes that the denial holds (`read-denial`).
|
|
123
|
+
|
|
124
|
+
## Upgrading existing wikis
|
|
125
|
+
|
|
126
|
+
`wiki init` writes a wiki's `AGENTS.md` (its rules for the model) once, and the
|
|
127
|
+
tool never rewrites it. Wikis created by an older version keep their old rules.
|
|
128
|
+
To pick up new rules, compare your `AGENTS.md` with the packaged template
|
|
129
|
+
(`llm_wiki/templates/AGENTS.md` in the installed package) and copy the changes
|
|
130
|
+
by hand, keeping your wiki's own name, description and sections table.
|
|
131
|
+
|
|
132
|
+
Wikis created before `wiki lint --deep` existed lack `.wiki/reports/` in their
|
|
133
|
+
`.gitignore`. Add that line so the deep reports stay out of git; the commands
|
|
134
|
+
work either way.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# llm-wiki
|
|
2
|
+
|
|
3
|
+
`wiki` keeps small, capped, LLM-maintained wikis that coding agents read. You
|
|
4
|
+
drop source files (documents, slides, sheets, PDFs, images, notes) into a wiki;
|
|
5
|
+
`wiki ingest` has headless Claude Code plan and write cited pages on a branch,
|
|
6
|
+
and you accept or reject the result at one gate. Agents read the wiki through
|
|
7
|
+
the read-only `wiki-lookup` skill, at no LLM cost.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- Python 3.12 or later
|
|
12
|
+
- git, with `user.name` and `user.email` configured
|
|
13
|
+
- [Claude Code](https://code.claude.com) logged in with a subscription
|
|
14
|
+
(`claude /login`); `wiki` never uses an API key
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
pipx install llm-wiki-tool # or: pipx install 'llm-wiki-tool[docling]'
|
|
20
|
+
wiki doctor # checks claude, its flags, login, isolation and image input
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The package is `llm-wiki-tool` on PyPI; the command it installs is `wiki`.
|
|
24
|
+
Releases and their notes are in [CHANGELOG.md](CHANGELOG.md).
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
wiki init ~/wikis/payments --description "Payments team conventions and architecture"
|
|
30
|
+
cp design.docx diagram.png ~/wikis/payments/inbox/
|
|
31
|
+
wiki ingest --wiki payments # plan, write on a branch, then accept or reject
|
|
32
|
+
wiki search "refund flow" --wiki payments
|
|
33
|
+
wiki status --all
|
|
34
|
+
wiki skill install # the wiki-lookup skill, for every repo
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
A wiki is either its own git repo or a folder inside an existing repo. Wikis are
|
|
38
|
+
found through `--wiki NAME|PATH`, the current directory, the registry
|
|
39
|
+
(`$LLM_WIKI_HOME/wikis.toml`, written by `wiki init`) or `LLM_WIKI_PATH`, a list
|
|
40
|
+
of wiki directories separated by the platform path separator. Running
|
|
41
|
+
`wiki init` on an existing or cloned wiki only registers it.
|
|
42
|
+
|
|
43
|
+
Claude Code asks once before it runs the skill. Once you approve, the skill's
|
|
44
|
+
own `allowed-tools` cover `wiki status`, `wiki search` and reading pages. To
|
|
45
|
+
skip the prompt, for example in headless `claude -p` runs, add
|
|
46
|
+
`"Skill(wiki-lookup)"` to `permissions.allow` in your Claude Code settings.
|
|
47
|
+
|
|
48
|
+
To make one repo prefer a wiki, run
|
|
49
|
+
`wiki skill install --project <repo> --pointer --wiki <name>`. It writes a short
|
|
50
|
+
marked block into `<repo>/CLAUDE.md`.
|
|
51
|
+
|
|
52
|
+
## Commands
|
|
53
|
+
|
|
54
|
+
| Command | What it does |
|
|
55
|
+
| -------------------- | ---------------------------------------------------------------- |
|
|
56
|
+
| `wiki init` | Create or register a wiki |
|
|
57
|
+
| `wiki ingest` | Ingest the inbox, paths or `--note` text, then one gate |
|
|
58
|
+
| `wiki search` | Ranked page search for agents (`--format json`) |
|
|
59
|
+
| `wiki status` | Usage against caps, stale pages, pending and quarantined |
|
|
60
|
+
| `wiki lint` | Deterministic checks of pages, links, citations and caps |
|
|
61
|
+
| `wiki lint --deep` | Also a sampled LLM pass; writes `.wiki/reports/lint-<date>.md` |
|
|
62
|
+
| `wiki lint --fix` | Repair the findings on a branch, then one gate (`--yes`) |
|
|
63
|
+
| `wiki consolidate` | Split, merge, promote and archive pages, then one gate (`--yes`) |
|
|
64
|
+
| `wiki skill install` | Install the read-only `wiki-lookup` skill |
|
|
65
|
+
| `wiki doctor` | Check `claude`, its flags, login, isolation and read denial |
|
|
66
|
+
|
|
67
|
+
## Caps and consolidate
|
|
68
|
+
|
|
69
|
+
Every wiki has hard caps (500 pages, 100 per section, 100 sources); `wiki.toml`
|
|
70
|
+
may only lower them, under `[caps]`, and each `warn_*` level defaults to 80% of
|
|
71
|
+
its `max_*`. Near a cap, `wiki status` prints a `near cap:` line. At the page
|
|
72
|
+
cap, ingest still updates existing pages but refuses new ones: the source is
|
|
73
|
+
kept in `raw/` as failed, and the message ends with "run `wiki consolidate` or
|
|
74
|
+
start a new wiki". `wiki consolidate` then frees room on a branch, behind the
|
|
75
|
+
same gate: it retargets links to retired pages, merges duplicate titles (one
|
|
76
|
+
page becomes a `superseded` stub; a to-be twin of an as-is page or a retired
|
|
77
|
+
page is left to you), splits pages over 400 lines while the cap leaves room for
|
|
78
|
+
one new page, and promotes to-be pages whose fact is now built (at most 3 a
|
|
79
|
+
run). Retired pages that nothing links to any more move to
|
|
80
|
+
`<section>/_archive/`, and their ids are never reused for topic pages (a source
|
|
81
|
+
page's id follows its raw path, so a raw file re-added at the same path gets its
|
|
82
|
+
`src-…` id back); the summary names each retired page that still waits and the
|
|
83
|
+
files that link it. At the source cap, ingest refuses new sources instead: start
|
|
84
|
+
a new wiki or delete obsolete raw sources. Maintenance never adds evidence or
|
|
85
|
+
refreshes `last_verified`; stale pages and changed sources stay yours to
|
|
86
|
+
re-check. The next `wiki ingest` retries the failed source.
|
|
87
|
+
|
|
88
|
+
A page with a secret or injection finding gets no maintenance job, and its text
|
|
89
|
+
never enters a plan or `--deep` prompt; when `wiki/open-questions.md` has such a
|
|
90
|
+
finding, no edit may change it. Edit sessions work in the wiki root with file
|
|
91
|
+
tools; `claude` denies them reading the flagged files and `.wiki/quarantine/`
|
|
92
|
+
(`Read(./…)` rules passed with `--disallowed-tools`, which also keep those files
|
|
93
|
+
out of Grep), and `wiki doctor` probes that the denial holds (`read-denial`).
|
|
94
|
+
|
|
95
|
+
## Upgrading existing wikis
|
|
96
|
+
|
|
97
|
+
`wiki init` writes a wiki's `AGENTS.md` (its rules for the model) once, and the
|
|
98
|
+
tool never rewrites it. Wikis created by an older version keep their old rules.
|
|
99
|
+
To pick up new rules, compare your `AGENTS.md` with the packaged template
|
|
100
|
+
(`llm_wiki/templates/AGENTS.md` in the installed package) and copy the changes
|
|
101
|
+
by hand, keeping your wiki's own name, description and sections table.
|
|
102
|
+
|
|
103
|
+
Wikis created before `wiki lint --deep` existed lack `.wiki/reports/` in their
|
|
104
|
+
`.gitignore`. Add that line so the deep reports stay out of git; the commands
|
|
105
|
+
work either way.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
# The import package is llm_wiki and the command is `wiki`. PyPI refuses
|
|
7
|
+
# "llm-wiki" and "llm-wiki-cli" as too close to "llmwiki" and "llmwiki-cli"
|
|
8
|
+
name = "llm-wiki-tool"
|
|
9
|
+
dynamic = ["version"]
|
|
10
|
+
description = "CLI that keeps small, capped, LLM-maintained wikis for coding agents"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
authors = [{ name = "Leonardo Marziali" }]
|
|
13
|
+
keywords = ["wiki", "llm", "claude", "knowledge-base", "coding-agents"]
|
|
14
|
+
requires-python = ">=3.12"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"typer~=0.27",
|
|
17
|
+
"pydantic~=2.14",
|
|
18
|
+
"pyyaml~=6.0",
|
|
19
|
+
"rapidfuzz~=3.14",
|
|
20
|
+
"platformdirs~=4.13",
|
|
21
|
+
"markitdown[docx,pptx,xlsx,pdf]~=0.1.9",
|
|
22
|
+
"python-pptx~=1.0",
|
|
23
|
+
"openpyxl~=3.1",
|
|
24
|
+
"pypdf~=6.21",
|
|
25
|
+
"pillow~=13.0",
|
|
26
|
+
"imagehash~=4.3",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
docling = ["docling~=2.142"]
|
|
31
|
+
dev = ["pytest~=9.1", "python-docx~=1.2", "pre-commit~=4.7"]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/leonardo-marziali/llm-wiki"
|
|
35
|
+
Changelog = "https://github.com/leonardo-marziali/llm-wiki/blob/main/CHANGELOG.md"
|
|
36
|
+
Issues = "https://github.com/leonardo-marziali/llm-wiki/issues"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
wiki = "llm_wiki.cli:main"
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
where = ["src"]
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.dynamic]
|
|
45
|
+
version = {attr = "llm_wiki.__version__"}
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.package-data]
|
|
48
|
+
llm_wiki = ["templates/*", "templates/**/*", "prompts/*"]
|
|
49
|
+
|
|
50
|
+
[tool.semantic_release]
|
|
51
|
+
# .github/workflows/release.yml runs it on every push to main: it bumps
|
|
52
|
+
# __version__, updates CHANGELOG.md, commits, tags and creates the release
|
|
53
|
+
version_variables = ["src/llm_wiki/__init__.py:__version__"]
|
|
54
|
+
tag_format = "v{version}"
|
|
55
|
+
commit_parser = "conventional"
|
|
56
|
+
commit_message = "chore(release): {version} [skip ci]\n\nAutomatically generated by python-semantic-release"
|
|
57
|
+
# Stay on 0.x until 1.0.0 is cut on purpose
|
|
58
|
+
allow_zero_version = true
|
|
59
|
+
major_on_zero = false
|
|
60
|
+
|
|
61
|
+
[tool.semantic_release.branches.main]
|
|
62
|
+
match = "main"
|
|
63
|
+
|
|
64
|
+
[tool.semantic_release.changelog]
|
|
65
|
+
mode = "update"
|
|
66
|
+
exclude_commit_patterns = ['chore\(release\): ']
|
|
67
|
+
|
|
68
|
+
[tool.semantic_release.changelog.default_templates]
|
|
69
|
+
changelog_file = "CHANGELOG.md"
|
|
70
|
+
|
|
71
|
+
[tool.pytest.ini_options]
|
|
72
|
+
testpaths = ["tests"]
|
|
73
|
+
|
|
74
|
+
[tool.pyright]
|
|
75
|
+
include = ["src", "tests"]
|
|
76
|
+
pythonVersion = "3.12"
|
|
77
|
+
typeCheckingMode = "standard"
|
|
78
|
+
venvPath = "."
|
|
79
|
+
venv = ".venv"
|
|
80
|
+
# pytest puts tests/ on sys.path, so tests can `from conftest import ...`
|
|
81
|
+
extraPaths = ["tests"]
|
|
82
|
+
|
|
83
|
+
[tool.ruff]
|
|
84
|
+
target-version = "py312"
|
|
85
|
+
line-length = 88
|
|
86
|
+
|
|
87
|
+
[tool.ruff.lint]
|
|
88
|
+
# Ruff >= 0.16 defaults already cover F, B, UP, SIM, RUF, PL*, FURB, PT and more
|
|
89
|
+
extend-select = ["E", "W", "I", "UP", "B", "SIM", "RUF"]
|
|
90
|
+
# ‹› delimit redaction marks; – and — appear in user-facing prose on purpose
|
|
91
|
+
allowed-confusables = ["‹", "›", "–"]
|
|
92
|
+
|
|
93
|
+
[tool.ruff.lint.flake8-bugbear]
|
|
94
|
+
# Typer reads parameter specs from defaults; they are never mutated
|
|
95
|
+
extend-immutable-calls = ["typer.Argument", "typer.Option"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Deterministic archiving for ``wiki consolidate`` (Ruling 5): a deprecated or
|
|
2
|
+
superseded page that no live page links to moves to its folder's ``_archive/``
|
|
3
|
+
by ``git mv``. No model is involved.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
from contextlib import suppress
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from llm_wiki import gitops
|
|
11
|
+
from llm_wiki.index import GENERATED_MARK
|
|
12
|
+
from llm_wiki.lint import is_flagged, link_targets
|
|
13
|
+
from llm_wiki.maint import RETIRED, live_linkers
|
|
14
|
+
from llm_wiki.manifest import Manifest
|
|
15
|
+
from llm_wiki.pages import id_for, iter_pages
|
|
16
|
+
|
|
17
|
+
ARCHIVE = "_archive"
|
|
18
|
+
_INDEXES = {"index.md", "_index.md"}
|
|
19
|
+
_ALWAYS_LINKED = {"overview", "glossary"} # the rebuilt index.md links them
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _link_text(path: Path) -> str:
|
|
23
|
+
"""The text whose links stay: an index's generated part is rebuilt without
|
|
24
|
+
the archived pages, its hand-written header is kept.
|
|
25
|
+
"""
|
|
26
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
27
|
+
return text.split(GENERATED_MARK, 1)[0] if path.name in _INDEXES else text
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _linked_from(root: Path) -> dict[Path, set[Path]]:
|
|
31
|
+
"""Each link target -> the wiki files outside ``_archive/`` that link it."""
|
|
32
|
+
wiki = root / "wiki"
|
|
33
|
+
out: dict[Path, set[Path]] = defaultdict(set)
|
|
34
|
+
for f in sorted(wiki.rglob("*.md")):
|
|
35
|
+
if not f.is_file() or ARCHIVE in f.relative_to(wiki).parts[:-1]:
|
|
36
|
+
continue
|
|
37
|
+
here = f.resolve()
|
|
38
|
+
for target in link_targets(f.parent, _link_text(f)) - {here}:
|
|
39
|
+
out[target].add(here)
|
|
40
|
+
return out
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _select(root: Path) -> tuple[dict[str, str], dict[str, list[str]]]:
|
|
44
|
+
"""(id -> wiki-root-relative path of every page ``archive_unlinked`` moves,
|
|
45
|
+
id -> the root-relative files that hold back each page that waits).
|
|
46
|
+
|
|
47
|
+
A candidate is retired and has no live linker (``maint.live_linkers``, R10:
|
|
48
|
+
the predicate relink jobs use). It still waits while any file that stays
|
|
49
|
+
links it (a retired page that is not archived, open-questions.md, an index
|
|
50
|
+
header): archiving it would break that link. A flagged page stays in sight
|
|
51
|
+
of lint (Ruling 4), and index.md always links the overview and glossary.
|
|
52
|
+
"""
|
|
53
|
+
root = Path(root)
|
|
54
|
+
picked: dict[Path, tuple[str, str]] = {}
|
|
55
|
+
for p in iter_pages(root):
|
|
56
|
+
id_ = id_for(p.rel)
|
|
57
|
+
if (
|
|
58
|
+
p.fm is not None
|
|
59
|
+
and p.fm.status in RETIRED
|
|
60
|
+
and id_ not in _ALWAYS_LINKED
|
|
61
|
+
and not is_flagged(p)
|
|
62
|
+
and not live_linkers(root, id_)
|
|
63
|
+
):
|
|
64
|
+
picked[p.path.resolve()] = (id_, f"wiki/{p.rel}")
|
|
65
|
+
linked_from = _linked_from(root)
|
|
66
|
+
first = dict(picked)
|
|
67
|
+
while blocked := [t for t in picked if linked_from[t] - set(picked)]:
|
|
68
|
+
for t in blocked:
|
|
69
|
+
del picked[t]
|
|
70
|
+
top = root.resolve()
|
|
71
|
+
waiting = {
|
|
72
|
+
id_: sorted(f.relative_to(top).as_posix() for f in linked_from[t] - set(picked))
|
|
73
|
+
for t, (id_, _) in first.items()
|
|
74
|
+
if t not in picked
|
|
75
|
+
}
|
|
76
|
+
return dict(sorted(picked.values())), dict(sorted(waiting.items()))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def archive_candidates(root: Path) -> list[str]:
|
|
80
|
+
"""The ids ``archive_unlinked`` would archive now."""
|
|
81
|
+
return list(_select(Path(root))[0])
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def archive_waiting(root: Path) -> dict[str, list[str]]:
|
|
85
|
+
"""The retired pages without a live linker that still wait, each with the
|
|
86
|
+
root-relative files that link it and stay.
|
|
87
|
+
"""
|
|
88
|
+
return _select(Path(root))[1]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _dest(root: Path, rel: str) -> str:
|
|
92
|
+
"""``wiki/<section>/_archive/<slug>.md`` (``wiki/_archive/`` for a top-level
|
|
93
|
+
page), with ``-2``, ``-3`` … when the name is taken.
|
|
94
|
+
"""
|
|
95
|
+
folder, name = rel.rsplit("/", 1)
|
|
96
|
+
stem = name.removesuffix(".md")
|
|
97
|
+
dest, n = f"{folder}/{ARCHIVE}/{name}", 2
|
|
98
|
+
while (root / dest).exists():
|
|
99
|
+
dest = f"{folder}/{ARCHIVE}/{stem}-{n}.md"
|
|
100
|
+
n += 1
|
|
101
|
+
return dest
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def archive_unlinked(root: Path, manifest: Manifest) -> list[str]:
|
|
105
|
+
"""Move every candidate to ``_archive/`` with ``git mv`` and commit the moves;
|
|
106
|
+
drop the archived ids from every ``pages_touched`` of ``manifest`` (the
|
|
107
|
+
caller saves it). Returns the archived ids.
|
|
108
|
+
"""
|
|
109
|
+
root = Path(root)
|
|
110
|
+
found = _select(root)[0]
|
|
111
|
+
moved: list[str] = []
|
|
112
|
+
made: list[Path] = [] # the _archive/ folders this call creates
|
|
113
|
+
try:
|
|
114
|
+
for rel in found.values():
|
|
115
|
+
dest = _dest(root, rel)
|
|
116
|
+
folder = (root / dest).parent
|
|
117
|
+
if not folder.is_dir():
|
|
118
|
+
folder.mkdir()
|
|
119
|
+
made.append(folder)
|
|
120
|
+
gitops.move(root, rel, dest)
|
|
121
|
+
moved += [rel, dest]
|
|
122
|
+
if moved:
|
|
123
|
+
message = f"consolidate(archive): {', '.join(found)}"
|
|
124
|
+
gitops.stage_commit(root, moved, message, force=moved)
|
|
125
|
+
except BaseException: # Ctrl-C before the commit: no move is left half-done
|
|
126
|
+
gitops.restore(root, moved)
|
|
127
|
+
for folder in made:
|
|
128
|
+
with suppress(OSError):
|
|
129
|
+
folder.rmdir()
|
|
130
|
+
raise
|
|
131
|
+
gone = set(found)
|
|
132
|
+
for entry in manifest.entries.values():
|
|
133
|
+
kept = [i for i in entry.pages_touched if i not in gone]
|
|
134
|
+
if len(kept) < len(entry.pages_touched):
|
|
135
|
+
manifest.record(entry.model_copy(update={"pages_touched": kept}))
|
|
136
|
+
return list(found)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Hard caps. wiki.toml may only lower them (see config.WikiConfig.effective_caps)."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True)
|
|
7
|
+
class Caps:
|
|
8
|
+
max_sources: int = 100
|
|
9
|
+
warn_sources: int = 80
|
|
10
|
+
max_pages: int = 500
|
|
11
|
+
warn_pages: int = 400
|
|
12
|
+
max_section_pages: int = 100
|
|
13
|
+
warn_section_pages: int = 80
|
|
14
|
+
max_index_lines: int = 99
|
|
15
|
+
max_page_lines: int = 400
|
|
16
|
+
max_pages_per_ingest: int = 15
|
|
17
|
+
lookup_pages: int = 5
|
|
18
|
+
split_chars: int = 80_000
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
HARD_CAPS = Caps()
|