54d9540c08
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
329 lines
14 KiB
Python
329 lines
14 KiB
Python
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from chemenu import config, conventions
|
|
from chemenu.frontmatter_io import write_page
|
|
from chemenu.type_resolver import resolver
|
|
|
|
# Environment the tool reads for its own behaviour. Cleared for every test, so
|
|
# that a test which needs one sets it itself and the rest run against the
|
|
# tool's own defaults. `WIKI_TRACE_DIR` is deliberately absent: it is not a
|
|
# leak but the redirect `isolated_trace_dir` installs one fixture below.
|
|
_WIKITOOL_ENV = (
|
|
"WIKI_AUTHOR",
|
|
"WIKI_TRACE",
|
|
"WIKI_TRACE_CONTENT",
|
|
"WIKI_TRACE_MAX_CONTENT",
|
|
"WIKITOOL_SESSION_ID",
|
|
"WIKITOOL_UPDATE_URL",
|
|
"WIKITOOL_UPDATE_TOKEN",
|
|
"CHEMENU_ROOT",
|
|
)
|
|
|
|
# Environment git reads for identity or for where its repo lives. A stray
|
|
# `GIT_DIR` would point every fixture repo at the developer's checkout; the
|
|
# identity variables outrank `git config user.name`, which is the value
|
|
# `config.default_author()` is supposed to be reading.
|
|
_GIT_ENV = (
|
|
"GIT_DIR",
|
|
"GIT_WORK_TREE",
|
|
"GIT_AUTHOR_NAME",
|
|
"GIT_AUTHOR_EMAIL",
|
|
"GIT_COMMITTER_NAME",
|
|
"GIT_COMMITTER_EMAIL",
|
|
"EMAIL",
|
|
)
|
|
|
|
|
|
def _working_tree_state() -> str | None:
|
|
"""`git status --porcelain` for the checkout the tests live in, or None if
|
|
there is no git available to ask."""
|
|
try:
|
|
result = subprocess.run(
|
|
["git", "-C", str(config._PACKAGE_ROOT), "status", "--porcelain"],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=60,
|
|
)
|
|
except (OSError, subprocess.SubprocessError):
|
|
return None
|
|
return result.stdout if result.returncode == 0 else None
|
|
|
|
|
|
_TREE_GUARD_MESSAGE = (
|
|
"A test wrote into the repository checkout instead of into its tmp_path.\n"
|
|
"`git status --porcelain` moved while the suite ran:\n\n"
|
|
" before:\n{before}\n"
|
|
" after:\n{after}\n\n"
|
|
"This is the class of bug Gitea #44 describes: code under test resolves a "
|
|
"path through `config.ROOT`/`config.KB_DIR` rather than through the "
|
|
"directory the fixture handed it, so the write lands in the real tree. Fix "
|
|
"the fixture (repoint `config.ROOT`, as `raw_dir` and `kb_dir` do) or the "
|
|
"code path, never the symptom.\n"
|
|
"To find the test that did it, re-run with CHEMENU_TREE_GUARD=each - the "
|
|
"guard then checks after every test and fails on the first one that moves "
|
|
"the tree."
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="session", autouse=True)
|
|
def repository_tree_guard():
|
|
"""Fail the run if the suite moved a file in the real checkout.
|
|
|
|
Two `git status` calls for the whole session, which is why this is on by
|
|
default: it catches the whole class rather than the one case that was
|
|
noticed. It compares before against after rather than demanding a clean
|
|
tree, so it says nothing about a developer's own uncommitted work.
|
|
|
|
It cannot name the culprit - set `CHEMENU_TREE_GUARD=each` for that, which
|
|
trades a `git status` per test for a failure on the test that did it.
|
|
"""
|
|
before = _working_tree_state()
|
|
yield
|
|
after = _working_tree_state()
|
|
if before is None or after is None or before == after:
|
|
return
|
|
raise AssertionError(
|
|
_TREE_GUARD_MESSAGE.format(before=before or "(clean)", after=after or "(clean)")
|
|
)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def per_test_tree_guard(repository_tree_guard):
|
|
"""The bisect half of `repository_tree_guard`, off unless asked for.
|
|
|
|
`CHEMENU_TREE_GUARD=each` turns the session-wide "something moved the tree"
|
|
into "this test moved the tree", at the cost of a `git status` per test.
|
|
"""
|
|
if os.environ.get("CHEMENU_TREE_GUARD") != "each":
|
|
yield
|
|
return
|
|
before = _working_tree_state()
|
|
yield
|
|
after = _working_tree_state()
|
|
if before is not None and after is not None and before != after:
|
|
raise AssertionError(
|
|
_TREE_GUARD_MESSAGE.format(before=before or "(clean)", after=after or "(clean)")
|
|
)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def hermetic_environment(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
|
"""Cut every test off from the machine it runs on.
|
|
|
|
The suite was green for months while silently depending on whoever ran it:
|
|
`config.default_author()` shells out to `git config user.name` and got an
|
|
answer from the *global* git configuration of the developer's account. The
|
|
first CI run that reached pytest had none, and two tests fell over
|
|
(Gitea #8); two more of the same kind were written afterwards, by someone
|
|
who had read that issue. Neither round was a mistake anyone could have seen
|
|
locally - which is the argument for closing the hole here rather than
|
|
fixing each case.
|
|
|
|
So: `HOME` points into `tmp_path`, git's global and system configuration
|
|
are `/dev/null`, and the tool's own environment is cleared. A test that
|
|
needs an identity now has to establish one - `WIKI_AUTHOR`, or a local
|
|
`git config user.name` in its own fixture repo - and one that does not gets
|
|
the same empty machine everywhere, CI included.
|
|
|
|
Returns the fake `HOME`, for the rare test that wants to put something in it.
|
|
"""
|
|
home = tmp_path / "home"
|
|
home.mkdir()
|
|
monkeypatch.setenv("HOME", str(home))
|
|
monkeypatch.setenv("XDG_CONFIG_HOME", str(home / ".config"))
|
|
|
|
# Both are read even when unset in the environment; pointing them at
|
|
# /dev/null is git's own documented way to say "there is no such file".
|
|
monkeypatch.setenv("GIT_CONFIG_GLOBAL", os.devnull)
|
|
monkeypatch.setenv("GIT_CONFIG_SYSTEM", os.devnull)
|
|
|
|
for name in (*_WIKITOOL_ENV, *_GIT_ENV):
|
|
monkeypatch.delenv(name, raising=False)
|
|
|
|
# The same hole as the environment above, one layer in: `config` resolves
|
|
# its paths on access, and `monkeypatch.setattr(config, "KB_DIR", ...)`
|
|
# undoes itself by writing the *resolved* old path back as a real
|
|
# attribute. That binding outlives the test and hands the next one a
|
|
# corpus directory belonging to the previous tree. Cleared on both sides,
|
|
# so neither a leak from before nor one from this test can be inherited.
|
|
# One layer further in again: `conventions` parses `kb/CONVENTIONS.md` once
|
|
# and keys the result on the file's own path and stat, so a repointed
|
|
# `KB_DIR` cannot be answered out of it. Cleared here anyway, on both sides,
|
|
# for the same reason `config.reset()` is - a fixture that leaves state
|
|
# behind is the hole this file exists to close, and the cost of proving it
|
|
# cannot leak is one function call per test.
|
|
config.reset()
|
|
conventions.reset_cache()
|
|
yield home
|
|
config.reset()
|
|
conventions.reset_cache()
|
|
|
|
|
|
def use_shipped_type_specs(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Keep the shipped `types/` reachable for a test that repoints `ROOT`.
|
|
|
|
`TYPES_DIR` and the `TypeResolver`'s own root both follow `ROOT` now, which
|
|
is the whole point of making the resolution lazy - but it means a fixture
|
|
tree without a `types/` resolves no page kind at all, and a check that
|
|
depends on a page being a `source` silently stops finding one. These tests
|
|
do want the real schema: a synthetic type-spec would prove the command
|
|
works against a fixture rather than against what it ships with.
|
|
|
|
So the dependency is declared instead of inherited. While both were bound at
|
|
import time it held by accident, which is the same shape as the hole
|
|
`raw_dir` was written to close, one layer down.
|
|
"""
|
|
monkeypatch.setattr(config, "TYPES_DIR", config._PACKAGE_ROOT / "types")
|
|
monkeypatch.setattr(resolver, "_repo_root", config._PACKAGE_ROOT)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def isolated_trace_dir(
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, hermetic_environment: Path
|
|
) -> Path:
|
|
"""Send every test's telemetry into its own tmp_path.
|
|
|
|
The emitter is wired into `cli.main()` and into both gates, so any test that
|
|
exercises those paths writes a trace. Without this the suite appends to the
|
|
real `reports/telemetry/` - which is exactly what it did for one run before
|
|
this fixture existed.
|
|
|
|
Depends on `hermetic_environment` for the ordering, not for a value: that
|
|
fixture clears `WIKI_TRACE`, so it has to run first for the redirect
|
|
installed here to survive with tracing still enabled. Tracing is never
|
|
turned off suite-wide - two telemetry tests assert that a trace is written.
|
|
"""
|
|
trace_dir = tmp_path / "telemetry"
|
|
monkeypatch.setenv("WIKI_TRACE_DIR", str(trace_dir))
|
|
return trace_dir
|
|
|
|
|
|
@pytest.fixture
|
|
def raw_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
|
"""A small fake raw/ tree sitting next to the kb_dir fixture (same
|
|
tmp_path), for provenance-coverage tests. One file is deliberately left
|
|
uncovered by any source page.
|
|
|
|
`config.ROOT` is repointed at `tmp_path` for the same reason
|
|
`hermetic_environment` clears the environment: a `raw/...` path in a
|
|
fixture page is resolved against the *repository* root by the code under
|
|
test (`provenance.legacy_source_pages`, for one), so without this the
|
|
fixture builds a raw tree that the code never looks at and answers from
|
|
the developer's own `raw/` instead.
|
|
|
|
That was not theoretical. `test_legacy_source_pages_flags_url_and_directory`
|
|
passed for months only because this checkout happened to have a
|
|
`raw/documents/` directory; the run that emptied it turned the test red in
|
|
CI while it stayed green locally, because git does not track empty
|
|
directories and the local one survived. Same shape as Gitea #8, closed the
|
|
same way - in the fixture, not in the one test that happened to trip.
|
|
"""
|
|
monkeypatch.setattr(config, "ROOT", tmp_path)
|
|
use_shipped_type_specs(monkeypatch)
|
|
raw = tmp_path / "raw"
|
|
(raw / "notes").mkdir(parents=True)
|
|
(raw / "notes" / "Aurora.md").write_text("# Aurora raw notes\n", encoding="utf-8")
|
|
(raw / "notes" / "Uningested.md").write_text("# Not yet ingested anywhere\n", encoding="utf-8")
|
|
return raw
|
|
|
|
|
|
@pytest.fixture
|
|
def kb_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
|
"""A minimal fixture kb/ with the standard collection layout, populated
|
|
with a handful of pages covering entities/concepts/sources/comparisons.
|
|
|
|
Every collection carries a COLLECTION.md, both because that is what makes it
|
|
a collection and because the scanner must prove it skips them at a depth the
|
|
kb-root meta files never reach.
|
|
|
|
`config.ROOT` is repointed for the same reason `raw_dir` does it, one
|
|
collection over: code under test that resolves a path through
|
|
`config.ROOT`/`config.KB_DIR` rather than through the directory it was
|
|
handed otherwise reaches the *real* repository. That was not theoretical
|
|
either - a test calling `kb_state.write_kb_state()` overwrote this
|
|
checkout's `.wikitool-kb.json`, and `lint`'s collection lookup answered
|
|
"no collection" for every fixture page, which left `unauthorised_labels`
|
|
with no working test at all (Gitea #44).
|
|
"""
|
|
monkeypatch.setattr(config, "ROOT", tmp_path)
|
|
use_shipped_type_specs(monkeypatch)
|
|
kb = tmp_path / "kb"
|
|
for sub in ("entities/projects", "entities/systems", "entities/tools",
|
|
"entities/technologies", "entities/people",
|
|
"concepts/protocols", "sources", "comparisons"):
|
|
(kb / sub).mkdir(parents=True)
|
|
# The contracts carry a real declaration, because three things now read one:
|
|
# `docs verify` checks `profile:`/`required_by_stack:`, and `xref add` asks
|
|
# `outbound:` whether a label is authorised from this collection. A fixture
|
|
# contract without it would make every `xref add` in the suite fail for a
|
|
# reason that has nothing to do with what the test is about.
|
|
for collection in ("entities", "concepts", "sources", "comparisons"):
|
|
(kb / collection / "COLLECTION.md").write_text(
|
|
"---\n"
|
|
f"profile: {collection}\n"
|
|
f"required_by_stack: {'true' if collection == 'sources' else 'false'}\n"
|
|
"outbound:\n"
|
|
" any: [depends-on, required-by, runs-on, hosts, uses, implements, see-also]\n"
|
|
"---\n\n"
|
|
f"# kb/{collection}/ - Collection Contract\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
write_page(
|
|
kb / "entities/systems/aurora.md",
|
|
{
|
|
"type": "types/entity.md", "entity_type": "system",
|
|
"tags": ["server"], "created": "2026-07-31", "modified": "2026-07-31",
|
|
"related": ["Borealis"], "sources": [],
|
|
"summary": "Server hosting DocStore with ZFS storage",
|
|
},
|
|
"\n# aurora\n\n## Description\n\nHosts things.\n\n## Relationships\n\n- **Related to:** [[Borealis]]\n\n## See Also\n\n- [[Borealis]]\n",
|
|
)
|
|
write_page(
|
|
kb / "entities/systems/Borealis.md",
|
|
{
|
|
"type": "types/entity.md", "entity_type": "system",
|
|
"tags": ["workstation"], "created": "2026-08-02", "modified": "2026-08-02",
|
|
"related": ["aurora"], "sources": [],
|
|
},
|
|
"\n# Borealis\n\n## Description\n\nA workstation.\n\n## Relationships\n\n- **Related to:** [[aurora]]\n",
|
|
)
|
|
write_page(
|
|
kb / "entities/tools/gdeploy.md",
|
|
{
|
|
"type": "types/entity.md", "entity_type": "tool",
|
|
"tags": [], "created": "2026-07-25", "modified": "2026-07-25",
|
|
"related": [], "sources": [],
|
|
},
|
|
"\n# gdeploy\n\n## Description\n\nDeploy tool.\n",
|
|
)
|
|
write_page(
|
|
kb / "concepts/protocols/Modbus.md",
|
|
{
|
|
"type": "types/concept.md", "concept_type": "protocol",
|
|
"tags": [], "created": "2026-07-25", "modified": "2026-07-25",
|
|
"related": [], "sources": [],
|
|
},
|
|
"\n# Modbus\n\n## Definition\n\nIndustrial protocol.\n",
|
|
)
|
|
write_page(
|
|
kb / "sources/notes/Source - Aurora.md",
|
|
{
|
|
"type": "types/source.md", "source_type": "notes", "author": "Torben",
|
|
"source": "raw/notes/Aurora.md", "date": "2026-08-02",
|
|
"tags": [], "entities": ["aurora"], "concepts": [],
|
|
},
|
|
"\n# Source: Aurora\n\n## Summary\n\nNotes.\n",
|
|
)
|
|
|
|
(kb / "index.md").write_text(
|
|
"# Wiki Index\n\n[[aurora]] [[Borealis]] [[gdeploy]] [[Modbus]] [[Source - Aurora]]\n",
|
|
encoding="utf-8",
|
|
)
|
|
(kb / "log.md").write_text("# Wiki Log\n", encoding="utf-8")
|
|
return kb
|