fix: cli.py help-patch NameError under a future typer, new record's kb/ paths (#148, #150)
CI / verify (push) Successful in 1m46s
Release / release (push) Successful in 38s

cli.py's typer._click.core.format_help patch was applied a second time
outside the try/except that exists to let a future typer without
typer._click degrade to plain Click help instead of crashing on import.
That second, unconditional line referenced two names the failed import
never defines, so the exact case the fallback exists for raised
NameError on every invocation instead. Removed; a subprocess test with
a targeted import hook (not sys.modules poisoning, which breaks typer
itself, and not importlib.reload, which would stay green against the
bug) pins that the fallback now actually falls back.

The new command's cli_contract record promised kb/concepts/<Name>.md
and kb/sources/Source - <Name>.md - both types have since grown a
layout: that files a page under a subtype-computed subdirectory, the
same rule an entity or a project already follows. Notes for concept
and source now name the subdirectory and where it comes from;
comparison was checked against its type-spec and left alone. Also
found and fixed: new source's example line omitted source_type,
fidelity and authority, so it could never succeed as written. A new
test reads each variant's note out of the record and checks the
written path against the pattern it promises, instead of against a
path re-typed into the test - which is what let the existing per-type
path tests stay green through this exact drift.

tools/CONTRACT.md regenerated via `wikitool docs contract --apply`;
pytest (1550), docs verify and instructions verify all green.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SnAJ7Z3CpVD3PRbN73QtU2
This commit is contained in:
torbenandClaude Sonnet 5 committed 2026-09-26 23:02:11 +02:00
1 parent b8ed8bd610
commit 6c0ebcc4f0
7 files changed
+240 -13

No files matched your search

+48 -1
View File
@@ -59,7 +59,7 @@ concern - readable here, never shipped as something to parse.
---
## 7.1.0-beta.28 - 2026-09-26 - gates.md and a run_budget comment name kb pages by title, not by a path that moved
## 7.1.0-beta.30 - 2026-09-26 - new: concept/source record notes name their layout-computed subdirectory; source usage names its required fields (Gitea #150)
**Author:** Torben Nehmer
@@ -97,8 +97,55 @@ concern - readable here, never shipped as something to parse.
- Command records: three more mismatches from #142 aligned to code
- docs contract: the merged-stream test pins its own ON FAILURE line
- gates.md and a run_budget comment name kb pages by title, not by a path that moved
- cli.py: removed a stale duplicate help-patch line outside the typer._click fallback's try (Gitea #148)
- new: concept/source record notes name their layout-computed subdirectory; source usage names its required fields (Gitea #150)
<!-- /wikitool:bumps -->
### new: concept/source record notes name their layout-computed subdirectory; source usage names its required fields (Gitea #150)
The `new`-command's `cli_contract` record promised `kb/concepts/<Name>.md` and
`kb/sources/Source - <Name>.md` for the `concept` and `source` variants - flat, with no
subdirectory. Both type-specs have carried a `layout:` for a while (`concept_type`/`source_type`
picks the area, same rule an entity or a project already follows), so the actual path is
`kb/concepts/<subdir>/<Name>.md` and `kb/sources/<subdir>/Source - <Name>.md`. Nothing wrote a
page to the wrong place - `new` computes the path itself - but the record is exactly what an
agent reads to find one afterwards, and it was wrong: `instructions/gates.md` named two concept
pages by their old flat path (fixed above) with the record itself as the plausible source of that
assumption. Both notes now name the subdirectory and where it comes from; `comparison`'s note was
checked against `types/comparison.md` and left alone; it genuinely has no `layout:`.
Found in the same pass: `new source`'s example line omitted `source_type` (required, no default
since #66) and `fidelity`/`authority` (enforced by `new` itself since #67), so copying it verbatim
always failed. It now names all three.
The new test in `tools/chemenu/tests/test_new_page.py` reads each variant's note out of the
record itself and turns it into the path pattern it promises, then checks what `new` actually
wrote against that pattern - coupled to the note text, not to a path re-typed into the test, which
is what let the existing per-type path tests stay green through this exact drift. A companion test
asserts every `Writes \`kb/...\`` variant has a case, so a future variant without one is caught
here instead of silently going unchecked. `tools/CONTRACT.md` regenerated via
`wikitool docs contract --apply`.
### cli.py: removed a stale duplicate help-patch line outside the typer._click fallback's try (Gitea #148)
The `typer._click.core.Command.format_help` patch was applied twice: once inside a
`try/except (ImportError, AttributeError)` meant to let a future typer without `typer._click`
degrade to Click's own plain help instead of crashing every invocation, and once more on the next
module-level line, unconditionally. Whenever the `try` actually failed, that second line referenced
two names the failed import never defined and raised `NameError` at import time - the exact crash
the fallback exists to prevent, on every single `wikitool` call. Typer 0.27.2 still has the module,
so nothing showed it in practice; the line was pure dead weight until the day it wasn't. Removed,
so the patch is applied exactly where the `try` already applies it.
The new regression test drives `chemenu.cli` in a subprocess with a `builtins.__import__` hook
that raises only for `typer._click.core` imported from `chemenu.cli`/`__main__`, then checks that
`wikitool search -h` still exits 0 with Click's own plain help. Two more direct ways to simulate
the missing module were tried and rejected: `sys.modules['typer._click.core'] = None` also breaks
typer's own lazy import of `typer._click.decorators` inside `get_help_option`, so `-h` fails
regardless of what `cli.py` does; `importlib.reload(chemenu.cli)` reruns the module in the same
`__dict__`, so the names from the first, real import survive and the broken line runs
successfully - a test built that way would stay green against the exact bug it exists to catch.
### gates.md and a run_budget comment name kb pages by title, not by a path that moved
`instructions/gates.md` pointed at `kb/concepts/Mass-Update Gate.md` and