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.
Files changed (88) hide show
  1. llm_wiki_tool-0.1.0/PKG-INFO +134 -0
  2. llm_wiki_tool-0.1.0/README.md +105 -0
  3. llm_wiki_tool-0.1.0/pyproject.toml +95 -0
  4. llm_wiki_tool-0.1.0/setup.cfg +4 -0
  5. llm_wiki_tool-0.1.0/src/llm_wiki/__init__.py +1 -0
  6. llm_wiki_tool-0.1.0/src/llm_wiki/archive.py +136 -0
  7. llm_wiki_tool-0.1.0/src/llm_wiki/caps.py +21 -0
  8. llm_wiki_tool-0.1.0/src/llm_wiki/claude.py +548 -0
  9. llm_wiki_tool-0.1.0/src/llm_wiki/cli.py +486 -0
  10. llm_wiki_tool-0.1.0/src/llm_wiki/config.py +215 -0
  11. llm_wiki_tool-0.1.0/src/llm_wiki/convert.py +342 -0
  12. llm_wiki_tool-0.1.0/src/llm_wiki/deep.py +158 -0
  13. llm_wiki_tool-0.1.0/src/llm_wiki/doctor.py +493 -0
  14. llm_wiki_tool-0.1.0/src/llm_wiki/errors.py +57 -0
  15. llm_wiki_tool-0.1.0/src/llm_wiki/gate.py +276 -0
  16. llm_wiki_tool-0.1.0/src/llm_wiki/gitops.py +313 -0
  17. llm_wiki_tool-0.1.0/src/llm_wiki/images.py +108 -0
  18. llm_wiki_tool-0.1.0/src/llm_wiki/index.py +108 -0
  19. llm_wiki_tool-0.1.0/src/llm_wiki/ingest.py +787 -0
  20. llm_wiki_tool-0.1.0/src/llm_wiki/ingest_run.py +481 -0
  21. llm_wiki_tool-0.1.0/src/llm_wiki/init.py +289 -0
  22. llm_wiki_tool-0.1.0/src/llm_wiki/intake.py +460 -0
  23. llm_wiki_tool-0.1.0/src/llm_wiki/lint.py +803 -0
  24. llm_wiki_tool-0.1.0/src/llm_wiki/maint.py +719 -0
  25. llm_wiki_tool-0.1.0/src/llm_wiki/maint_job.py +360 -0
  26. llm_wiki_tool-0.1.0/src/llm_wiki/maint_run.py +383 -0
  27. llm_wiki_tool-0.1.0/src/llm_wiki/manifest.py +159 -0
  28. llm_wiki_tool-0.1.0/src/llm_wiki/pages.py +213 -0
  29. llm_wiki_tool-0.1.0/src/llm_wiki/plan.py +739 -0
  30. llm_wiki_tool-0.1.0/src/llm_wiki/prompts/consolidate.md +54 -0
  31. llm_wiki_tool-0.1.0/src/llm_wiki/prompts/edit.md +71 -0
  32. llm_wiki_tool-0.1.0/src/llm_wiki/prompts/fix.md +55 -0
  33. llm_wiki_tool-0.1.0/src/llm_wiki/prompts/lint_deep.md +31 -0
  34. llm_wiki_tool-0.1.0/src/llm_wiki/prompts/maint_edit.md +61 -0
  35. llm_wiki_tool-0.1.0/src/llm_wiki/prompts/plan.md +69 -0
  36. llm_wiki_tool-0.1.0/src/llm_wiki/registry.py +120 -0
  37. llm_wiki_tool-0.1.0/src/llm_wiki/schemas.py +296 -0
  38. llm_wiki_tool-0.1.0/src/llm_wiki/screen.py +354 -0
  39. llm_wiki_tool-0.1.0/src/llm_wiki/search.py +109 -0
  40. llm_wiki_tool-0.1.0/src/llm_wiki/session.py +257 -0
  41. llm_wiki_tool-0.1.0/src/llm_wiki/sheets.py +105 -0
  42. llm_wiki_tool-0.1.0/src/llm_wiki/skill.py +75 -0
  43. llm_wiki_tool-0.1.0/src/llm_wiki/status.py +106 -0
  44. llm_wiki_tool-0.1.0/src/llm_wiki/templates/AGENTS.md +493 -0
  45. llm_wiki_tool-0.1.0/src/llm_wiki/templates/CLAUDE.md +1 -0
  46. llm_wiki_tool-0.1.0/src/llm_wiki/templates/claude-pointer.md +8 -0
  47. llm_wiki_tool-0.1.0/src/llm_wiki/templates/gitignore +5 -0
  48. llm_wiki_tool-0.1.0/src/llm_wiki/templates/pages/glossary.md +11 -0
  49. llm_wiki_tool-0.1.0/src/llm_wiki/templates/pages/overview.md +8 -0
  50. llm_wiki_tool-0.1.0/src/llm_wiki/templates/profiles/general.toml +29 -0
  51. llm_wiki_tool-0.1.0/src/llm_wiki/templates/profiles/software.toml +39 -0
  52. llm_wiki_tool-0.1.0/src/llm_wiki/templates/skill-wiki-lookup.md +53 -0
  53. llm_wiki_tool-0.1.0/src/llm_wiki_tool.egg-info/PKG-INFO +134 -0
  54. llm_wiki_tool-0.1.0/src/llm_wiki_tool.egg-info/SOURCES.txt +86 -0
  55. llm_wiki_tool-0.1.0/src/llm_wiki_tool.egg-info/dependency_links.txt +1 -0
  56. llm_wiki_tool-0.1.0/src/llm_wiki_tool.egg-info/entry_points.txt +2 -0
  57. llm_wiki_tool-0.1.0/src/llm_wiki_tool.egg-info/requires.txt +19 -0
  58. llm_wiki_tool-0.1.0/src/llm_wiki_tool.egg-info/top_level.txt +1 -0
  59. llm_wiki_tool-0.1.0/tests/test_claude.py +958 -0
  60. llm_wiki_tool-0.1.0/tests/test_cli.py +90 -0
  61. llm_wiki_tool-0.1.0/tests/test_config.py +201 -0
  62. llm_wiki_tool-0.1.0/tests/test_consolidate.py +952 -0
  63. llm_wiki_tool-0.1.0/tests/test_convert.py +281 -0
  64. llm_wiki_tool-0.1.0/tests/test_deep.py +342 -0
  65. llm_wiki_tool-0.1.0/tests/test_doctor.py +592 -0
  66. llm_wiki_tool-0.1.0/tests/test_e2e_m1.py +70 -0
  67. llm_wiki_tool-0.1.0/tests/test_e2e_m3.py +225 -0
  68. llm_wiki_tool-0.1.0/tests/test_e2e_m5.py +281 -0
  69. llm_wiki_tool-0.1.0/tests/test_gate.py +58 -0
  70. llm_wiki_tool-0.1.0/tests/test_index.py +160 -0
  71. llm_wiki_tool-0.1.0/tests/test_ingest.py +837 -0
  72. llm_wiki_tool-0.1.0/tests/test_ingest_changes.py +831 -0
  73. llm_wiki_tool-0.1.0/tests/test_ingest_run.py +1387 -0
  74. llm_wiki_tool-0.1.0/tests/test_init.py +561 -0
  75. llm_wiki_tool-0.1.0/tests/test_intake.py +486 -0
  76. llm_wiki_tool-0.1.0/tests/test_lint.py +827 -0
  77. llm_wiki_tool-0.1.0/tests/test_llm_settings.py +121 -0
  78. llm_wiki_tool-0.1.0/tests/test_maint.py +1072 -0
  79. llm_wiki_tool-0.1.0/tests/test_maint_run.py +933 -0
  80. llm_wiki_tool-0.1.0/tests/test_manifest.py +183 -0
  81. llm_wiki_tool-0.1.0/tests/test_pages.py +213 -0
  82. llm_wiki_tool-0.1.0/tests/test_plan.py +772 -0
  83. llm_wiki_tool-0.1.0/tests/test_registry.py +158 -0
  84. llm_wiki_tool-0.1.0/tests/test_screen.py +340 -0
  85. llm_wiki_tool-0.1.0/tests/test_search.py +147 -0
  86. llm_wiki_tool-0.1.0/tests/test_session.py +197 -0
  87. llm_wiki_tool-0.1.0/tests/test_skill.py +182 -0
  88. 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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()