2 Commits

Author SHA1 Message Date
Homer 25aa208d04 Documentation that points where the documentation is
The working notes, the roadmap and the eight topic notes now live on the wiki,
so the twelve places in the source that said "see CLAUDE.md" were pointing at
a file this repository no longer has. They say "see the working notes" now,
and the README opens onto the wiki rather than onto two files beside it.

Four references are deliberately untouched -- prompts.py, settings_store.py,
admin/agents.html and the whole of agent/instructions.py. Those name AGENTS.md
and CLAUDE.md as the file an agent chat looks for in *somebody else's* project
directory. Rewriting them would have broken the feature while looking tidy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 01:38:38 +02:00
Homer 7ebdc9c722 A version that was not two spellings of itself
The Updates page read "v1.0.0 (reports 1.0.0)". That note exists to warn
that a tag was cut before the version bump -- a release nobody can
identify afterwards -- and it was firing on two ways of writing one
version, because `git describe` answers with the tag's name and tags here
carry a `v`.

Stripped in `_describe`, where `resolve_target` has always stripped it and
where the docstring already promised the stripped form. The mismatch check
then compares two things spelled the same way, and still reports a tag
that really does disagree; there is a test for each half.

Found by cutting the first release, which is the only place it could have
been found.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 15:33:32 +02:00
15 changed files with 99 additions and 18 deletions
+21
View File
@@ -16,6 +16,27 @@ for 1.0.0 have something to be assembled from.
## Unreleased
## 1.0.2
- **The documentation moved to the [wiki](https://git.houmeres.sk/Houmeres/LLeMbas/wiki).**
`CLAUDE.md`, `PLAN.md` and `docs/` are gone from the repository: they are
documentation *about* this project rather than part of it, and a clone should
carry software. Nothing was lost — the working notes, the roadmap and the eight
topic notes are all there, with every internal link rewritten, and the README
now opens onto them. Where a source comment said "see `CLAUDE.md`" it now says
"see the working notes".
- Entries below this one still name `PLAN.md` and `docs/notes/…`, and are left as
they were written. A changelog records what happened at the time; rewriting old
entries to match a later decision makes it a worse record, not a better one.
## 1.0.1
- Fixed: the Updates page showed **"v1.0.0 (reports 1.0.0)"** — two spellings of
one version, in a note whose whole purpose is to warn that a tag was cut
before the version bump. `git describe` answers with the tag's name, and tags
here carry a `v`. Found by cutting the first release, which is the only place
it could have been.
## 1.0.0
The first release. Every version before it shipped as a running deployment
+19 -2
View File
@@ -144,7 +144,24 @@ runtime. Clone it, `pip install -e .`, run it.
OCR for scanned PDFs · conversation branching · chat export · archived chats.
See [PLAN.md](PLAN.md) for what is built, what is not, and why.
See the [Roadmap](https://git.houmeres.sk/Houmeres/LLeMbas/wiki/Roadmap) for what
is built, what is not, and why.
## Documentation
The **[wiki](https://git.houmeres.sk/Houmeres/LLeMbas/wiki)** carries everything
about how this works and why — it is documentation *about* the project rather
than part of it, so a clone stays software.
- **[Working notes](https://git.houmeres.sk/Houmeres/LLeMbas/wiki/Working-notes)**
— read this before changing anything. The hard rules the project is built
around, the layout, and a long catalogue of *things that will bite you*: bugs
that shipped looking correct, why each happened, and what stops it recurring.
- **[Roadmap](https://git.houmeres.sk/Houmeres/LLeMbas/wiki/Roadmap)** — what is
built, what is deliberately not, and the reasoning behind each.
- A page each for agent chats, schedules and reports, permissions and sharing,
search and extraction, image generation, subagents, branding, and the manual
release checklist.
## Quick start
@@ -479,7 +496,7 @@ python scripts/fetch_vendor.py # verify vendored JS against the lockfile
There is no Alembic. The schema is SQLite-only and synchronised at startup:
missing tables and missing columns are added automatically, so adding a field to
a model needs nothing but a restart. Renames, drops and retypes are still manual
— see `CLAUDE.md`.
— see the [working notes](https://git.houmeres.sk/Houmeres/LLeMbas/wiki/Working-notes).
## Artwork
+1 -1
View File
@@ -1,3 +1,3 @@
"""LLeMbas - a Middle-earth themed web UI for OpenAI-compatible LLM endpoints."""
__version__ = "1.0.0"
__version__ = "1.0.2"
+1 -1
View File
@@ -3,7 +3,7 @@
Two shapes on one nav entry, because they are two different kinds of thing. The
connection, the checkpoints and the switches are instance settings and get a
settings page. A workflow is an authored document with a name, a description and
a body, so the workflows are list-plus-detail -- the shape `CLAUDE.md` requires
a body, so the workflows are list-plus-detail -- the shape the working notes require
of any admin list, and for the reason it gives: a page that renders a ten-line
JSON textarea per row is unusable at three rows.
+1 -1
View File
@@ -238,7 +238,7 @@ async def browse_profile(
it holds for the same reason -- somebody who owns the credential could list
the directory with an ssh client -- but it does mean Manual mode's promise
that everything is shown to you first now has a second exception. Both are
written down in CLAUDE.md.
written down in the working notes.
"""
profile = _profile(db, user, profile_id)
entries: list = []
+1 -1
View File
@@ -985,7 +985,7 @@ def _too_many_replies(db: DBSession, chat: Chat, user: User) -> str:
"""Why this account may not start another reply right now, or "".
In-process, and that is exact rather than approximate only because this
application runs one worker -- see the first known limit in PLAN.md. With
application runs one worker -- see the first known limit in the roadmap. With
several, this becomes a guess, and a quota that is a guess should be a
number in the database instead. Stated here rather than discovered.
"""
+1 -1
View File
@@ -263,7 +263,7 @@ def register_error_handlers(app: FastAPI) -> None:
# Flavour lives in error pages, empty states and theme names -- never in the
# functional UI. See CLAUDE.md.
# functional UI. See the working notes.
#
# The three lines themselves moved into `services/branding.py` with the rest of
# what an administrator can replace. What is left here is the mapping from a
+1 -1
View File
@@ -18,7 +18,7 @@ a model choosing to run something. Both are read-only, both are built here
rather than assembled from anything a model said, and the project directory is
configuration rather than input. It is still an exception to Manual mode's
"everything is shown to you before it happens", and it is written down in
CLAUDE.md next to the others.
the working notes, next to the others.
**Nothing here is trusted.** Filenames come off somebody else's machine and end
up inside a system prompt, so they are stripped of control characters, capped
+1 -1
View File
@@ -184,7 +184,7 @@ def launch_and_wait_command(chat_id: str, job_id: str, command: str, max_bytes:
# operand and formats to "<Logger … (WARNING)>", whose angle brackets and
# parentheses are shell syntax -- so this line died with a syntax error,
# after the sentinel where nothing reads it, and every job's four files
# were left on the far side forever. See the note in CLAUDE.md.
# were left on the far side forever. See the note in the working notes.
f"rm -f {_file(chat_id, job_id, 'sh')} {pid} {logf} {exit_}\n"
)
+2 -2
View File
@@ -7,7 +7,7 @@ are separate because they fail differently:
shows;
- **flavour text** the Middle-earth lines, which live in the artwork, the
empty states, the loading lines and the error pages and nowhere else (see the
flavour rule in CLAUDE.md), and which somebody rebranding needs to be able to
flavour rule in the working notes), and which somebody rebranding needs to be able to
replace without editing templates;
- **themes**, which are token sets rather than stylesheets, because the
invariant that no component hard-codes a colour is what makes a third one
@@ -36,7 +36,7 @@ page and by nothing else.
The cost of being a cache is stated rather than discovered: with several
workers, a save in one is not seen by the others until each next reads. That is
already true of this application for other reasons -- see the "one worker" note
in PLAN.md -- and this does not make it worse.
in the roadmap -- and this does not make it worse.
"""
from __future__ import annotations
+1 -1
View File
@@ -383,7 +383,7 @@ async def _run_fetch(context: ToolContext, args: dict[str, Any]) -> ToolOutcome:
Straight through `services/fetch.py`, which owns the SSRF guard, the
hand-rolled redirect loop that re-checks every hop, and the content-type
sniff. Deliberately not a second HTTP client: CLAUDE.md already names three
sniff. Deliberately not a second HTTP client: the working notes already name three
places that follow redirects by hand as the ceiling, and a fourth is how one
of them loses its check.
"""
+11 -4
View File
@@ -340,7 +340,7 @@ def resolve_target(root: Path, channel: str, branch: str) -> Target | None:
return None
return Target(
ref=tag,
label=tag.lstrip("v"),
label=tag.removeprefix("v"),
sha=commit.sha,
subject=commit.subject,
notes=_notes_for(root, tag),
@@ -362,9 +362,16 @@ def _describe(root: Path) -> str:
bare short sha when nothing has ever been tagged. That last case is why
`--always` is there: without it this fails outright on a repository with no
tags, which is every repository before its first release.
The leading `v` comes off, because git answers with the **tag's name** and
tags here are `v1.0.0` while `__version__` is `1.0.0`. Without this the page
read "v1.0.0 (reports 1.0.0)" -- a note whose whole purpose is to flag a tag
cut before a version bump, firing on two spellings of the same version. The
first release is what showed it. `resolve_target` has always stripped it for
the same reason, and this docstring already promised the stripped form.
"""
code, output = _git(["describe", "--tags", "--always", "--dirty="], cwd=root)
return output if code == 0 else ""
return output.removeprefix("v") if code == 0 else ""
def read(*, fetch: bool = False) -> State:
@@ -418,8 +425,8 @@ def read(*, fetch: bool = False) -> State:
# Exactly at a tag whose name disagrees with the version this process
# reports. No subprocess: `running` and `__version__` are both already here.
mismatch = ""
if RELEASE_TAG.match(running) and running.lstrip("v") != __version__:
mismatch = running.lstrip("v")
if RELEASE_TAG.match(running) and running != __version__:
mismatch = running
return State(
**base,
+1 -1
View File
@@ -118,7 +118,7 @@
toggling their panel twice, which is to say doing nothing at all.
None of it looks like a script loaded twice. Found by driving the file under a
DOM stub, which is the rule `CLAUDE.md` sets out and the reason it does.
DOM stub, which is the rule the working notes set out and the reason it does.
#}
{% block scripts %}
<script src="{{ url_for('static', path='js/steps.js') }}" defer></script>
+1 -1
View File
@@ -5,7 +5,7 @@
Cached at install time, so it has to stand entirely on its own: no user, no
chats, nothing that was rendered from the database. One of the few places
flavour belongs -- see the flavour rule in CLAUDE.md.
flavour belongs -- see the flavour rule in the working notes.
#}
{% block title %}Offline - {{ brand.name }}{% endblock %}
+36
View File
@@ -509,3 +509,39 @@ def test_a_reinstall_does_not_move_the_channel_by_itself():
assert 'CHANNEL="${LEMBAS_CHANNEL:-${_installed_channel:-stable}}"' in install
# Parsed rather than sourced: that file holds the secret key.
assert ". $PREFIX/lembas.env" not in install
def test_the_running_version_is_not_two_spellings_of_itself(db, tagged):
"""`git describe` answers with the **tag's name**, and tags here are
`v1.0.0` while `__version__` is `1.0.0`. The page prints the described
version and appends "(reports X)" when the two disagree -- a note whose
whole purpose is to flag a tag cut *before* a version bump.
Unstripped, it fired on the first release: "v9.9.9 (reports 9.9.9)", which
reads as a discrepancy and is two ways of writing one version. Found by
cutting the release, which is the only place it could have been.
"""
root = updates.checkout_dir()
running = updates._describe(root)
assert not running.startswith("v"), running
assert running.startswith("9.9.9"), running
def test_a_tag_that_really_disagrees_is_still_reported(db):
"""The note has to keep working, or stripping the prefix has removed the
check rather than fixed it. A tag cut before the version bump names a
release nobody can identify afterwards."""
import subprocess
root = updates.checkout_dir()
subprocess.run(
["git", "tag", "-a", "v99.0.0", "-m", "Wrong."],
cwd=root, check=True, capture_output=True,
)
try:
assert updates.read().version_mismatch == "99.0.0"
finally:
subprocess.run(
["git", "tag", "-d", "v99.0.0"], cwd=root, check=False, capture_output=True
)