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:
1 parent
bb8f956719
commit
54d9540c08
225 files changed
+637
-1145
No files matched your search
@@ -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.")
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user