stack: Konfidenz-Mechanismus ersatzlos entfernt, Korpus migriert (schliesst #60, #86)
CI / verify (push) Successful in 56s
Release / release (push) Successful in 36s

Files changed:
- .wikitool-kb.json
- AGENTS.md
- CHANGES.md
- INSTALL-MCP.md
- INSTALL.md
- README.md
- VERSION
- instructions/capture-session.md
- instructions/dev/issue-tracking.md
- instructions/german-terminology.md
- instructions/kb-profiles.md
- instructions/migrate-corpus.md
- instructions/migrations/5.0.0-confidence-removal.md
- instructions/private-instance.md
- instructions/setup-instance.md
- instructions/wiki-lint/SKILL.md
- instructions/wiki-manage/SKILL.md
- instructions/wiki-query/SKILL.md
- kb/CONTRACT.md
- kb/CONVENTIONS.md
- kb/CONVENTIONS.md.template
- kb/concepts/architectures/Consolidation Tiers.md
- kb/concepts/architectures/Context Isolation.md
- kb/concepts/architectures/Cross-platform Agent Skills.md
- kb/concepts/architectures/Episodic Memory.md
- kb/concepts/architectures/Hybrid Search.md
- kb/concepts/architectures/Implementation Spectrum.md
- kb/concepts/architectures/Knowledge Graph.md
- kb/concepts/architectures/LLM Wiki Pattern.md
- kb/concepts/architectures/MCP-Leseserver.md
- kb/concepts/architectures/Memory Lifecycle.md
- kb/concepts/architectures/OKF Compatibility.md
- kb/concepts/architectures/Optional Instance Context File.md
- kb/concepts/architectures/Personalization Plane.md
- kb/concepts/architectures/Procedural Memory.md
- kb/concepts/architectures/RAG.md
- kb/concepts/architectures/Scale Ceiling.md
- kb/concepts/architectures/Semantic Memory.md
- kb/concepts/architectures/Three-Layer Architecture.md
- kb/concepts/architectures/Token Economics.md
- kb/concepts/architectures/Working Memory.md
- kb/concepts/decisions/Delete Rather Than Anonymize.md
- kb/concepts/decisions/Denylist over Allowlist.md
- kb/concepts/decisions/Diff-Reviewable Agent Edits.md
- kb/concepts/decisions/Dual Licensing by File Plan.md
- kb/concepts/decisions/Issue Label Scheme.md
- kb/concepts/decisions/KB Stack Versioning.md
- kb/concepts/decisions/Structural Enforcement over Documented Rule.md
- kb/concepts/patterns/Audit Trail.md
- kb/concepts/patterns/BM25.md
- kb/concepts/patterns/Command Round-Trip Integrity.md
- kb/concepts/patterns/Confidence Scoring.md
- kb/concepts/patterns/Contradiction Resolution.md
- kb/concepts/patterns/Entity Extraction.md
- kb/concepts/patterns/Filter on Ingest.md
- kb/concepts/patterns/Forgetting.md
- kb/concepts/patterns/Graph Traversal.md
- kb/concepts/patterns/Mesh Sync.md
- kb/concepts/patterns/Quality Scoring.md
- kb/concepts/patterns/Reciprocal Rank Fusion.md
- kb/concepts/patterns/Self-Healing.md
- kb/concepts/patterns/Shared vs Private.md
- kb/concepts/patterns/Typed Relationships.md
- kb/concepts/patterns/Vector Search.md
- kb/concepts/patterns/Work Coordination.md
- kb/concepts/problems/Ambient Environment Dependency.md
- kb/concepts/problems/Detect-Repair Asymmetry.md
- kb/concepts/problems/Green Suite Blind Spot.md
- kb/concepts/problems/Naming Convention Conflict.md
- kb/concepts/problems/Write-Once Frontmatter Fields.md
- kb/concepts/protocols/CPPC.md
- kb/concepts/protocols/Modbus.md
- kb/concepts/protocols/SSD TRIM.md
- kb/concepts/workflows/Anti-Cramming Heuristic.md
- kb/concepts/workflows/Bulk Operations.md
- kb/concepts/workflows/CI Integration.md
- kb/concepts/workflows/Checkpoint Audit.md
- kb/concepts/workflows/Claude Code Auto Mode.md
- kb/concepts/workflows/Content Quality Control.md
- kb/concepts/workflows/Crystallization.md
- kb/concepts/workflows/Event-Driven Automation.md
- kb/concepts/workflows/Hooks.md
- kb/concepts/workflows/Index Scaling.md
- kb/concepts/workflows/Iteration and Cost Limits.md
- kb/concepts/workflows/KB Migration.md
- kb/concepts/workflows/Knowledge Compounding.md
- kb/concepts/workflows/Lint Workflow.md
- kb/concepts/workflows/Mass-Update Gate.md
- kb/concepts/workflows/Multi-Agent Collaboration.md
- kb/concepts/workflows/Privacy and Governance.md
- kb/concepts/workflows/Publish-Remote Gate.md
- kb/concepts/workflows/Quality and Self-Correction.md
- kb/concepts/workflows/Semantic Lint Automation.md
- kb/concepts/workflows/Session Orientation.md
- kb/concepts/workflows/Split Merge Reclassify.md
- kb/concepts/workflows/Split Threshold.md
- kb/concepts/workflows/Stub Threshold.md
- kb/concepts/workflows/Supersession.md
- kb/concepts/workflows/User Management.md
- kb/concepts/workflows/Workflow Extraction.md
- kb/concepts/workflows/Workflow Orchestration.md
- kb/entities/people/Andrej Karpathy.md
- kb/entities/people/E3DC GmbH.md
- kb/entities/people/Rohit Gupta.md
- kb/entities/people/Vannevar Bush.md
- kb/entities/projects/BCDModule.md
- kb/entities/projects/Chemenu.md
- kb/entities/projects/andybalholm-edl.md
- kb/entities/projects/goresponsiveness.md
- kb/entities/projects/ha-core.md
- kb/entities/projects/hacs-e3dc.md
- kb/entities/projects/hacs-integration-blueprint.md
- kb/entities/projects/llm-wiki-skills.md
- kb/entities/projects/plugnburn-edl.md
- kb/entities/projects/wiki-skills-vanillaflava.md
- kb/entities/projects/wiki-skills.md
- kb/entities/systems/AGENTS.md.md
- kb/entities/systems/CLAUDE.md.md
- kb/entities/systems/E3DC.md
- kb/entities/systems/ENVIRONMENT.md.md
- kb/entities/systems/Memex.md
- kb/entities/systems/Tolkien Gateway.md
- kb/entities/technologies/Arch Linux.md
- kb/entities/technologies/Disk Encryption.md
- kb/entities/technologies/Docker.md
- kb/entities/technologies/GRUB.md
- kb/entities/technologies/Gitea Actions.md
- kb/entities/technologies/Gitea.md
- kb/entities/technologies/Go.md
- kb/entities/technologies/Home Assistant.md
- kb/entities/technologies/Kernel PM Governors.md
- kb/entities/technologies/LVM.md
- kb/entities/technologies/Linux Kernel.md
- kb/entities/technologies/MQTT.md
- kb/entities/technologies/OPC UA.md
- kb/entities/technologies/Python.md
- kb/entities/technologies/Rust.md
- kb/entities/technologies/Wine GE.md
- kb/entities/technologies/Wine-Staging.md
- kb/entities/technologies/acpi-cpufreq.md
- kb/entities/technologies/amd-pstate.md
- kb/entities/technologies/iii Engine.md
- kb/entities/tools/AUR.md
- kb/entities/tools/Act Runner.md
- kb/entities/tools/Agent Memory.md
- kb/entities/tools/Aura.md
- kb/entities/tools/Bottles.md
- kb/entities/tools/ChatGPT.md
- kb/entities/tools/Claude Code.md
- kb/entities/tools/Codex CLI.md
- kb/entities/tools/Dataview.md
- kb/entities/tools/GPG.md
- kb/entities/tools/GitHub Copilot.md
- kb/entities/tools/Gitea MCP Server.md
- kb/entities/tools/Lutris.md
- kb/entities/tools/Marp.md
- kb/entities/tools/Mistral Vibe.md
- kb/entities/tools/NotebookLM.md
- kb/entities/tools/Obsidian Web Clipper.md
- kb/entities/tools/Obsidian.md
- kb/entities/tools/OpenAI Codex.md
- kb/entities/tools/OpenCode.md
- kb/entities/tools/Pi.md
- kb/entities/tools/Proton.md
- kb/entities/tools/Steam.md
- kb/entities/tools/Wine.md
- kb/entities/tools/awesome-llm-wiki.md
- kb/entities/tools/farzaa gist.md
- kb/entities/tools/gdeploy.md
- kb/entities/tools/makepkg.md
- kb/entities/tools/pascalandy schema.md
- kb/entities/tools/qmd.md
- kb/entities/tools/wikitool.md
- kb/index.md
- kb/log.md
- raw/CONTRACT.md
- tools/CONTRACT.md
- tools/README.md
- tools/chemenu/api.py
- tools/chemenu/cli.py
- tools/chemenu/commands/confidence_decay.py
- tools/chemenu/commands/docs_verify.py
- tools/chemenu/commands/doctor.py
- tools/chemenu/commands/index_build.py
- tools/chemenu/commands/new_page.py
- tools/chemenu/commands/search.py
- tools/chemenu/commands/touch.py
- tools/chemenu/commands/version_cmd.py
- tools/chemenu/conventions.py
- tools/chemenu/corpus_diff.py
- tools/chemenu/frontmatter_io.py
- tools/chemenu/lint_core.py
- tools/chemenu/mcp/server.py
- tools/chemenu/page.py
- tools/chemenu/search/base.py
- tools/chemenu/search/filters.py
- tools/chemenu/search/ripgrep.py
- tools/chemenu/search/service.py
- tools/chemenu/search/types.py
- tools/chemenu/tests/conftest.py
- tools/chemenu/tests/test_api.py
- tools/chemenu/tests/test_confidence_decay.py
- tools/chemenu/tests/test_corpus_diff.py
- tools/chemenu/tests/test_docs_verify.py
- tools/chemenu/tests/test_frontmatter_io.py
- tools/chemenu/tests/test_index_build.py
- tools/chemenu/tests/test_kb_scan.py
- tools/chemenu/tests/test_lint.py
- tools/chemenu/tests/test_mcp_server.py
- tools/chemenu/tests/test_new_page.py
- tools/chemenu/tests/test_page_ops.py
- tools/chemenu/tests/test_provenance.py
- tools/chemenu/tests/test_raw_cmd.py
- tools/chemenu/tests/test_search.py
- tools/chemenu/tests/test_touch.py
- tools/chemenu/tests/test_type_resolver.py
- tools/chemenu/tests/test_version_cmd.py
- tools/chemenu/tests/test_xref.py
- tools/chemenu/version.py
- types/concept.md
- types/concept.schema.yaml
- types/entity.md
- types/entity.schema.yaml
- types/instruction.md
- types/type-spec.md
This commit is contained in:
torben committed 2026-09-10 19:51:48 +02:00
1 parent bb8f956719
commit 54d9540c08
225 files changed
+637 -1145

No files matched your search

-169
View File
@@ -1,169 +0,0 @@
"""Apply the confidence decay formula defined in the wiki contract's
"Confidence Scoring" section: confidence decays at 1% per month since last
confirmation (the page's `modified` / `date` / `created` field), floored at 0.2.
This is pure arithmetic - previously left to the LLM's judgment even though
the contract specifies it exactly. Dry-run by default; `--apply` writes changes.
`confidence` is a *derived* field: it is always recomputed as
`confidence_base * (1 - 0.01 * months)`, never from its own previous value.
Keeping the undecayed anchor in `confidence_base` is what makes repeated runs
idempotent - decaying the stored `confidence` in place (the pre-2026-08-13
behavior) compounded on every run, because the elapsed-months factor kept
growing while the multiplicand had already shrunk.
Pages with `concept_type: decision` are skipped structurally, not as an
interim measure. The formula models staleness - a claim that nobody has
re-checked in a while becomes less trustworthy - and a decision is not a
claim about the world that time can falsify. What retires a decision is a
later decision superseding it, never elapsed months on its own; that is a
category the decay formula does not have a term for, so it does not apply
one.
"""
from __future__ import annotations
import datetime
from typing import Optional
import typer
from chemenu import config
from chemenu.commands._util import success
from chemenu.frontmatter_io import write_page
from chemenu.kb_scan import load_kb_pages
app = typer.Typer(help="Apply confidence decay per the wiki contract's Confidence Scoring formula.")
DECAY_RATE_PER_MONTH = 0.01
FLOOR = 0.2
DAYS_PER_MONTH = 30.44
def _parse_date(value) -> Optional[datetime.date]:
if isinstance(value, datetime.datetime):
return value.date()
if isinstance(value, datetime.date):
return value
if isinstance(value, str):
try:
return datetime.date.fromisoformat(value)
except ValueError:
return None
return None
def compute_decay(confidence: float, last_confirmed: datetime.date, today: datetime.date) -> float:
months = max(0.0, (today - last_confirmed).days / DAYS_PER_MONTH)
decayed = confidence * (1 - DECAY_RATE_PER_MONTH * months)
return round(max(FLOOR, decayed), 2)
def _last_confirmed(frontmatter: dict) -> Optional[datetime.date]:
return (
_parse_date(frontmatter.get("modified"))
or _parse_date(frontmatter.get("date"))
or _parse_date(frontmatter.get("created"))
)
@app.command("init-base")
def confidence_init_base(
apply: bool = typer.Option(False, "--apply", help="Write changes; default is dry-run (preview only)"),
):
"""Backfill `confidence_base` from the current `confidence` on pages that
don't have one yet.
Needed once, when a wiki predates the derived-`confidence` model. Pages
already carrying a base are left untouched, so this is safe to re-run.
"""
pages = load_kb_pages(config.KB_DIR)
changes = []
for title, page in sorted(pages.items()):
confidence = page.frontmatter.get("confidence")
if confidence is None or page.frontmatter.get("confidence_base") is not None:
continue
changes.append((title, page, float(confidence)))
if not changes:
success("Every page with a confidence already has a confidence_base.")
return
for title, page, base in changes:
typer.echo(f"{title}: confidence_base <- {base:.2f}")
if apply:
_set_after(page.frontmatter, "confidence", "confidence_base", round(base, 2))
write_page(page.path, page.frontmatter, page.body)
if apply:
success(f"Set confidence_base on {len(changes)} page(s).")
else:
typer.echo(f"\n{len(changes)} page(s) would change. Re-run with --apply to write.")
def _set_after(frontmatter: dict, after_key: str, key: str, value) -> None:
"""Insert `key` immediately after `after_key`, preserving frontmatter order
(write_page serializes in dict insertion order, and the schemas list
confidence_base right after confidence)."""
if key in frontmatter or after_key not in frontmatter:
frontmatter[key] = value
return
items = list(frontmatter.items())
frontmatter.clear()
for existing_key, existing_value in items:
frontmatter[existing_key] = existing_value
if existing_key == after_key:
frontmatter[key] = value
@app.command("decay")
def confidence_decay(
apply: bool = typer.Option(False, "--apply", help="Write changes; default is dry-run (preview only)"),
):
pages = load_kb_pages(config.KB_DIR)
today = datetime.date.today()
changes = []
missing_base = []
for title, page in sorted(pages.items()):
if page.frontmatter.get("concept_type") == "decision":
continue
confidence = page.frontmatter.get("confidence")
if confidence is None:
continue
base = page.frontmatter.get("confidence_base")
if base is None:
missing_base.append(title)
continue
last_confirmed = _last_confirmed(page.frontmatter)
if last_confirmed is None:
continue
new_confidence = compute_decay(float(base), last_confirmed, today)
if abs(new_confidence - round(float(confidence), 2)) >= 0.01:
changes.append((title, page, float(confidence), new_confidence))
if missing_base:
typer.echo(
f"Skipped {len(missing_base)} page(s) with a confidence but no confidence_base "
"- run `wikitool confidence init-base --apply` first:"
)
for title in missing_base[:10]:
typer.echo(f" - {title}")
if len(missing_base) > 10:
typer.echo(f" ... and {len(missing_base) - 10} more")
typer.echo("")
if not changes:
success("No confidence values need decaying.")
return
for title, page, old, new in changes:
typer.echo(f"{title}: {old:.2f} -> {new:.2f}")
if apply:
page.frontmatter["confidence"] = new
write_page(page.path, page.frontmatter, page.body)
if apply:
success(f"Updated confidence on {len(changes)} page(s).")
else:
typer.echo(f"\n{len(changes)} page(s) would change. Re-run with --apply to write.")
+1 -1
View File
@@ -209,7 +209,7 @@ def check_cli_readme() -> list[str]:
table, and every command documented there must exist.
The reverse check matches a documented cell against the full registered
command path (e.g. `xref add`, `confidence init-base`), not just its first
command path (e.g. `xref add`, `migrate verify`), not just its first
token - checking only the top-level word would let a typo'd or invented
subcommand (`xref frobnicate`) sit undetected next to a real command group
(`xref`) forever.
+1 -1
View File
@@ -218,7 +218,7 @@ def check_conventions() -> Check:
`kb/CONVENTIONS.md` carries the decisions `kb/CONTRACT.md` deliberately no
longer makes: the KB language and the headings its two generated regions
render under, the tone examples, the confidence rubric, the naming forms.
render under, the tone examples, the hedging rule, the naming forms.
`FAIL` rather than `WARN` because those decisions bind every page, and
because it has the same two failure modes the personalization pair has: the
+1 -1
View File
@@ -132,7 +132,7 @@ def build_index_map(collections: list[Collection]) -> str:
"To *find* a page, search instead of reading this file:",
"",
'- `tools/wikitool search "<text>"` - ranked text search, with summaries',
"- `tools/wikitool search --field entity_type=system --field 'confidence<0.6'`"
"- `tools/wikitool search --field entity_type=system --field '!sources'`"
" - structured query over frontmatter",
"",
"The page tables live in a generated `INDEX.md` inside each collection, linked below.",
+2 -2
View File
@@ -77,8 +77,8 @@ def _build_frontmatter(
schema's own `default:` where declared, an empty list for arrays), or are
omitted entirely if optional with no sensible default (e.g.
`source_url`). This is what lets frontmatter shape - and scaffold-time
defaults like `provenance: general` or `confidence: 0.5` - follow the
schema instead of being hand-declared per CLI command.
defaults like `provenance: general` - follow the schema instead of being
hand-declared per CLI command.
"""
frontmatter: Dict[str, Any] = {"type": type_path}
for field_name, field_schema in (schema or {}).get("properties", {}).items():
+2 -2
View File
@@ -11,7 +11,7 @@ Two halves, deliberately kept separate:
`chemenu/search/`.
- Frontmatter predicates (`--field`) are evaluated here, in-process, on the
structured YAML rather than on its rendering. With no text at all this is a
pure structured query, which is how "systems below 0.6 confidence, oldest
pure structured query, which is how "systems with no sources, oldest
first" is asked without a second command.
Scope is `kb/` only. `instructions/` is discovered through
@@ -101,7 +101,7 @@ def search_command(
),
limit: int = typer.Option(20, "--limit", help="Maximum number of results. 0 for no limit."),
sort: str = typer.Option(
None, "--sort", help="Sort by a result field; prefix with '-' to reverse, e.g. -confidence."
None, "--sort", help="Sort by a result field; prefix with '-' to reverse, e.g. -modified."
),
backend: str = typer.Option(
None,
+10 -26
View File
@@ -1,12 +1,12 @@
"""`wikitool touch` - update the self-describing frontmatter fields of a page.
`modified:`, `summary:`, `provenance:` and `confidence_base:` describe the page
itself rather than its relationships, so they were the one part of frontmatter
the skills still told the LLM to edit by hand - a carve-out in the otherwise
absolute "never hand-write frontmatter" rule. Bumping a date and rewriting a
one-line summary are mechanical, so they belong here: the field name is chosen
from the type's own schema (`modified` for entity/concept, `date` for source),
and the result is schema-validated before it is written.
`modified:`, `summary:` and `provenance:` describe the page itself rather than
its relationships, so they were the one part of frontmatter the skills still
told the LLM to edit by hand - a carve-out in the otherwise absolute "never
hand-write frontmatter" rule. Bumping a date and rewriting a one-line summary
are mechanical, so they belong here: the field name is chosen from the type's
own schema (`modified` for entity/concept, `date` for source), and the result
is schema-validated before it is written.
Only `modified:` is bumped automatically. A source's `date:` is the publication
date of the material itself, not a record of when we last edited the page, so it
@@ -49,10 +49,6 @@ UNSETTABLE = {
"changing it changes the page's schema *and* the directory it belongs in - "
"see instructions/page-lifecycle.md"
),
"confidence": (
"derived, not authored: set `--confidence-base` and run "
"`wikitool confidence decay --apply` to recompute it"
),
"related": "page-reference field - use `wikitool xref add` / `xref remove`",
"sources": (
"page-reference field - written from the other side by "
@@ -193,12 +189,6 @@ def touch_command(
provenance: Optional[str] = typer.Option(
None, "--provenance", help="Replace the page's provenance marker (sourced|general|mixed)"
),
confidence_base: Optional[float] = typer.Option(
None,
"--confidence-base",
help="Re-assess the page's undecayed confidence (0.0-1.0). `confidence` itself is derived - "
"run `wikitool confidence decay --apply` afterwards to recompute it.",
),
date: Optional[str] = typer.Option(
None, "--date",
help="Date to record (YYYY-MM-DD). `modified:` defaults to today; a source's "
@@ -230,9 +220,9 @@ def touch_command(
):
"""Bump a page's `modified:` date and optionally rewrite its other frontmatter fields.
`--summary`/`--provenance`/`--confidence-base` are shorthands for the three
fields worth their own flag; `--set`/`--add`/`--remove` reach every other
field the page's type declares. Before they existed, a field `new` wrote
`--summary`/`--provenance` are shorthands for the two fields worth their
own flag; `--set`/`--add`/`--remove` reach every other field the page's
type declares. Before they existed, a field `new` wrote
once - `tags:`, `raw_files:` - could never be corrected: `touch` did not
know it, hand-editing frontmatter is what the tool exists to prevent, and
deleting the page to recreate it breaks every reference already pointing at
@@ -291,12 +281,6 @@ def touch_command(
frontmatter["provenance"] = provenance
touched.add("provenance")
changes.append(f"provenance: {page.frontmatter.get('provenance')} -> {provenance}")
if confidence_base is not None:
frontmatter["confidence_base"] = round(confidence_base, 2)
touched.add("confidence_base")
changes.append(
f"confidence_base: {page.frontmatter.get('confidence_base')} -> {round(confidence_base, 2)}"
)
# --set/--add/--remove last, so an explicit field always wins over the
# shorthand flags rather than depending on option order.
+43 -1
View File
@@ -203,6 +203,13 @@ def bump_command(
"--no-migration",
help="Why the escalation to a boundary crossing needs no content migration (recorded in CHANGES.md)",
),
migration_required: bool = typer.Option(
False,
"--migration-required",
help="Retract this candidate's earlier --no-migration line: a migration is needed after all. "
"Requires a migration document already targeting the new base, and refuses when the entry "
"carries no --no-migration line to retract.",
),
dry_run: bool = typer.Option(False, "--dry-run", help="Report the change without writing"),
):
"""Raise or continue the running candidate, and open or update its
@@ -221,7 +228,13 @@ def bump_command(
migration document for the new base or `--no-migration "<reason>"`. Both
lines are written into the entry once and then persist across every later
bump at the same stage: a follow-up bump need not repeat them, and passing
either on a bump that crosses nothing at all is refused."""
either on a bump that crosses nothing at all is refused.
A later bump of the same candidate that finds out `--no-migration` was
wrong after all retracts it with `--migration-required` - write the
migration document first, then re-run with this flag instead of
`--no-migration`. There is no other way to take the line back: it is
machine-written, and invariant 1 forbids hand-editing it."""
selected = [name for name, chosen in (("major", major), ("minor", minor), ("patch", patch)) if chosen]
if len(selected) != 1:
fail("Pass exactly one of --major / --minor / --patch")
@@ -299,6 +312,34 @@ def bump_command(
)
return
if migration_required and no_migration:
fail("--migration-required and --no-migration contradict each other on the same bump.")
return
if migration_required and not current.is_prerelease:
fail(
"--migration-required only makes sense on a bump that continues an already-open "
"candidate - there is no running candidate here to retract a no-migration line from."
)
return
if migration_required:
current_section = version_mod.changes_section(text, current) or ""
if version_mod.MIGRATION_NONE_MARKER not in current_section:
fail(
f"{current}'s {version_mod.CHANGES_FILENAME} entry carries no "
f"`{version_mod.MIGRATION_NONE_MARKER}` line to retract - nothing to do."
)
return
from chemenu import kb_state
if not any(m.target == new_version.base for m in kb_state.load_migrations()):
fail(
f"--migration-required retracts the no-migration line, so a migration document must "
f"target {new_version.base} first - write one under "
f"{rel_path(kb_state.migrations_dir())}/{new_version.base}-<slug>.md "
f"(see instructions/migrate-corpus.md), then re-run with --migration-required."
)
return
if dry_run:
success(f"Dry run: {current} -> {new_version}{boundary}. Nothing written.")
return
@@ -309,6 +350,7 @@ def bump_command(
text, new_version, today_iso(), title.strip(), author,
no_migration_reason=no_migration.strip() if no_migration else None,
breaking_reason=breaking.strip() if breaking else None,
migration_required=migration_required,
),
encoding="utf-8",
)