Skip to main content
Glama

biz.dfch.SpecMgr

License: AGPL v3 Python Lint and Test Coverage TestPyPI version PyPI version PyPI downloads MCP Registry

An artifact manager for system specifications.

This project is an MCP server that you can use to manage different specification artifacts — see Concepts for the full list of artifact types and how they relate, and MCP Server / docs/MCP.md for the tool/resource/prompt reference.

The MCP server (and the management CLI) are optional. You install them as "extras" (see Installation).

Table of Contents

Related MCP server: OKF Knowledge MCP

Concepts

Every artifact is a plain markdown file with a YAML frontmatter block on disk — git-friendly, diffable, and readable/editable by hand. The filesystem is always the source of truth; the MCP server wraps it with structured, validated read/write/list/status tools (see Usage).

Type

Purpose

QA — Question and Answer

Structured elicitation interview (organized by ISO/IEC 25010 characteristic) capturing stakeholder Q&A; usually where a new spec effort starts

GOL — Goal

High-level business goal — what the organization wants to achieve, above individual requirements

PRB — Problem Statement

Six-Sigma-style problem statement describing the problem a goal/requirement addresses

REQ — Requirement

A single system requirement (EARS-style phrasing), classified by ISO/IEC 25010 characteristic

UC — Use Case

Actor/system interaction describing a specific usage scenario

RSK — Risk

Risk-register entry: cause/trigger/consequence, 5x5 probability/impact assessment (initial + residual), TARA response strategy

DEC — Decision

General-purpose decision record (MADR-style), not limited to architecture

ADR — Architecture Decision Record

Architecture-specific decision record; deprecated, use DEC instead

TSK — Task List

Checklist of implementation tasks, e.g. for a requirement or feature

VCR — Verification Case Record

Verifies exactly one REQ or UC; holds a collection of AC-NNN acceptance criteria (each with a DTAIS verification method) plus one overall coverage outcome (full/partial/none)

SOP — Standard Operating Procedure

Step-by-step operational procedure with RASCI responsibility assignment and approval lifecycle

FEAT — Feature

Development work-unit plan/progress tracking (.specmgr/feat/<id>/README.md)

SYSRS — System Requirements Spec

Aggregates GOL/PRB/QA/UC/REQ/RSK/DEC-ADR/VCR via cross-reference lists into one navigable spec; its structure follows ISO/IEC/IEEE 29148:2018 (Systems and software engineering — Life cycle processes — Requirements engineering), with a few specmgr-only additions (e.g. ## Decisions, ## Risks)

TXT — Free-Form Text

Generic, minimally structured catch-all for unstructured markdown that does not warrant a dedicated domain (ad hoc notes, meeting minutes, review/audit write-ups, scratch analysis); free body (any markdown, incl. H2..H6) plus a mandatory newest-first ## Updates log

A typical spec effort starts with a QA elicitation interview, which surfaces GOLs (goals) and PRBs (problems). Those inform REQs and UCs, which get risk-assessed (RSK) and backed by DECisions, verified via one VCR per REQ/UC (each collecting its acceptance criteria), and tracked via TSK task lists. A SYSRS ties all of it together into one ISO/IEC/IEEE 29148-structured specification. SOP and FEAT support the process itself rather than sitting in this chain, and TXT is the free-form catch-all that likewise sits outside it (any markdown that does not warrant a dedicated artifact type).

QA -> GOL / PRB -> REQ / UC -> RSK / DEC -> TSK, VCR -> SYSRS (aggregates all)

Installation

As a library only (no CLI, no MCP server):

pip install biz-dfch-specmgr

With the CLI:

pip install "biz-dfch-specmgr[cli]"

With the MCP server:

pip install "biz-dfch-specmgr[mcp]"

With semantic-similarity search (find_related/find_similar_text, see Semantic Similarity Search below), add the similarity extra on top of mcp:

pip install "biz-dfch-specmgr[mcp,similarity]"

Or with uv:

uv add "biz-dfch-specmgr[cli,mcp,similarity]"

CLI Usage

With the CLI you can generate schema and documentation. We use these commands in pre-commit hooks and ci.yml.

No domain document-management commands (create/update/status/etc.) exist in the CLI yet — those are currently MCP-only, see MCP Server. The CLI covers version, mcp (below), a handful of cross-cutting/doc-generation commands, and the diagram commands — UC → PlantUML (see UC → PlantUML Diagrams) and reference-graph (see Reference Graph Diagrams) (specmgr --help for the full list).

specmgr version
specmgr lint [paths]... [--type T [--dir D]] [--all] [--skip-unrecognized]  # in-tree spec artifact parseability gate: full model-level parse of the paths (type inferred from the layout, --type overrides) or a --type/--all sweep (exit 0 parsed / 1 parse failure or missing mcp extra / 2 usage-or-resolution)

specmgr lint requires the mcp extra, like specmgr diagram (the decoupling is tracked as issue #238).

UC → PlantUML Diagrams

Specmgr can turn UC artifacts into PlantUML diagrams — deterministic use-case and package diagrams, plus sequence diagrams attributed by the agent flow.

Sequence diagrams (agent path). The generate_uc_sequence_diagram MCP prompt (in OpenCode: the self-triggering uc-diagram skill or the /uc-diagram <id> command) runs the frozen rulebook flow: the agent attributes every UNATTRIBUTED message (asking via the question tool when not confident), validates with validate_plantuml, and writes diagrams/uc/<id>.sequence.puml host-natively — no specmgr tool writes .puml files.

Deterministic diagrams + tooling (CLI, deterministic-only — the CLI never creates, overwrites, or diffs the agent-owned *.sequence.puml files):

specmgr diagram uc <id>|all --out <dir> [--check]  # per-UC <id>.usecase.puml + package.puml; --check byte-diffs without writing (exit 0 ok / 1 diff / 2 usage-or-render error)
specmgr plantuml-check <path...>                   # validates any .puml through the chain (exit 0 valid / 1 invalid / 2 source misconfigured-or-unavailable / 3 inconclusive)
specmgr plantuml-encode <path|->                   # offline classic SoWkI… encoding

Validation source. Exactly three env vars, the first one set is the only source used (no fall-through, no PATH discovery, no public default): SPECMGR_PLANTUML_JAR (a plantuml.jar path), SPECMGR_PLANTUML_BIN (an executable speaking the PlantUML CLI contract), SPECMGR_PLANTUML_URL (a PlantUML server base URL). All unset ⇒ offline structure-only validation. Diagram content is never sent to plantuml.com unless you set that URL.

The normative rulebook ships as the specmgr://uc/plantuml resource; the full MCP surface is in docs/MCP.md.

Reference Graph Diagrams

Specmgr can render the deterministic cross-reference network between its artifacts (the <TYPE> <id> tag vocabulary behind the list_references tool) as a PlantUML component diagram — one node per artifact (label = the document title, truncated to the SPECMGR_DIAGRAM_MAX_TITLE limit, default 40), a stereotype + colour per artifact type (a frozen 15-entry palette), nodes grouped into per-domain curly component "TYPE" { … } domain groups, one label-free edge per unique reference ({src} --> {dst}), every edge that lies on a cycle (self-loops and same-SCC ≥ 2) drawn with a red label-free -[#red]-> arrow, and every reference whose target cannot be resolved on disk drawn as a top-level <<UNRESOLVED>> node. Fully deterministic: the same registry and the same arguments always emit byte-identical source.

MCP tools. get_reference_diagram(type, id, depth=1, reverse=False, include_feat=True, title=None) renders one source document's reference neighbourhood (the forward walk to depth, 1..5; reverse=True adds the direct incoming references, additive and direct-only); get_reference_diagram_all(include_feat=True, title=None) renders the registry-wide graph. Both return the PlantUML source directly (no file writes); the scoped tool accepts all 14 domains (adr included) and returns the non-raising ParseFailureResult for an existing-but-broken source; the registry-wide tool never raises for content.

CLI (writes the artifact, deterministic-only):

specmgr diagram refs <type> <id> [--depth N] [--reverse] [--no-feat] [--title T] [--out diagrams/refs] [--check]  # scoped → <id>.refs.puml
specmgr diagram refs all [--no-feat] [--title T] [--out diagrams/refs] [--check]                                   # registry-wide → reference-graph.puml

--no-feat drops every FEAT node except the explicitly requested source (then every edge touching a dropped node). Exit codes: 0 written / no diff, 1 a --check diff (a missing file counts as differing) or the missing mcp extra, 2 a usage-or-resolution error (wrong-format type/id/depth — a bare feat-NNN is rejected, use list_feat to discover the exact id — a missing source, an existing-but-broken source, all combined with a source). Written artifacts carry no structure-only header (checker-clean by construction).

Validation. specmgr plantuml-check <path...> (or the validate_plantuml MCP tool over the source) validates the diagram through the same strict chain as the UC diagrams (the three SPECMGR_PLANTUML_* source env vars, see UC → PlantUML Diagrams; all unset is the offline structure-only floor).

Label limit. SPECMGR_DIAGRAM_MAX_TITLE (an int, default 40) bounds the resolved-node H1 labels (word-boundary cut + …; a set-but-invalid value falls back to 40); the @startuml diagram title is sanitized but never truncated. Its set/valid/default/resolved status is reported by the diagram section of the specmgr://config resource (the raw value is never disclosed).

The normative rulebook ships as the specmgr://reference-graph resource; the full MCP surface is in docs/MCP.md.

MCP Server

Requires the mcp extra. The server exposes resources, tools, and prompts for document management, plus cross-cutting utilities (e.g. markdown formatting).

The full, up-to-date list of every resource, resource template, tool, and prompt — with parameters, MIME types, and descriptions — lives in docs/MCP.md. That document generated from the live server registration by specmgr mcp-docs and kept in sync by a pre-commit hook and a CI check.

Environment Variables

Every document type stores its .md files in a base directory on disk — the file is always the source of truth, re-read and re-parsed on every tool call, so hand-editing a file between calls is safe.

  • ADRs: base directory defaults to docs/adr, configurable via the SPECMGR_ADR_DIR environment variable. This is ADR-specific and not shared with other document types.

  • Requirements (REQ) and future document types: share one root directory, configurable via the SPECMGR_DOCS_DIR environment variable (default docs), with each type's own subdirectory appended automatically (e.g. docs/req for requirements).

  • Features (FEAT): base directory defaults to .specmgr/feat, configurable via the SPECMGR_FEAT_DIR environment variable. This is FEAT-specific, like ADRs above, and not shared via SPECMGR_DOCS_DIR.

  • PlantUML validation source (UC diagrams): SPECMGR_PLANTUML_JAR (a plantuml.jar path), SPECMGR_PLANTUML_BIN (an executable speaking the PlantUML CLI contract), and SPECMGR_PLANTUML_URL (a PlantUML server base URL) — the first one set is the only source used (no fall-through, no PATH discovery, no public default); all unset is the offline structure-only floor. See UC → PlantUML Diagrams for the how-to.

  • Reference-graph diagram label limit: SPECMGR_DIAGRAM_MAX_TITLE (an int, default 40) — the maximum length of a resolved node's H1 label in the get_reference_diagram / get_reference_diagram_all PlantUML diagrams (word-boundary cut + …; the diagram title itself is never truncated). A set-but-invalid value (empty, non-int, or < 1) falls back to 40. Its set/valid/default/resolved status is reported by the diagram section of the specmgr://config resource (the raw value is never disclosed).

The specmgr mcp command's own transport settings are likewise environment-driven: SPECMGR_MCP_TRANSPORT selects the transport (default stdio; the allowed values are stdio, sse, and streamable-http), and SPECMGR_MCP_HOST (default localhost) and SPECMGR_MCP_PORT (default 8000) are the bind address and TCP port, used only in sse/streamable-http mode. The command's --transport/--host/--port flags take precedence over the variables — see Start the MCP Server below for the options table.

The find_related/find_similar_text tools (semantic-similarity search, requires the similarity extra) are turned off by setting the SPECMGR_SIMILARITY_DISABLED environment variable (any value; presence-based) — see Semantic Similarity Search below for the full behavior (install, the non-blocking background warmup at server startup, the model cache location, and the structured unavailable result both tools return when the feature is disabled or the backend/model fails to load).

The semantic-similarity feature's model cache directory is the FASTEMBED_CACHE_PATH environment variable (the third-party fastembed backend's own variable, not a SPECMGR_* one): an unset or empty value falls back to <tempdir>/fastembed_cache (e.g. /tmp/fastembed_cache on Linux). The resolved directory is reported by the similarity section of the specmgr://config resource — see Semantic Similarity Search below for the full behavior.

The feat domain's two background cache-warming phases (frontmatter and full-parse, run by the same unified server-startup warmup thread as the similarity warmup) are likewise turned off by setting the SPECMGR_FEAT_WARMUP_DISABLED environment variable (any value; presence-based) — see the background warmup paragraph of Semantic Similarity Search below for the thread's full behavior and both opt-out flags.

SPECMGR_TESTS_NO_DOTENV is a presence-based test/CI sentinel (no default; never set outside test runs): set to any value to skip the CLI's module-level default .env load entirely, so a test run sees no local (gitignored) .env configuration whatever the developer's machine has.

All of the base directories above are resolved relative to the MCP server process's own current working directory unless overridden by their env var (or, for the shared SPECMGR_DOCS_DIR root, unless the server was started with --directory/uv run --directory targeting your project). If that CWD is not what you expect — e.g. after adding specmgr to an MCP host per Add to OpenCode below — read the specmgr://config resource to see every domain's actually-resolved absolute base directory and whether its env var is explicitly set, without needing shell access to the server's host.

The find_related and find_similar_text tools rank documents by semantic similarity using local sentence embeddings (no API keys, no network at inference time). find_related(type, id) finds the documents most related to an existing document; find_similar_text(query) finds the documents most similar to a free-form query — e.g. a draft you have not saved yet, for pre-creation dedup/discovery. Both tools always appear in the tool list, regardless of whether the feature is installed or enabled: when it is unavailable, each call returns a structured {available: false, reason, message} result instead of raising.

Install. The tools need the optional similarity extra on top of mcp — the exact commands are in Installation (pip install "biz-dfch-specmgr[mcp,similarity]" or uv add "biz-dfch-specmgr[cli,mcp,similarity]"). The backend is fastembed running BAAI/bge-small-en-v1.5 (CPU-only, ONNX Runtime, 384-dim).

Model download and cache. The model is downloaded once from the Hugging Face Hub the first time it is loaded (network required at that point only; after that, inference is fully offline). It is cached in $FASTEMBED_CACHE_PATH if that environment variable is set, otherwise in <tempdir>/fastembed_cache (e.g. /tmp/fastembed_cache on Linux).

Background warmup at server startup. Unless both SPECMGR_FEAT_WARMUP_DISABLED and SPECMGR_SIMILARITY_DISABLED are set, the server starts a single background daemon thread at startup that runs three warmup phases, strictly in order: the feat frontmatter phase (cheap: frontmatter plus H1 only), the feat full-parse phase (expensive: the full feat corpus), and the similarity phase, which embeds the full document corpus into the in-memory cache. Each phase is independently gated by its own opt-out flag: SPECMGR_FEAT_WARMUP_DISABLED (any value; presence-based) skips the two feat phases, and SPECMGR_SIMILARITY_DISABLED (any value; presence-based) skips the similarity phase, regardless of whether the similarity extra is installed. With the extra missing, or with the model failing to load, the similarity phase runs the availability probe inside itself and exits immediately without cache writes (still never raising). The thread is non-blocking — server startup never waits for it (the model load/download, if needed, happens inside the thread, not on the startup path) — and a mid-warmup failure is logged and swallowed, leaving the cache partially warm. Its purpose is to keep the first tool call fast: against a cold cache, list_feat scans and re-parses the whole feat corpus on the request path, and the similarity tools embed the corpus on demand — both can cost minutes of CPU.

Opt-out. Set SPECMGR_SIMILARITY_DISABLED (any value; presence-based) to disable the feature without uninstalling the extra — e.g. to skip the model download entirely, on a host where the model was never downloaded, or to free the memory the model and cache use. Both tools stay registered either way. Set SPECMGR_FEAT_WARMUP_DISABLED (any value; presence-based) to skip the two feat cache-warming phases without any other effect — e.g. on a host where list_feat/get_feat are rarely called. With both opt-out flags set, no warmup thread is started at all.

Introspection. Read the specmgr://config resource for the feature's static configuration — its similarity section reports whether the similarity extra is installed, whether the opt-out flag is set, the model name, and the resolved cache directory (static facts only; reading the resource never loads the model or creates the cache directory), and its feat_warmup_disabled field reports whether the feat warmup opt-out flag is set — and the specmgr://version resource for the installed fastembed version (null when the extra is not installed).

Start the MCP Server

Start the server with the mcp command:

specmgr mcp

By default it runs over stdio, for MCP hosts that launch it as a subprocess (see Add to OpenCode below). It can also run over SSE/network:

specmgr mcp --transport sse --host localhost --port 8000

Or over the spec-current streamable-http transport, which replaces the legacy/deprecated sse transport for HTTP deployments:

specmgr mcp --transport streamable-http --host localhost --port 8000

Option

Env var

Default

Description

--transport / -t

SPECMGR_MCP_TRANSPORT

stdio

Transport mode: stdio, sse, or streamable-http

--host / -h

SPECMGR_MCP_HOST

localhost

Bind address (SSE/streamable-http mode only)

--port / -p

SPECMGR_MCP_PORT

8000

TCP port (SSE/streamable-http mode only)

Add to OpenCode

To add the specmgr MCP server to your OpenCode configuration:

  1. Open your OpenCode config file (typically ~/.config/opencode/opencode.json or ~/.config/opencode/opencode.jsonc)

  2. Add a configuration to the mcp section (and use it via stdio).

    A bare uvx --from biz-dfch-specmgr[mcp] specmgr mcp command with no --directory and no SPECMGR_*_DIR env vars is unsafe: the server resolves every base directory (docs, docs/adr, .specmgr/feat, ...) relative to its own process's current working directory, which an MCP host is free to launch from anywhere — not necessarily your project root. Pick one of the two options below instead of the plain form:

    Option A — pin the working directory with --directory (a global uv/uvx flag, so it must come before --from):

    "specmgr": {
      "type": "local",
      "enabled": true,
      "command": [
        "uvx",
        "--directory",
        "<path-to-your-project>",
        "--from",
        "biz-dfch-specmgr[mcp]",
        "specmgr",
        "mcp"
      ]
    }

    To also enable the Semantic Similarity Search feature (find_related/ find_similar_text), change [mcp] to [mcp,similarity] in the --from value above — see that section for install/runtime-activation details.

    Option B — set the directory env vars explicitly instead of (or in addition to) --directory:

    "specmgr": {
      "type": "local",
      "enabled": true,
      "command": [
        "uvx",
        "--from",
        "biz-dfch-specmgr[mcp]",
        "specmgr",
        "mcp"
      ],
      "environment": {
        "SPECMGR_DOCS_DIR": "<path-to-your-project>/docs",
        "SPECMGR_ADR_DIR": "<path-to-your-project>/docs/adr",
        "SPECMGR_FEAT_DIR": "<path-to-your-project>/.specmgr/feat"
      }
    }

    The same [mcp] -> [mcp,similarity] change in the --from value applies here too, to enable Semantic Similarity Search.

    Either option (or both together) makes the resolved base directories independent of wherever the MCP host happens to launch the server from. Whichever you choose, you can confirm it worked by reading the specmgr://config resource, which reports the actually-resolved absolute base directory for every domain and whether its env var is explicitly set.

    Option C — run from a local source checkout (development). Options A and B both use uvx (uv tool run), which resolves biz-dfch-specmgr[...] as a named package from the configured index (PyPI by default) into an ephemeral tool environment — they always run the published release, never local/unreleased changes. To instead run the project at a specific directory on disk — using its own pyproject.toml, lockfile, and source tree directly, including unreleased/dev-branch commits — use uv run --directory <path-to-your-checkout> (no --from, not uvx):

    "specmgr": {
      "type": "local",
      "enabled": true,
      "command": [
        "uv",
        "run",
        "--directory",
        "<path-to-your-checkout>",
        "--extra",
        "mcp",
        "--extra",
        "similarity",
        "specmgr",
        "mcp"
      ]
    }

    This is the mechanism for contributors/developers pointing OpenCode at their own git checkout instead of the published package. Repeatable --extra flags (or --all-extras for everything) control which optional dependencies get synced into that local run, independent of what's published on PyPI — drop --extra similarity if you don't need Semantic Similarity Search.

  3. Save the file and restart OpenCode

Installing the specmgr Harness Files

Beyond the MCP server itself, this repo ships its agent harness files — every markdown file under .opencode/{agent,command,skill}/ (the subagents, slash commands, and skills the commands above drive) — as a single packaged bundle: the specmgr://harness-bundle MCP resource (application/json), an instructions field carrying the complete install protocol plus a files array with each file's path (relative to .opencode/), sha256, and verbatim content. The bundle is generated from the repo's own tree by the specmgr harness-bundle CLI command and drift-guarded by a pre-commit hook and a CI check, so the published bundle always matches the released .opencode/ files exactly. The Add to OpenCode section above covers the MCP server setup the harness files pair with — it is not repeated here.

Agent-driven install (recommended). Ask your agent to install the specmgr harness files: it reads specmgr://harness-bundle (discoverable via the MCP host's resource listing) and follows the bundle's instructions field — picking its harness config root via the harness's own configuration skill (OpenCode: customize-opencode), writing every file byte-exact (skipping sha256-identical ones, asking you via the question tool before overwriting a differing one), re-verifying every write, and reporting installed/skipped/updated/failed per file. The install never reads, writes, or merges opencode.json.

What gets installed. Every .opencode/agent/*.md subagent, .opencode/command/*.md slash command, and .opencode/skill/*/SKILL.md skill of the published package — the bundle is all-inclusive on purpose, since the commands' dependency closure (e.g. /implement-feature driving phase-orchestrator → phase-implementer) needs the existing agents too.

Manual / Claude Code path. Claude Code's structurally similar .claude/agents/*.md and .claude/commands/*.md conventions (one markdown file per agent/command, YAML frontmatter plus prose body) make the bundle's files hand-portable: copy each file's frontmatter and body into the matching .claude/ file, translating the permission model. The two models do not map one-to-one — OpenCode carries per-pattern bash/edit/read permission rules (last matching rule wins), while Claude Code's frontmatter takes a coarse tool allow-list (tools:) — so a hand-ported file is a simplified equivalent, not an automatic translation:

# .claude/agents/ref-finder.md -- hand-ported from the bundle's
# agent/ref-finder.md: the description and the prose body are unchanged,
# and the OpenCode per-pattern permission rules (read/glob/grep/list
# allowed, edit/write/task/bash denied) collapse to a coarse read-only
# tool allow-list.
---
name: ref-finder
description: >-
  Lists and resolves the artifacts a single specmgr document references --
  its `<TYPE> <id>` cross-reference lines -- by calling the `list_references`
  MCP tool and reporting each referenced artifact's `type`/`id`/`title`/`path`,
  flagging any that could not be resolved on disk. Read-only -- never edits,
  writes, or commits.
tools: Read, Grep, Glob, mcp__specmgr__list_references
---

<the file's prose body, unchanged>

OpenCode's skills (.opencode/skill/*/SKILL.md) have no Claude Code counterpart with the same self-triggering semantics; keep them as plain reference documents if you need their content.

Usage

Once connected, your AI assistant sees two kinds of tools per document domain: per-domain tools (create_<d>, get_<d>, list_<d>, parse_<d>, plus get_<d>_example/get_<d>_template), and generic dispatch tools that take a type= parameter and behave the same way across every domain: update (whole-body or line-range edits), set_status, set_classification, delete, and validate (ADR is the one exception, predating this pattern, with its own dedicated tools instead). See docs/MCP.md for the full list.

The server also ships prompts (e.g. create_req, update_req, implement_task) — guided, multi-turn interview flows that are the intended way to author documents conversationally, rather than calling tools one by one. Most MCP hosts surface these as slash commands or a prompt picker.

Day to day, you don't need to know any tool names at all — just ask your connected AI assistant in plain language, e.g.:

  • "Create a new goal for reducing customer onboarding time."

  • "List all open risks and show me the high-severity ones."

  • "Draft a task list for requirement <id>."

To resolve the cross-references an artifact already carries, see Referencing Artifacts.

Referencing Artifacts

specmgr documents cross-reference each other via <TYPE> <uuid> lines in the structured locations (VCR's ## Verifies, SYSRS's per-section bullet lists, DEC's ## Related Artifacts) and — anywhere in free-form prose — via any of the reference tags, including a FEAT tag carrying a feature's own id (the full feat-NNN-slug or the bare feat-NNN number; the structured locations above remain uuid-only). The list_references MCP tool resolves those references: point your assistant at any artifact (<type> <id>) and it lists every cross-reference tag in the artifact's body, each resolved to the referenced document's type, id, title (its H1), and on-disk path. Results are paged like every list_* tool; references that do not resolve on disk come back as rows carrying an error (not a failure), and any other free-form or non-conforming text is ignored.

In opencode, the /refs <type> <id> slash command (.opencode/command/refs.md) wraps the same tool: it delegates to the read-only ref-finder subagent (.opencode/agent/ref-finder.md), which calls list_references and reports the rows, flagging any NOT FOUND references. /refs requires opencode — the tool itself is plain MCP, callable directly from any MCP client.

Also in opencode, the project-local specmgr-refs skill (.opencode/skill/specmgr-refs/SKILL.md) is the agent-initiated surface: it loads itself when a task matches reference-retrieval phrasings, routes single-document requests to the list_references tool, the ref-finder subagent, or the /refs command, and codifies the graph, batch, and reverse workflows that /refs and ref-finder do not cover.

Multi-Language Artifacts

The same artifact can exist in several languages. English is the default and keeps its location (docs/<type>/); every other language is a docs/<type>/<language>/ subfolder (<language> is a lowercase two-letter code such as de) that holds the variant under the exact same file name as its English source, with the same id and the identical frontmatter and body structure. The language lives in the file's location only — there is no language frontmatter field. It applies to the twelve domains req, uc, tsk, qa, prb, gol, rsk, dec, sop, vcr, sysrs and txt.

feat and adr are always English, permanently: no feat or adr tool, resource or prompt takes a language, and the generic tools reject a non-default language for those two types (language="en", the default, is accepted and behaves as before).

  • Reading and writing. get_<d>, list_<d> and create_<d>, and the generic update, edit, set_status, set_classification and delete tools take an optional language (default "en"; leaving it out behaves exactly as before). list_<d>(language="de") lists only the German variants. A variant is created from an existing English document: create_<d>(content, language="de", id=<id of the EN source>). validate stays English-only.

  • Headings. A translated heading is mapped back to its canonical English heading through a per-language table, so a variant is checked against the exact same schema as the English document. The file keeps its translation. Closed-vocabulary values (status, classification, RSK Strategy/Probability/Impact, VCR Coverage and AC-NNN (Method), Option N:/Step N: tokens, [ ]/[x], cross-reference tags, SOP lifecycle values) are never translated, and neither is the PRB problem_statement sentence.

  • Translating. The MCP prompt translate_artifact (type, id, language, optional source_language) drives the translation; the resources specmgr://i18n and specmgr://i18n/headings/{language} document the rules and return a language's heading table. In opencode: the translate-artifact skill and /translate <type> <id> <language> [source_language]. No tool translates by itself — the model does the translating around the deterministic tools.

  • Status and drift. list_languages(type, id) reports the languages an artifact exists in. check_translation_drift(type, id, language) reports structural drift (missing, extra or reordered sections) and staleness (the English source is newer than the translation). In opencode: the translation-status skill and /translation-status <type> <id> [language].

  • English-only search and references. find_related, find_similar_text, list_references and get_reference_diagram(_all) never see a language variant: a hit or a resolved reference is always the English document.

  • Adding a language. Add one file, src/biz/dfch/specmgr/general/data/headings/<language>.json — a flat JSON object {"<translated heading>": "<canonical English heading>"} that covers exactly the fixed-text headings of the document models, with distinct values. No code change is needed. The drift test tests/general/models/test_heading_table_drift.py checks every shipped table and names each missing or surplus heading. Without a table file a language code is rejected when a document of that language is read or written.

  • Titles and headings. Only the section headings (levels 2-6) are mapped through the table. The document title (the level-1 # line) is free text in every language and is never looked up or rewritten, even when it equals a table entry (a German title Quelle stays Quelle).

Development

Install dev dependencies

uv sync --all-extras

Install pre-commit hooks (one-time)

uv run --frozen pre-commit install

uv sync only installs Python dependencies into the venv — it never registers the hooks from .pre-commit-config.yaml with git, so run this once per clone before your first commit.

Run linters

uv run --frozen ruff format --check
uv run --frozen ruff check
uv run --frozen pylint $(git ls-files '*.py')

Run tests

uv run --frozen pytest -n auto --cov=src --cov-report=

Testing

You can exercise the MCP server directly with the MCP Inspector, in either its CLI (scriptable) or TUI (interactive terminal) client.

Prerequisites

  • The mcp extra installed (see Installation), so .venv/bin/specmgr exists.

  • npx (ships with Node.js, version 22.19.0 or newer) — no separate Inspector install is required, it runs on demand via npx @modelcontextprotocol/inspector.

Point the Inspector at the venv's specmgr binary directly (rather than at uv run specmgr mcp) so none of uv run's own flags (e.g. --frozen) are mistaken for Inspector flags:

npx @modelcontextprotocol/inspector --tui .venv/bin/specmgr mcp
npx @modelcontextprotocol/inspector --cli .venv/bin/specmgr mcp --method tools/list

CLI examples

Each CLI invocation connects, runs one request, prints the result, and exits — useful for scripting or a quick smoke test.

Get the specmgr://version resource:

npx @modelcontextprotocol/inspector --cli .venv/bin/specmgr mcp \
  --method resources/read --uri specmgr://version

List task lists via the list_tsk tool:

npx @modelcontextprotocol/inspector --cli .venv/bin/specmgr mcp \
  --method tools/call --tool-name list_tsk

Get one task list via the get_tsk tool (replace <id> with a real task list id from the list_tsk output above):

npx @modelcontextprotocol/inspector --cli .venv/bin/specmgr mcp \
  --method tools/call --tool-name get_tsk --tool-arg id=<id>

Add --format json to any of the above to get machine-readable output, e.g. piped into jq.

Connecting with the TUI

npx @modelcontextprotocol/inspector --tui .venv/bin/specmgr mcp

This launches the server as an ad-hoc stdio target and opens the terminal UI with it preselected (unlike the CLI, the TUI has no --server <name> flag — it lists whichever servers are available and you pick one, though with a single ad-hoc target there is nothing else to pick). Press c to connect, then use the tabs to explore:

  • t — Tools tab: browse and call tools (e.g. get_tsk) with a form-based input.

  • r — Resources tab: browse and read resources (e.g. specmgr://version, specmgr://iso25010).

  • m — Prompts tab: list and render prompts.

  • p — Protocol tab: raw JSON-RPC request/response history, useful for debugging.

  • o — Console tab: stderr from the connected specmgr mcp process (tracebacks land here).

  • c / d — connect / disconnect; Esc or Ctrl+C — exit.

The TUI requires a real TTY (raw-mode support) and does not run in a headless CI job — use the CLI client there instead.

Make a Release

The normative release procedure is the SOP Perform a release of biz.dfch.SpecMgr (SOP 98537416). Where this section, the script, or the command ever disagree with the SOP, the SOP wins.

/release [X.Y.Z | patch | minor | major] [--dry-run]

The command drives the staged script and performs the SOP's agent-judgment steps: it confirms the resolved version with you, curates the changelog's [Unreleased] section, pauses at the merge gate before dev is merged into main, and triages failures without ever auto-retrying.

Using the script directly

Each SOP step maps to a deterministic, idempotent stage (the SOP carries a manual fallback command for every step):

scripts/release.sh resolve minor         # print the target version (e.g. 0.15.0); no mutation
scripts/release.sh precheck 0.15.0       # fail-fast pre-release checks, incl. the feat closeout gate and the previous-release branch cleanup
scripts/release.sh bump 0.15.0           # pyproject.toml + uv.lock
scripts/release.sh changelog 0.15.0      # [Unreleased] -> dated section
scripts/release.sh commit-push 0.15.0    # 3-file release commit, push dev, wait for CI
scripts/release.sh pr-create 0.15.0      # dev->main release PR, wait for checks (no merge)
scripts/release.sh pr-merge 0.15.0       # ff-only merge (after maintainer go-ahead)
scripts/release.sh tag-push 0.15.0       # tag on main, push the tag, back to dev
scripts/release.sh publish-wait 0.15.0   # the four publish.yml jobs
scripts/release.sh release-notes 0.15.0  # verify the release + set the GH release notes
scripts/release.sh status 0.15.0         # where does this release stand?
scripts/release.sh all 0.15.0            # the whole chain, TTY only (interactive merge gate)

precheck ends with two sub-items added by feat-233 (issue #233): the feat closeout gate — every .specmgr/feat/feat-*/ folder shipped in the current release window (since the last tag) must be closed out to status: done in its README frontmatter, and a violation fails the precheck with one actionable line per offending feat (remediation: close out the named feats to done on dev, commit, re-run precheck); and the previous-release branch cleanup — stale feat-*/fix/* branches from the previous release or older are deleted on the local and origin side, each deletion proven by the branch's merged PR and guarded by four hazard checks (--dry-run makes this sub-item report-only: the would-delete/would-keep table, no ref is deleted).

Changelog curation (SOP step 3) is an agent or manual step: the changelog stage only moves the already-curated [Unreleased] section into its dated form.

Manual fallback

Follow the SOP step by step — each step carries a Manual fallback paragraph. The essentials: bump the version in pyproject.toml and move the [Unreleased] section of CHANGELOG.md into a new dated ## [x.y.z] - YYYY-MM-DD section; uv lock; commit exactly pyproject.toml + uv.lock + CHANGELOG.md as chore(release): bump version to vX.Y.Z and push to dev; once CI is green, open the dev → main pull request and merge it fast-forward-only (git merge --ff-only dev — never a merge commit or squash: main must stay a strict ancestor of dev); then create git tag vX.Y.Z on main, push the tag, and wait for the publish workflow.

Note: .github/workflows/publish.yml handles the rest of the release automatically once the tag above is pushed — it builds and publishes the sdist/wheel to TestPyPI then PyPI via Trusted Publishing (OIDC, no stored token), creates the matching GitHub Release with the built artifacts attached, and publishes server.json (repo root, the MCP Registry publisher manifest — see the server.json format spec) to the MCP Registry via mcp-publisher/GitHub OIDC. biz-dfch-specmgr is live on PyPI and in the MCP Registry as of v0.1.0.

License

AGPL-3.0-or-later

Available Tools

124 tools
check_translation_driftCheck a translation for structural drift and stalenessA

Read-only. Compare a translated language variant with its source document (the 'en' source by default) and report both drift kinds in one structured, non-raising TranslationDriftResult. type is one of req, uc, tsk, qa, prb, gol, rsk, dec, sop, vcr, sysrs, txt (feat and adr documents are always English and are rejected with a ValueError); id the artifact's id (shared by both languages); language the translated variant (never 'en'); source_language (default 'en') the language compared against. STRUCTURAL drift: every section heading of both documents is mapped back to canonical English through the language's heading table (the free text of Option N:/Step N:/Extension N./AC-NNN (Method): headings is ignored, only the schema-constant token is compared; timestamp-led update entries are not compared) and structural lists each missing (a source section the variant lacks), extra (a variant section the source lacks) and reordered section with its canonical heading, path, level and positions; a document that fails to parse is an unparseable finding carrying the parse error (byte-identical to list_'s failure text), never raised. STALENESS: stale is true iff the source's frontmatter updated is strictly newer than the variant's (compared as offset-aware datetimes; equal is not stale), with both timestamps, the signed source_ahead_by_seconds, and staleness_error when a timestamp is missing or unparseable. in_sync is true iff there is no structural finding, the variant is not stale and staleness could be determined. Wording, prose, frontmatter values and prb's English problem_statement are never compared. A source or variant that does not exist at all raises the domain's not-found error; invalid type/id/language (path-injection, wrong shape, a language without a heading table, language 'en', equal languages) is a ValueError raised before any file access. It writes, creates and invalidates nothing; to translate or update a variant use the translate_artifact prompt.

Inputs: id (required), type (required), language (required), source_language. Returns: The ``check_translation_drift`` tool's non-raising result: structural drift and…

create_adrCreate ADRA

Create a new ADR: assigns a fresh id, derives a filename from the title, validates, renders, and writes the new document to the ADR base directory. There is deliberately no specmgr://adr/schema resource -- this tool's own typed input parameters (frontmatter: AdrFrontmatter, body: AdrBody) are the authoritative ADR structure reference.

Inputs: body (required), frontmatter (required). Returns: A full ADR document: YAML frontmatter and body. This is the structured object t…

create_decCreate decisionA

Create a new decision: assigns a fresh id, derives a filename from the body's H1 title, validates the submitted body-only content, and writes the new document to the decision base directory. A content-validation failure of the submitted body returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); nothing is written in that case. The structure reference is the get_dec_schema tool (the generated JSON Schema); the get_dec_template/get_dec_example tools return the authoring template and a complete sample. Returns the newly created document's frontmatter only (no body); use the corresponding get_dec tool to fetch the full document afterward. The optional language parameter (default 'en', feat-197-i18n) selects the language to create the document in: omitted/'en' = a new flat docs/dec/ document with a fresh id, exactly the default behavior (id must then be omitted); a non-default ISO 639-1-shaped lowercase two-letter code (e.g. 'de') creates a TRANSLATION of an existing decision instead: id -- the shared id of the existing EN source document -- is then required (no fresh id is assigned; the variant carries the same id), the EN source must exist (else DecNotFoundError), and the variant is written to docs/dec/{language}/ under the EN source's exact file name with status draft; content is validated through that language's heading-mapping table (translated headings are accepted) and persisted verbatim. An existing variant of that id and language is never overwritten (FileExistsError). A malformed language, one with no packaged heading-mapping table, a missing id for a non-default language, or an id given for the default language is a ValueError raised before any file access.

Inputs: content (required), id, language. Returns: result

create_featCreate featureA

Create a new feature: assigns a fresh id (caller-chosen via the optional 'id' parameter, or defaulted to feat-0- when omitted -- no max+1 auto-generation), derives a filename from the body's H1 title, validates the submitted body-only content, and writes the new document to the feature base directory. A content-validation failure of the submitted body returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); nothing is written in that case. Content validation runs BEFORE the id-shape ValueError and already-existing-id/folder FileExistsError guards, so a compound failure (invalid content + a malformed or already-existing id) returns the ValidateResult and the guard never runs in that call; each guard still raises whenever the content is valid. The structure reference is the get_feat_schema tool (the generated JSON Schema); the get_feat_template/get_feat_example tools return the authoring template and a complete sample. Returns the newly created document's frontmatter only (no body); use the corresponding get_feat tool to fetch the full document afterward.

Inputs: content (required), id. Returns: result

create_golCreate goalA

Create a new goal: assigns a fresh id, derives a filename from the body's H1 title, validates the submitted body-only content, and writes the new document to the goal base directory. A content-validation failure of the submitted body returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); nothing is written in that case. The structure reference is the get_gol_schema tool (the generated JSON Schema); the get_gol_template/get_gol_example tools return the authoring template and a complete sample. Returns the newly created document's frontmatter only (no body); use the corresponding get_gol tool to fetch the full document afterward. The optional language parameter (default 'en', feat-197-i18n) selects the language to create the document in: omitted/'en' = a new flat docs/gol/ document with a fresh id, exactly the default behavior (id must then be omitted); a non-default ISO 639-1-shaped lowercase two-letter code (e.g. 'de') creates a TRANSLATION of an existing goal instead: id -- the shared id of the existing EN source document -- is then required (no fresh id is assigned; the variant carries the same id), the EN source must exist (else GolNotFoundError), and the variant is written to docs/gol/{language}/ under the EN source's exact file name with status draft; content is validated through that language's heading-mapping table (translated headings are accepted) and persisted verbatim. An existing variant of that id and language is never overwritten (FileExistsError). A malformed language, one with no packaged heading-mapping table, a missing id for a non-default language, or an id given for the default language is a ValueError raised before any file access.

Inputs: content (required), id, language. Returns: result

create_prbCreate problem statementA

Create a new Problem Statement: assigns a fresh id, derives a filename from the body's H1 title, validates the submitted body-only content, and writes the new document to the problem statement base directory. A content-validation failure of the submitted body returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); nothing is written in that case. The structure reference is the get_prb_schema tool (the generated JSON Schema); the get_prb_template/get_prb_example tools return the authoring template and a complete sample. Returns the newly created document's frontmatter only (no body); use the corresponding get_prb tool to fetch the full document afterward. The optional language parameter (default 'en', feat-197-i18n) selects the language to create the document in: omitted/'en' = a new flat docs/prb/ document with a fresh id, exactly the default behavior (id must then be omitted); a non-default ISO 639-1-shaped lowercase two-letter code (e.g. 'de') creates a TRANSLATION of an existing problem statement instead: id -- the shared id of the existing EN source document -- is then required (no fresh id is assigned; the variant carries the same id), the EN source must exist (else PrbNotFoundError), and the variant is written to docs/prb/{language}/ under the EN source's exact file name with status draft; content is validated through that language's heading-mapping table (translated headings are accepted) and persisted verbatim. An existing variant of that id and language is never overwritten (FileExistsError). A malformed language, one with no packaged heading-mapping table, a missing id for a non-default language, or an id given for the default language is a ValueError raised before any file access.

Inputs: content (required), id, language. Returns: result

create_qaCreate QA documentA

Create a new Question and Answer (QA) document: assigns a fresh id, derives a filename from the body's H1 title, validates the submitted body-only content, and writes the new document to the QA base directory. A content-validation failure of the submitted body returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); nothing is written in that case. The structure reference is the get_qa_schema tool (the generated JSON Schema); the get_qa_template/get_qa_example tools return the authoring template and a complete sample. Returns the newly created document's frontmatter only (no body); use the corresponding get_qa tool to fetch the full document afterward. The optional language parameter (default 'en', feat-197-i18n) selects the language to create the document in: omitted/'en' = a new flat docs/qa/ document with a fresh id, exactly the default behavior (id must then be omitted); a non-default ISO 639-1-shaped lowercase two-letter code (e.g. 'de') creates a TRANSLATION of an existing QA document instead: id -- the shared id of the existing EN source document -- is then required (no fresh id is assigned; the variant carries the same id), the EN source must exist (else QaNotFoundError), and the variant is written to docs/qa/{language}/ under the EN source's exact file name with status draft; content is validated through that language's heading-mapping table (translated headings are accepted) and persisted verbatim. An existing variant of that id and language is never overwritten (FileExistsError). A malformed language, one with no packaged heading-mapping table, a missing id for a non-default language, or an id given for the default language is a ValueError raised before any file access.

Inputs: content (required), id, language. Returns: result

create_reqCreate requirementA

Create a new requirement: assigns a fresh id, derives a filename from the body's H1 title, validates the submitted body-only content, and writes the new document to the requirement base directory. A content-validation failure of the submitted body returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); nothing is written in that case. The structure reference is the get_req_schema tool (the generated JSON Schema); the get_req_template/get_req_example tools return the authoring template and a complete sample. Returns the newly created document's frontmatter only (no body); use the corresponding get_req tool to fetch the full document afterward. The optional language parameter (default 'en', feat-197-i18n) selects the language to create the document in: omitted/'en' = a new flat docs/req/ document with a fresh id, exactly the default behavior (id must then be omitted); a non-default ISO 639-1-shaped lowercase two-letter code (e.g. 'de') creates a TRANSLATION of an existing requirement instead: id -- the shared id of the existing EN source document -- is then required (no fresh id is assigned; the variant carries the same id), the EN source must exist (else ReqNotFoundError), and the variant is written to docs/req/{language}/ under the EN source's exact file name with status draft; content is validated through that language's heading-mapping table (translated headings are accepted) and persisted verbatim. An existing variant of that id and language is never overwritten (FileExistsError). A malformed language, one with no packaged heading-mapping table, a missing id for a non-default language, or an id given for the default language is a ValueError raised before any file access.

Inputs: content (required), id, language. Returns: result

create_rskCreate riskA

Create a new risk: assigns a fresh id, derives a filename from the body's H1 title, validates the submitted body-only content, and writes the new document to the risk base directory. A content-validation failure of the submitted body returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); nothing is written in that case. The structure reference is the get_rsk_schema tool (the generated JSON Schema); the get_rsk_template/get_rsk_example tools return the authoring template and a complete sample. Returns the newly created document's frontmatter only (no body); use the corresponding get_rsk tool to fetch the full document afterward. The optional language parameter (default 'en', feat-197-i18n) selects the language to create the document in: omitted/'en' = a new flat docs/rsk/ document with a fresh id, exactly the default behavior (id must then be omitted); a non-default ISO 639-1-shaped lowercase two-letter code (e.g. 'de') creates a TRANSLATION of an existing risk instead: id -- the shared id of the existing EN source document -- is then required (no fresh id is assigned; the variant carries the same id), the EN source must exist (else RskNotFoundError), and the variant is written to docs/rsk/{language}/ under the EN source's exact file name with status open; content is validated through that language's heading-mapping table (translated headings are accepted) and persisted verbatim. An existing variant of that id and language is never overwritten (FileExistsError). A malformed language, one with no packaged heading-mapping table, a missing id for a non-default language, or an id given for the default language is a ValueError raised before any file access.

Inputs: content (required), id, language. Returns: result

create_sopCreate Standard Operating ProcedureA

Create a new Standard Operating Procedure: assigns a fresh id, derives a filename from the body's H1 title, validates the submitted body-only content, and writes the new document to the SOP base directory. A content-validation failure of the submitted body returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); nothing is written in that case. The structure reference is the get_sop_schema tool (the generated JSON Schema); the get_sop_template/get_sop_example tools return the authoring template and a complete sample. Returns the newly created document's frontmatter only (no body); use the corresponding get_sop tool to fetch the full document afterward. The optional language parameter (default 'en', feat-197-i18n) selects the language to create the document in: omitted/'en' = a new flat docs/sop/ document with a fresh id, exactly the default behavior (id must then be omitted); a non-default ISO 639-1-shaped lowercase two-letter code (e.g. 'de') creates a TRANSLATION of an existing Standard Operating Procedure instead: id -- the shared id of the existing EN source document -- is then required (no fresh id is assigned; the variant carries the same id), the EN source must exist (else SopNotFoundError), and the variant is written to docs/sop/{language}/ under the EN source's exact file name with status draft; content is validated through that language's heading-mapping table (translated headings are accepted) and persisted verbatim. An existing variant of that id and language is never overwritten (FileExistsError). A malformed language, one with no packaged heading-mapping table, a missing id for a non-default language, or an id given for the default language is a ValueError raised before any file access.

Inputs: content (required), id, language. Returns: result

create_sysrsCreate System Requirements SpecificationA

Create a new System Requirements Specification: assigns a fresh id, derives a filename from the body's H1 title, validates the submitted body-only content, and writes the new document to the System Requirements Specification base directory. A content-validation failure of the submitted body returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); nothing is written in that case. The structure reference is the get_sysrs_schema tool (the generated JSON Schema); the get_sysrs_template/get_sysrs_example tools return the authoring template and a complete sample. Returns the newly created document's frontmatter only (no body); use the corresponding get_sysrs tool to fetch the full document afterward. The optional language parameter (default 'en', feat-197-i18n) selects the language to create the document in: omitted/'en' = a new flat docs/sysrs/ document with a fresh id, exactly the default behavior (id must then be omitted); a non-default ISO 639-1-shaped lowercase two-letter code (e.g. 'de') creates a TRANSLATION of an existing System Requirements Specification instead: id -- the shared id of the existing EN source document -- is then required (no fresh id is assigned; the variant carries the same id), the EN source must exist (else SysrsNotFoundError), and the variant is written to docs/sysrs/{language}/ under the EN source's exact file name with status draft; content is validated through that language's heading-mapping table (translated headings are accepted) and persisted verbatim. An existing variant of that id and language is never overwritten (FileExistsError). A malformed language, one with no packaged heading-mapping table, a missing id for a non-default language, or an id given for the default language is a ValueError raised before any file access.

Inputs: content (required), id, language. Returns: result

create_tskCreate task listA

Create a new task list: assigns a fresh id, derives a filename from the body's H1 title, validates the submitted body-only content, and writes the new document to the task list base directory. A content-validation failure of the submitted body returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); nothing is written in that case. The structure reference is the get_tsk_schema tool (the generated JSON Schema); the get_tsk_template/get_tsk_example tools return the authoring template and a complete sample. Returns the newly created document's frontmatter only (no body); use the corresponding get_tsk tool to fetch the full document afterward. The optional language parameter (default 'en', feat-197-i18n) selects the language to create the document in: omitted/'en' = a new flat docs/tsk/ document with a fresh id, exactly the default behavior (id must then be omitted); a non-default ISO 639-1-shaped lowercase two-letter code (e.g. 'de') creates a TRANSLATION of an existing task list instead: id -- the shared id of the existing EN source document -- is then required (no fresh id is assigned; the variant carries the same id), the EN source must exist (else TskNotFoundError), and the variant is written to docs/tsk/{language}/ under the EN source's exact file name with status draft; content is validated through that language's heading-mapping table (translated headings are accepted) and persisted verbatim. An existing variant of that id and language is never overwritten (FileExistsError). A malformed language, one with no packaged heading-mapping table, a missing id for a non-default language, or an id given for the default language is a ValueError raised before any file access.

Inputs: content (required), id, language. Returns: result

create_txtCreate free-form textA

Create a new free-form text document: assigns a fresh id, derives a filename from the body's H1 title, validates the submitted body-only content, and writes the new document to the free-form text base directory. A content-validation failure of the submitted body returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); nothing is written in that case. The structure reference is the get_txt_schema tool (the generated JSON Schema); the get_txt_template/get_txt_example tools return the authoring template and a complete sample. Returns the newly created document's frontmatter only (no body); use the corresponding get_txt tool to fetch the full document afterward. The optional language parameter (default 'en', feat-197-i18n) selects the language to create the document in: omitted/'en' = a new flat docs/txt/ document with a fresh id, exactly the default behavior (id must then be omitted); a non-default ISO 639-1-shaped lowercase two-letter code (e.g. 'de') creates a TRANSLATION of an existing free-form text document instead: id -- the shared id of the existing EN source document -- is then required (no fresh id is assigned; the variant carries the same id), the EN source must exist (else TxtNotFoundError), and the variant is written to docs/txt/{language}/ under the EN source's exact file name with status draft; content is validated through that language's heading-mapping table (translated headings are accepted) and persisted verbatim. An existing variant of that id and language is never overwritten (FileExistsError). A malformed language, one with no packaged heading-mapping table, a missing id for a non-default language, or an id given for the default language is a ValueError raised before any file access.

Inputs: content (required), id, language. Returns: result

create_ucCreate use caseA

Create a new use case: assigns a fresh id, derives a filename from the body's H1 title, validates the submitted body-only content, and writes the new document to the use-case base directory. A content-validation failure of the submitted body returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); nothing is written in that case. The structure reference is the get_uc_schema tool (the generated JSON Schema); the get_uc_template/get_uc_example tools return the authoring template and a complete sample. Returns the newly created document's frontmatter only (no body); use the corresponding get_uc tool to fetch the full document afterward. The optional language parameter (default 'en', feat-197-i18n) selects the language to create the document in: omitted/'en' = a new flat docs/uc/ document with a fresh id, exactly the default behavior (id must then be omitted); a non-default ISO 639-1-shaped lowercase two-letter code (e.g. 'de') creates a TRANSLATION of an existing use case instead: id -- the shared id of the existing EN source document -- is then required (no fresh id is assigned; the variant carries the same id), the EN source must exist (else UcNotFoundError), and the variant is written to docs/uc/{language}/ under the EN source's exact file name with status draft; content is validated through that language's heading-mapping table (translated headings are accepted) and persisted verbatim. An existing variant of that id and language is never overwritten (FileExistsError). A malformed language, one with no packaged heading-mapping table, a missing id for a non-default language, or an id given for the default language is a ValueError raised before any file access.

Inputs: content (required), id, language. Returns: result

create_vcrCreate verification case recordA

Create a new verification case record: assigns a fresh id, derives a filename from the body's H1 title, validates the submitted body-only content, and writes the new document to the verification case record base directory. A content-validation failure of the submitted body returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); nothing is written in that case. The structure reference is the get_vcr_schema tool (the generated JSON Schema); the get_vcr_template/get_vcr_example tools return the authoring template and a complete sample. Returns the newly created document's frontmatter only (no body); use the corresponding get_vcr tool to fetch the full document afterward. The optional language parameter (default 'en', feat-197-i18n) selects the language to create the document in: omitted/'en' = a new flat docs/vcr/ document with a fresh id, exactly the default behavior (id must then be omitted); a non-default ISO 639-1-shaped lowercase two-letter code (e.g. 'de') creates a TRANSLATION of an existing verification case record instead: id -- the shared id of the existing EN source document -- is then required (no fresh id is assigned; the variant carries the same id), the EN source must exist (else VcrNotFoundError), and the variant is written to docs/vcr/{language}/ under the EN source's exact file name with status draft; content is validated through that language's heading-mapping table (translated headings are accepted) and persisted verbatim. An existing variant of that id and language is never overwritten (FileExistsError). A malformed language, one with no packaged heading-mapping table, a missing id for a non-default language, or an id given for the default language is a ValueError raised before any file access.

Inputs: content (required), id, language. Returns: result

deleteDelete documentA

Permanently delete an existing document from disk across the whole-body domains (type is one of req, uc, tsk, qa, prb, gol, rsk, dec, sop, feat, vcr, sysrs, txt; adr is not supported). Resolves the document by id, takes the domain lock, and removes it: the single *.md file for every flat domain (every whole-body domain except feat), or the entire <base>/<id>/ folder for feat. Returns the deleted path as a string. An invalid id (path-injection attempt or wrong format) is a ValueError raised before any file access; a missing document is the domain's own XNotFoundError; an I/O failure is a DeleteError. This is the sole delete entry point -- the former per-domain delete_<d> tools are removed. The optional language parameter (default 'en', feat-197-i18n) selects the language variant to delete: omitted/'en' = the flat default-language document, exactly the default behavior; a non-default ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = that language's variant at docs//{language}/ (found by id-scan of that subdirectory), deleting that variant file ONLY -- the EN file stays untouched. Every whole-body type except feat supports a non-default language; for feat (always English) a non-default language raises a ValueError, as does a malformed language or one with no packaged heading-mapping table -- all raised before any file access.

Inputs: id (required), type (required), language. Returns: result

editEdit documentA

Surgical, exact-match string replacement of an existing document's frontmatter-stripped body across the whole-body domains (type is one of req, uc, tsk, qa, prb, gol, rsk, dec, sop, feat, vcr, sysrs, txt; adr is not supported -- an explicit pre-dispatch ValueError, unlike update's inherited KeyError). Read the current body via the corresponding get_<d>(id, raw=True) first -- the read-before-edit step is a client convention, not server-enforced. Use edit for a surgical string replacement within an existing body; use the generic update tool for a whole-body or line-range replacement instead. The YAML frontmatter is never addressable (body only). The edit is 2-fold: stage 1 requires old_str to match the on-disk body byte-exactly -- uniquely, unless replace_all is true, in which case every exact occurrence is rewritten -- and stage 2 validates the edited body as a whole document; the document is written to disk only if both stages pass, and nothing is written on any failure (the file stays byte-unchanged). An empty new_str is a pure deletion (legal iff stage 2 validates). Matching is pure byte-exact with no line-ending normalization (an old_str containing \n will not match a CRLF body), no BOM handling, and no fuzzy/regex fallback. A document that exists but fails to parse returns a ParseFailureResult (error/path/id) instead of raising the domain's not-found error -- its error text is byte-identical to the domain's list_<d> failed row's error for the same file (identical field path and cause, including the trailing pydantic documentation line; ADR 9080b37c-82b3-4f63-81f1-79641d0bf14c) -- and a truly-absent id still raises the domain's not-found error. A stage-2 content-validation failure on the post-edit result returns a non-raising ValidateResult (valid=False, errors=[{message}]) with the message capped at 300 chars as the generic validate tool caps it, instead of raising AssertionError/pydantic.ValidationError (ADR b8c9bfea-6dcf-4158-bfc5-4ec17abb842f, GitHub issue #170 -- case 4 of the ADR 519d1206 non-raising-structured-result workaround chain). Stage-1's not-found/multiple-matches guards and every caller-usage ValueError (invalid id shape, unknown or adr type, identical input, empty old_str) still raise. An invalid id (path-injection attempt or wrong format for type) is a ValueError raised before any file access. Returns the updated frontmatter only (no body; updated bumped) on success; use the corresponding get_<d> tool to fetch the full document afterward. The optional language parameter (default 'en', feat-197-i18n) selects the language variant to edit: omitted/'en' = the flat default-language document, exactly the default behavior; a non-default ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = that language's variant at docs//{language}/ (found by id-scan of that subdirectory), matched against and written to that file ONLY -- the EN file's content and updated timestamp stay untouched -- with the stage-2 validation going through that language's heading-mapping table. Every whole-body type except feat supports a non-default language; for feat (always English) a non-default language raises a ValueError, as does a malformed language or one with no packaged heading-mapping table -- all raised before any file access.

Inputs: id (required), type (required), new_str (required), old_str (required), +2 more. Returns: result

find_similar_textFind documents similar to textA

Find the documents most semantically similar to a free-form query text (for pre-creation dedup/discovery checks), ranked by cosine similarity of local sentence embeddings (fastembed/bge-small, the similarity extra). By default every whole-body domain is searched (req, uc, tsk, qa, prb, gol, rsk, dec, sop, feat, vcr, sysrs, txt; adr is excluded structurally) -- restrict with target_types. Returns up to top_k (default 10, validated 1..100) hits as {type, id, title, status, path, score} rows, sorted by score descending; min_score (default: no filter) is an inclusive cosine lower bound. An unparseable candidate still appears, embedded from its full raw text, with id = null and the <failed to parse> marker title/status. When the embedding feature is unavailable (SPECMGR_SIMILARITY_DISABLED present, or the backend/model fails to load), returns the structured {available: false, reason, message} result instead of raising. A bad target_types/top_k is a ValueError before any filesystem access.

Inputs: query (required), top_k, min_score, target_types. Returns: result

get_adrGet ADRB

Read, parse, and return a full ADR document (frontmatter and body) by its id. An invalid id (path-injection attempt or wrong format) is a ValueError raised before any file access.

Inputs: id (required). Returns: A full ADR document: YAML frontmatter and body. This is the structured object t…

get_decGet decisionA

Read, parse, and return a full decision document (frontmatter and body) by its id. The optional language parameter (default 'en') selects the language variant to read: omitted/'en' = the flat docs/dec/ document, exactly the default behavior; any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = that language's variant at docs/dec/{language}/ (the EN source's exact basename). The language is shape-validated before any file access (a path-injection-shaped payload is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError, also before any file access. A raw=True read of a variant returns the variant's on-disk body verbatim (translation preserved); a raw=False read parses it through the variant's heading table, so the structured model holds canonical heading text. Pass raw=True to return the frontmatter-stripped body text verbatim instead. With raw=True, optional read-style offset/limit window the raw read: offset (1-based, default 1) is the first body line to return, limit (line count, default through end of body) how many; out-of-range values clamp (offset > N returns the empty string), and coordinates with raw=False raise ValueError. A document that exists but fails to parse returns a ParseFailureResult (error/path/id) instead of raising; its error text is byte-identical to the domain's own list tool's failed-row error for the same file (identical field path and cause, including the trailing pydantic documentation line; ADR 9080b37c-82b3-4f63-81f1-79641d0bf14c) -- for a language variant, against list_dec(language=...) on the same file. An invalid id (path-injection attempt or wrong format) is also a ValueError, raised before any file access. With raw=True, an optional numbered=True prefixes every returned body line with its 1-based absolute body-line number in the "<n>: " form (plain decimal, no padding) -- under offset/limit windowing the numbers start at the clamped offset and never restart at 1, so a number seen in the output can be fed straight back into the generic update tool's offset. numbered is meaningful only with raw=True: combining it with raw=False raises ValueError (like offset/limit), and a numbered read of a document that fails to parse returns the ParseFailureResult described above, never numbered text. Numbered output must never be fed back verbatim into content for update or create_dec -- strip the "<n>: " prefix from each line first (it is likewise never a valid edit old_str).

Inputs: id (required), raw, limit, offset, +2 more. Returns: result

get_dec_exampleGet DEC exampleA

Return a complete, valid sample decision document as raw markdown -- frontmatter and body -- exercising every section, for use as a learning example.

Inputs: no named parameters. Returns: result

get_dec_schemaGet DEC schemaA

Return the DEC domain's generated JSON Schema (2020-12 dialect) as raw text -- the machine-readable, parsed-JSON view of the document structure, not the markdown view (call get_dec_template for the authoring starting point). Fetch it for constraint discovery, structural questions, or an unresolved validation error -- never as a mandatory pre-authoring fetch. Generated by specmgr schema and kept current by a pre-commit hook/CI step; includes a $comment schema-layout version marker for detecting a shape change without diffing the whole document.

Inputs: no named parameters. Returns: result

get_dec_templateGet DEC templateA

Return a DEC document template -- frontmatter and every body field present, populated with short placeholder ('blind text') content -- as raw markdown, for use as a starting point when drafting a new decision.

Inputs: no named parameters. Returns: result

get_featGet featureA

Read, parse, and return a full feature document (frontmatter and body) by its id. Pass raw=True to return the frontmatter-stripped body text verbatim instead. With raw=True, optional read-style offset/limit window the raw read: offset (1-based, default 1) is the first body line to return, limit (line count, default through end of body) how many; out-of-range values clamp (offset > N returns the empty string), and coordinates with raw=False raise ValueError. A document that exists but fails to parse returns a ParseFailureResult (error/path/id) instead of raising; get_feat always fully parses, so its error text is the full-fidelity authority for this document -- byte-identical to list_feat's own failed-row error for the same file once list_feat's background warmup (or this very call) has fully parsed it; before that (or under SPECMGR_FEAT_WARMUP_DISABLED), list_feat's row may carry a transiently-healthy or dirty-stage-specific error instead (ADR 3982712a-a46b-4b2b-809f-9c6925a49b44, time-qualifying ADR 9080b37c-82b3-4f63-81f1-79641d0bf14c for feat only; every other domain's list/get error byte-identity stays unconditional). An invalid id (path-injection attempt or wrong format) is also a ValueError, raised before any file access. With raw=True, an optional numbered=True prefixes every returned body line with its 1-based absolute body-line number in the "<n>: " form (plain decimal, no padding) -- under offset/limit windowing the numbers start at the clamped offset and never restart at 1, so a number seen in the output can be fed straight back into the generic update tool's offset. numbered is meaningful only with raw=True: combining it with raw=False raises ValueError (like offset/limit), and a numbered read of a document that fails to parse returns the ParseFailureResult described above, never numbered text. Numbered output must never be fed back verbatim into content for update or create_feat -- strip the "<n>: " prefix from each line first (it is likewise never a valid edit old_str).

Inputs: id (required), raw, limit, offset, +1 more. Returns: result

get_feat_exampleGet FEAT exampleA

Return a complete, valid sample feature document as raw markdown -- frontmatter and body -- exercising every section, for use as a learning example.

Inputs: no named parameters. Returns: result

get_feat_schemaGet FEAT schemaA

Return the FEAT domain's generated JSON Schema (2020-12 dialect) as raw text -- the machine-readable, parsed-JSON view of the document structure, not the markdown view (call get_feat_template for the authoring starting point). Fetch it for constraint discovery, structural questions, or an unresolved validation error -- never as a mandatory pre-authoring fetch. Generated by specmgr schema and kept current by a pre-commit hook/CI step; includes a $comment schema-layout version marker for detecting a shape change without diffing the whole document.

Inputs: no named parameters. Returns: result

get_feat_templateGet FEAT templateA

Return a FEAT document template -- frontmatter and every body field present, populated with short placeholder ('blind text') content -- as raw markdown, for use as a starting point when drafting a new feature.

Inputs: no named parameters. Returns: result

get_golGet goalA

Read, parse, and return a full goal document (frontmatter and body) by its id. Pass raw=True to return the frontmatter-stripped body text verbatim instead. With raw=True, optional read-style offset/limit window the raw read: offset (1-based, default 1) is the first body line to return, limit (line count, default through end of body) how many; out-of-range values clamp (offset > N returns the empty string), and coordinates with raw=False raise ValueError. A document that exists but fails to parse returns a ParseFailureResult (error/path/id) instead of raising; its error text is byte-identical to the domain's own list tool's failed-row error for the same file (identical field path and cause, including the trailing pydantic documentation line; ADR 9080b37c-82b3-4f63-81f1-79641d0bf14c). An invalid id (path-injection attempt or wrong format) is also a ValueError, raised before any file access. With raw=True, an optional numbered=True prefixes every returned body line with its 1-based absolute body-line number in the "<n>: " form (plain decimal, no padding) -- under offset/limit windowing the numbers start at the clamped offset and never restart at 1, so a number seen in the output can be fed straight back into the generic update tool's offset. numbered is meaningful only with raw=True: combining it with raw=False raises ValueError (like offset/limit), and a numbered read of a document that fails to parse returns the ParseFailureResult described above, never numbered text. Numbered output must never be fed back verbatim into content for update or create_gol -- strip the "<n>: " prefix from each line first (it is likewise never a valid edit old_str). The optional language parameter (default 'en') selects the language variant to read: omitted/'en' = the flat docs/gol/ document, exactly the default behavior; any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = that language's variant at docs/gol/{language}/ (the EN source's exact basename). The language is shape-validated before any file access (a path-injection-shaped payload is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError, also before any file access. A raw=True read of a variant returns the variant's on-disk body verbatim (translation preserved); a raw=False read parses it through the variant's heading table, so the structured model holds canonical heading text. The ParseFailureResult error of a broken variant is byte-identical to list_gol(language=...)'s failed row for the same file.

Inputs: id (required), raw, limit, offset, +2 more. Returns: result

get_gol_exampleGet GOL exampleA

Return a complete, valid sample goal document as raw markdown -- frontmatter and body -- exercising every section, for use as a learning example.

Inputs: no named parameters. Returns: result

get_gol_schemaGet GOL schemaA

Return the GOL domain's generated JSON Schema (2020-12 dialect) as raw text -- the machine-readable, parsed-JSON view of the document structure, not the markdown view (call get_gol_template for the authoring starting point). Fetch it for constraint discovery, structural questions, or an unresolved validation error -- never as a mandatory pre-authoring fetch. Generated by specmgr schema and kept current by a pre-commit hook/CI step; includes a $comment schema-layout version marker for detecting a shape change without diffing the whole document.

Inputs: no named parameters. Returns: result

get_gol_templateGet GOL templateA

Return a GOL document template -- frontmatter and every body field present, populated with short placeholder ('blind text') content -- as raw markdown, for use as a starting point when drafting a new goal.

Inputs: no named parameters. Returns: result

get_prbGet problem statementA

Read, parse, and return a full problem statement document (frontmatter and body) by its id. Pass raw=True to return the frontmatter-stripped body text verbatim instead. With raw=True, optional read-style offset/limit window the raw read: offset (1-based, default 1) is the first body line to return, limit (line count, default through end of body) how many; out-of-range values clamp (offset > N returns the empty string), and coordinates with raw=False raise ValueError. A document that exists but fails to parse returns a ParseFailureResult (error/path/id) instead of raising; its error text is byte-identical to the domain's own list tool's failed-row error for the same file (identical field path and cause, including the trailing pydantic documentation line; ADR 9080b37c-82b3-4f63-81f1-79641d0bf14c). An invalid id (path-injection attempt or wrong format) is also a ValueError, raised before any file access. With raw=True, an optional numbered=True prefixes every returned body line with its 1-based absolute body-line number in the "<n>: " form (plain decimal, no padding) -- under offset/limit windowing the numbers start at the clamped offset and never restart at 1, so a number seen in the output can be fed straight back into the generic update tool's offset. numbered is meaningful only with raw=True: combining it with raw=False raises ValueError (like offset/limit), and a numbered read of a document that fails to parse returns the ParseFailureResult described above, never numbered text. Numbered output must never be fed back verbatim into content for update or create_prb -- strip the "<n>: " prefix from each line first (it is likewise never a valid edit old_str). The optional language parameter (default 'en') selects the language variant to read: omitted/'en' = the flat docs/prb/ document, exactly the default behavior; any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = that language's variant at docs/prb/{language}/ (the EN source's exact basename). The language is shape-validated before any file access (a path-injection-shaped payload is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError, also before any file access. A raw=True read of a variant returns the variant's on-disk body verbatim (translation preserved); a raw=False read parses it through the variant's heading table, so the structured model holds canonical heading text. The ParseFailureResult error of a broken variant is byte-identical to list_prb(language=...)'s failed row for the same file.

Inputs: id (required), raw, limit, offset, +2 more. Returns: result

get_prb_exampleGet PRB exampleA

Return a complete, valid sample problem statement document as raw markdown -- frontmatter and body -- exercising every section, for use as a learning example.

Inputs: no named parameters. Returns: result

get_prb_schemaGet PRB schemaA

Return the PRB domain's generated JSON Schema (2020-12 dialect) as raw text -- the machine-readable, parsed-JSON view of the document structure, not the markdown view (call get_prb_template for the authoring starting point). Fetch it for constraint discovery, structural questions, or an unresolved validation error -- never as a mandatory pre-authoring fetch. Generated by specmgr schema and kept current by a pre-commit hook/CI step; includes a $comment schema-layout version marker for detecting a shape change without diffing the whole document.

Inputs: no named parameters. Returns: result

get_prb_templateGet PRB templateA

Return a PRB document template -- frontmatter and every body field present, populated with short placeholder ('blind text') content -- as raw markdown, for use as a starting point when drafting a new problem statement.

Inputs: no named parameters. Returns: result

get_qaGet QA documentA

Read, parse, and return a full QA document (frontmatter and body) by its id. Pass raw=True to return the frontmatter-stripped body text verbatim instead. With raw=True, optional read-style offset/limit window the raw read: offset (1-based, default 1) is the first body line to return, limit (line count, default through end of body) how many; out-of-range values clamp (offset > N returns the empty string), and coordinates with raw=False raise ValueError. A document that exists but fails to parse returns a ParseFailureResult (error/path/id) instead of raising; its error text is byte-identical to the domain's own list tool's failed-row error for the same file (identical field path and cause, including the trailing pydantic documentation line; ADR 9080b37c-82b3-4f63-81f1-79641d0bf14c). An invalid id (path-injection attempt or wrong format) is also a ValueError, raised before any file access. With raw=True, an optional numbered=True prefixes every returned body line with its 1-based absolute body-line number in the "<n>: " form (plain decimal, no padding) -- under offset/limit windowing the numbers start at the clamped offset and never restart at 1, so a number seen in the output can be fed straight back into the generic update tool's offset. numbered is meaningful only with raw=True: combining it with raw=False raises ValueError (like offset/limit), and a numbered read of a document that fails to parse returns the ParseFailureResult described above, never numbered text. Numbered output must never be fed back verbatim into content for update or create_qa -- strip the "<n>: " prefix from each line first (it is likewise never a valid edit old_str). The optional language parameter (default 'en') selects the language variant to read: omitted/'en' = the flat docs/qa/ document, exactly the default behavior; any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = that language's variant at docs/qa/{language}/ (the EN source's exact basename). The language is shape-validated before any file access (a path-injection-shaped payload is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError, also before any file access. A raw=True read of a variant returns the variant's on-disk body verbatim (translation preserved); a raw=False read parses it through the variant's heading table, so the structured model holds canonical heading text. The ParseFailureResult error of a broken variant is byte-identical to list_qa(language=...)'s failed row for the same file.

Inputs: id (required), raw, limit, offset, +2 more. Returns: result

get_qa_exampleGet QA exampleA

Return a complete, valid sample QA document as raw markdown -- frontmatter and body -- exercising every section, for use as a learning example.

Inputs: no named parameters. Returns: result

get_qa_schemaGet QA schemaA

Return the QA domain's generated JSON Schema (2020-12 dialect) as raw text -- the machine-readable, parsed-JSON view of the document structure, not the markdown view (call get_qa_template for the authoring starting point). Fetch it for constraint discovery, structural questions, or an unresolved validation error -- never as a mandatory pre-authoring fetch. Generated by specmgr schema and kept current by a pre-commit hook/CI step; includes a $comment schema-layout version marker for detecting a shape change without diffing the whole document.

Inputs: no named parameters. Returns: result

get_qa_templateGet QA templateA

Return a QA document template -- frontmatter and every body field present, populated with short placeholder ('blind text') content -- as raw markdown, for use as a starting point when drafting a new QA document.

Inputs: no named parameters. Returns: result

get_reference_diagramGet the reference diagram of one documentA

Render and return the deterministic PlantUML component diagram of one source document's reference neighbourhood (the frozen rulebook specmgr://reference-graph §2–§5): one node per artifact (label = the document title, truncated to SPECMGR_DIAGRAM_MAX_TITLE, default 40), a stereotype + colour per artifact type, nodes grouped into per-domain curly-brace component groups (one per non-empty domain, closed with } -- the 2026-10-15 corrected form), one label-free edge per unique reference ({src} --> {dst}, the referenced item carries the incoming arrow), every edge lying on a cycle (SCC-based) drawn with the red label-free -[#red]-> arrow, and every reference whose target cannot be resolved on disk drawn as a top-level <<UNRESOLVED>> node. type is the source document's domain (one of adr, req, uc, tsk, qa, prb, gol, rsk, dec, sop, feat, vcr, sysrs, txt) and id the source's own identifier; depth (1..5, default 1) is the forward BFS depth; reverse=True is additive (the forward expansion to depth plus the direct incoming references to the source, direct-only on the incoming side); include_feat=False drops every FEAT node except the explicitly requested source (then every edge touching a dropped node); title overrides the diagram title (default: the source's H1; sanitized but never truncated). Guard order: id (path-injection attempt or wrong format -- for type="feat" the full feat-NNN-slug only; a bare feat-NNN is rejected, use list_feat to discover the exact id) and depth are both a ValueError raised before any filesystem access; a missing source raises the source domain's own XNotFoundError; an existing-but-broken source returns the non-raising ParseFailureResult (error/path/id, the error text byte-identical to the domain's list_<d> failed-row error for the same file (ADR 9080b37c) for the 13 whole-body domains -- time-qualified by ADR 3982712a for feat; for adr (whose list_adr carries no failed row: broken ADRs are skipped), the leg-read AdrParseError's own text). Returns PlantUML source (not a specmgr document) -- run it through specmgr plantuml-check (CLI) or the validate_plantuml tool for a validation verdict.

Inputs: id (required), type (required), depth, title, +2 more. Returns: result

get_reference_diagram_allGet the registry-wide reference diagramA

Render and return the deterministic PlantUML component diagram of the whole reference registry (the frozen rulebook specmgr://reference-graph §2–§5, the registry-wide construction of §3.3): one node per parseable document across all 14 domains (broken documents are skipped -- the render never fails for content), one label-free edge per unique reference ({src} --> {dst}, the referenced item carries the incoming arrow), every edge lying on a cycle (SCC-based) drawn with the red label-free -[#red]-> arrow, and every reference whose target cannot be resolved on disk drawn as a top-level <<UNRESOLVED>> node. Node labels are truncated to SPECMGR_DIAGRAM_MAX_TITLE (default 40). include_feat=False drops every FEAT node (then every edge touching a dropped node); title overrides the diagram title (default: Reference Graph; sanitized but never truncated). Returns PlantUML source (not a specmgr document) -- run it through specmgr plantuml-check (CLI) or the validate_plantuml tool for a validation verdict.

Inputs: title, include_feat. Returns: result

get_reqGet requirementA

Read, parse, and return a full requirement document (frontmatter and body) by its id. Pass raw=True to return the frontmatter-stripped body text verbatim instead. With raw=True, optional read-style offset/limit window the raw read: offset (1-based, default 1) is the first body line to return, limit (line count, default through end of body) how many; out-of-range values clamp (offset > N returns the empty string), and coordinates with raw=False raise ValueError. A document that exists but fails to parse returns a ParseFailureResult (error/path/id) instead of raising; its error text is byte-identical to the domain's own list tool's failed-row error for the same file (identical field path and cause, including the trailing pydantic documentation line; ADR 9080b37c-82b3-4f63-81f1-79641d0bf14c). An invalid id (path-injection attempt or wrong format) is also a ValueError, raised before any file access. With raw=True, an optional numbered=True prefixes every returned body line with its 1-based absolute body-line number in the "<n>: " form (plain decimal, no padding) -- under offset/limit windowing the numbers start at the clamped offset and never restart at 1, so a number seen in the output can be fed straight back into the generic update tool's offset. numbered is meaningful only with raw=True: combining it with raw=False raises ValueError (like offset/limit), and a numbered read of a document that fails to parse returns the ParseFailureResult described above, never numbered text. Numbered output must never be fed back verbatim into content for update or create_req -- strip the "<n>: " prefix from each line first (it is likewise never a valid edit old_str). The optional language parameter (default 'en') selects the language variant to read: omitted/'en' = the flat docs/req/ document, exactly the default behavior; any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = that language's variant at docs/req/{language}/ (the EN source's exact basename). The language is shape-validated before any file access (a path-injection-shaped payload is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError, also before any file access. A raw=True read of a variant returns the variant's on-disk body verbatim (translation preserved); a raw=False read parses it through the variant's heading table, so the structured model holds canonical heading text. The ParseFailureResult error of a broken variant is byte-identical to list_req(language=...)'s failed row for the same file.

Inputs: id (required), raw, limit, offset, +2 more. Returns: result

get_req_exampleGet REQ exampleA

Return a complete, valid sample requirement document as raw markdown -- frontmatter and body -- exercising every section, for use as a learning example.

Inputs: no named parameters. Returns: result

get_req_schemaGet REQ schemaA

Return the REQ domain's generated JSON Schema (2020-12 dialect) as raw text -- the machine-readable, parsed-JSON view of the document structure, not the markdown view (call get_req_template for the authoring starting point). Fetch it for constraint discovery, structural questions, or an unresolved validation error -- never as a mandatory pre-authoring fetch. Generated by specmgr schema and kept current by a pre-commit hook/CI step; includes a $comment schema-layout version marker for detecting a shape change without diffing the whole document.

Inputs: no named parameters. Returns: result

get_req_templateGet REQ templateA

Return a REQ document template -- frontmatter and every body field present, populated with short placeholder ('blind text') content -- as raw markdown, for use as a starting point when drafting a new requirement.

Inputs: no named parameters. Returns: result

get_rskGet riskA

Read, parse, and return a full risk document (frontmatter and body) by its id. Pass raw=True to return the frontmatter-stripped body text verbatim instead. With raw=True, optional read-style offset/limit window the raw read: offset (1-based, default 1) is the first body line to return, limit (line count, default through end of body) how many; out-of-range values clamp (offset > N returns the empty string), and coordinates with raw=False raise ValueError. A document that exists but fails to parse returns a ParseFailureResult (error/path/id) instead of raising; its error text is byte-identical to the domain's own list tool's failed-row error for the same file (identical field path and cause, including the trailing pydantic documentation line; ADR 9080b37c-82b3-4f63-81f1-79641d0bf14c). An invalid id (path-injection attempt or wrong format) is also a ValueError, raised before any file access. With raw=True, an optional numbered=True prefixes every returned body line with its 1-based absolute body-line number in the "<n>: " form (plain decimal, no padding) -- under offset/limit windowing the numbers start at the clamped offset and never restart at 1, so a number seen in the output can be fed straight back into the generic update tool's offset. numbered is meaningful only with raw=True: combining it with raw=False raises ValueError (like offset/limit), and a numbered read of a document that fails to parse returns the ParseFailureResult described above, never numbered text. Numbered output must never be fed back verbatim into content for update or create_rsk -- strip the "<n>: " prefix from each line first (it is likewise never a valid edit old_str). The optional language parameter (default 'en') selects the language variant to read: omitted/'en' = the flat docs/rsk/ document, exactly the default behavior; any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = that language's variant at docs/rsk/{language}/ (the EN source's exact basename). The language is shape-validated before any file access (a path-injection-shaped payload is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError, also before any file access. A raw=True read of a variant returns the variant's on-disk body verbatim (translation preserved); a raw=False read parses it through the variant's heading table, so the structured model holds canonical heading text. The ParseFailureResult error of a broken variant is byte-identical to list_rsk(language=...)'s failed row for the same file.

Inputs: id (required), raw, limit, offset, +2 more. Returns: result

get_rsk_exampleGet RSK exampleA

Return a complete, valid sample risk document as raw markdown -- frontmatter and body -- exercising every section, for use as a learning example.

Inputs: no named parameters. Returns: result

get_rsk_schemaGet RSK schemaA

Return the RSK domain's generated JSON Schema (2020-12 dialect) as raw text -- the machine-readable, parsed-JSON view of the document structure, not the markdown view (call get_rsk_template for the authoring starting point). Fetch it for constraint discovery, structural questions, or an unresolved validation error -- never as a mandatory pre-authoring fetch. Generated by specmgr schema and kept current by a pre-commit hook/CI step; includes a $comment schema-layout version marker for detecting a shape change without diffing the whole document.

Inputs: no named parameters. Returns: result

get_rsk_templateGet RSK templateA

Return a risk document template -- frontmatter and every body field present, populated with short placeholder ('blind text') content -- as raw markdown, for use as a starting point when drafting a new risk.

Inputs: no named parameters. Returns: result

get_sopGet Standard Operating ProcedureA

Read, parse, and return a full SOP document (frontmatter and body) by its id. Pass raw=True to return the frontmatter-stripped body text verbatim instead. With raw=True, optional read-style offset/limit window the raw read: offset (1-based, default 1) is the first body line to return, limit (line count, default through end of body) how many; out-of-range values clamp (offset > N returns the empty string), and coordinates with raw=False raise ValueError. A document that exists but fails to parse returns a ParseFailureResult (error/path/id) instead of raising; its error text is byte-identical to the domain's own list tool's failed-row error for the same file (identical field path and cause, including the trailing pydantic documentation line; ADR 9080b37c-82b3-4f63-81f1-79641d0bf14c). An invalid id (path-injection attempt or wrong format) is also a ValueError, raised before any file access. With raw=True, an optional numbered=True prefixes every returned body line with its 1-based absolute body-line number in the "<n>: " form (plain decimal, no padding) -- under offset/limit windowing the numbers start at the clamped offset and never restart at 1, so a number seen in the output can be fed straight back into the generic update tool's offset. numbered is meaningful only with raw=True: combining it with raw=False raises ValueError (like offset/limit), and a numbered read of a document that fails to parse returns the ParseFailureResult described above, never numbered text. Numbered output must never be fed back verbatim into content for update or create_sop -- strip the "<n>: " prefix from each line first (it is likewise never a valid edit old_str). The optional language parameter (default 'en') selects the language variant to read: omitted/'en' = the flat docs/sop/ document, exactly the default behavior; any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = that language's variant at docs/sop/{language}/ (the EN source's exact basename). The language is shape-validated before any file access (a path-injection-shaped payload is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError, also before any file access. A raw=True read of a variant returns the variant's on-disk body verbatim (translation preserved); a raw=False read parses it through the variant's heading table, so the structured model holds canonical heading text. The ParseFailureResult error of a broken variant is byte-identical to list_sop(language=...)'s failed row for the same file.

Inputs: id (required), raw, limit, offset, +2 more. Returns: result

get_sop_exampleGet SOP exampleA

Return a complete, valid sample Standard Operating Procedure document as raw markdown -- frontmatter and body -- exercising every section, for use as a learning example.

Inputs: no named parameters. Returns: result

get_sop_schemaGet SOP schemaA

Return the SOP domain's generated JSON Schema (2020-12 dialect) as raw text -- the machine-readable, parsed-JSON view of the document structure, not the markdown view (call get_sop_template for the authoring starting point). Fetch it for constraint discovery, structural questions, or an unresolved validation error -- never as a mandatory pre-authoring fetch. Generated by specmgr schema and kept current by a pre-commit hook/CI step; includes a $comment schema-layout version marker for detecting a shape change without diffing the whole document.

Inputs: no named parameters. Returns: result

get_sop_templateGet SOP templateA

Return a SOP document template -- frontmatter and every body field present, populated with short placeholder ('blind text') content -- as raw markdown, for use as a starting point when drafting a new Standard Operating Procedure.

Inputs: no named parameters. Returns: result

get_sysrsGet System Requirements SpecificationA

Read, parse, and return a full System Requirements Specification document (frontmatter and body) by its id. Pass raw=True to return the frontmatter-stripped body text verbatim instead. With raw=True, optional read-style offset/limit window the raw read: offset (1-based, default 1) is the first body line to return, limit (line count, default through end of body) how many; out-of-range values clamp (offset > N returns the empty string), and coordinates with raw=False raise ValueError. A document that exists but fails to parse returns a ParseFailureResult (error/path/id) instead of raising; its error text is byte-identical to the domain's own list tool's failed-row error for the same file (identical field path and cause, including the trailing pydantic documentation line; ADR 9080b37c-82b3-4f63-81f1-79641d0bf14c). An invalid id (path-injection attempt or wrong format) is also a ValueError, raised before any file access. With raw=True, an optional numbered=True prefixes every returned body line with its 1-based absolute body-line number in the "<n>: " form (plain decimal, no padding) -- under offset/limit windowing the numbers start at the clamped offset and never restart at 1, so a number seen in the output can be fed straight back into the generic update tool's offset. numbered is meaningful only with raw=True: combining it with raw=False raises ValueError (like offset/limit), and a numbered read of a document that fails to parse returns the ParseFailureResult described above, never numbered text. Numbered output must never be fed back verbatim into content for update or create_sysrs -- strip the "<n>: " prefix from each line first (it is likewise never a valid edit old_str). The optional language parameter (default 'en') selects the language variant to read: omitted/'en' = the flat docs/sysrs/ document, exactly the default behavior; any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = that language's variant at docs/sysrs/{language}/ (the EN source's exact basename). The language is shape-validated before any file access (a path-injection-shaped payload is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError, also before any file access. A raw=True read of a variant returns the variant's on-disk body verbatim (translation preserved); a raw=False read parses it through the variant's heading table, so the structured model holds canonical heading text. The ParseFailureResult error of a broken variant is byte-identical to list_sysrs(language=...)'s failed row for the same file.

Inputs: id (required), raw, limit, offset, +2 more. Returns: result

get_sysrs_exampleGet SYSRS exampleA

Return a complete, valid sample System Requirements Specification document as raw markdown -- frontmatter and body -- exercising every section, for use as a learning example.

Inputs: no named parameters. Returns: result

get_sysrs_schemaGet SYSRS schemaA

Return the SYSRS domain's generated JSON Schema (2020-12 dialect) as raw text -- the machine-readable, parsed-JSON view of the document structure, not the markdown view (call get_sysrs_template for the authoring starting point). Fetch it for constraint discovery, structural questions, or an unresolved validation error -- never as a mandatory pre-authoring fetch. Generated by specmgr schema and kept current by a pre-commit hook/CI step; includes a $comment schema-layout version marker for detecting a shape change without diffing the whole document.

Inputs: no named parameters. Returns: result

get_sysrs_templateGet SYSRS templateA

Return a SYSRS document template -- frontmatter and every body field present, populated with short placeholder ('blind text') content -- as raw markdown, for use as a starting point when drafting a new System Requirements Specification.

Inputs: no named parameters. Returns: result

get_tskGet task listA

Read, parse, and return a full task list document (frontmatter and body) by its id. Pass raw=True to return the frontmatter-stripped body text verbatim instead. With raw=True, optional read-style offset/limit window the raw read: offset (1-based, default 1) is the first body line to return, limit (line count, default through end of body) how many; out-of-range values clamp (offset > N returns the empty string), and coordinates with raw=False raise ValueError. A document that exists but fails to parse returns a ParseFailureResult (error/path/id) instead of raising; its error text is byte-identical to the domain's own list tool's failed-row error for the same file (identical field path and cause, including the trailing pydantic documentation line; ADR 9080b37c-82b3-4f63-81f1-79641d0bf14c). An invalid id (path-injection attempt or wrong format) is also a ValueError, raised before any file access. With raw=True, an optional numbered=True prefixes every returned body line with its 1-based absolute body-line number in the "<n>: " form (plain decimal, no padding) -- under offset/limit windowing the numbers start at the clamped offset and never restart at 1, so a number seen in the output can be fed straight back into the generic update tool's offset. numbered is meaningful only with raw=True: combining it with raw=False raises ValueError (like offset/limit), and a numbered read of a document that fails to parse returns the ParseFailureResult described above, never numbered text. Numbered output must never be fed back verbatim into content for update or create_tsk -- strip the "<n>: " prefix from each line first (it is likewise never a valid edit old_str). The optional language parameter (default 'en') selects the language variant to read: omitted/'en' = the flat docs/tsk/ document, exactly the default behavior; any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = that language's variant at docs/tsk/{language}/ (the EN source's exact basename). The language is shape-validated before any file access (a path-injection-shaped payload is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError, also before any file access. A raw=True read of a variant returns the variant's on-disk body verbatim (translation preserved); a raw=False read parses it through the variant's heading table, so the structured model holds canonical heading text. The ParseFailureResult error of a broken variant is byte-identical to list_tsk(language=...)'s failed row for the same file.

Inputs: id (required), raw, limit, offset, +2 more. Returns: result

get_tsk_exampleGet TSK exampleA

Return a complete, valid sample task list document as raw markdown -- frontmatter and body -- exercising every section, for use as a learning example.

Inputs: no named parameters. Returns: result

get_tsk_schemaGet TSK schemaA

Return the TSK domain's generated JSON Schema (2020-12 dialect) as raw text -- the machine-readable, parsed-JSON view of the document structure, not the markdown view (call get_tsk_template for the authoring starting point). Fetch it for constraint discovery, structural questions, or an unresolved validation error -- never as a mandatory pre-authoring fetch. Generated by specmgr schema and kept current by a pre-commit hook/CI step; includes a $comment schema-layout version marker for detecting a shape change without diffing the whole document.

Inputs: no named parameters. Returns: result

get_tsk_templateGet TSK templateA

Return a TSK document template -- frontmatter and every body field present, populated with short placeholder ('blind text') content -- as raw markdown, for use as a starting point when drafting a new task list.

Inputs: no named parameters. Returns: result

get_txtGet free-form textA

Read, parse, and return a full free-form text document (frontmatter and body) by its id. Pass raw=True to return the frontmatter-stripped body text verbatim instead. With raw=True, optional read-style offset/limit window the raw read: offset (1-based, default 1) is the first body line to return, limit (line count, default through end of body) how many; out-of-range values clamp (offset > N returns the empty string), and coordinates with raw=False raise ValueError. A document that exists but fails to parse returns a ParseFailureResult (error/path/id) instead of raising; its error text is byte-identical to the domain's own list tool's failed-row error for the same file (identical field path and cause, including the trailing pydantic documentation line; ADR 9080b37c-82b3-4f63-81f1-79641d0bf14c). An invalid id (path-injection attempt or wrong format) is also a ValueError, raised before any file access. With raw=True, an optional numbered=True prefixes every returned body line with its 1-based absolute body-line number in the "<n>: " form (plain decimal, no padding) -- under offset/limit windowing the numbers start at the clamped offset and never restart at 1, so a number seen in the output can be fed straight back into the generic update tool's offset. numbered is meaningful only with raw=True: combining it with raw=False raises ValueError (like offset/limit), and a numbered read of a document that fails to parse returns the ParseFailureResult described above, never numbered text. Numbered output must never be fed back verbatim into content for update or create_txt -- strip the "<n>: " prefix from each line first (it is likewise never a valid edit old_str). The optional language parameter (default 'en') selects the language variant to read: omitted/'en' = the flat docs/txt/ document, exactly the default behavior; any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = that language's variant at docs/txt/{language}/ (the EN source's exact basename). The language is shape-validated before any file access (a path-injection-shaped payload is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError, also before any file access. A raw=True read of a variant returns the variant's on-disk body verbatim (translation preserved); a raw=False read parses it through the variant's heading table, so the structured model holds canonical heading text. The ParseFailureResult error of a broken variant is byte-identical to list_txt(language=...)'s failed row for the same file.

Inputs: id (required), raw, limit, offset, +2 more. Returns: result

get_txt_exampleGet TXT exampleA

Return a complete, valid sample free-form text document as raw markdown -- frontmatter and body -- exercising the free-form body (free H2/H3 headings, a list, a free-floating reference line) and the mandatory ## Updates log with two ordered entries, for use as a learning example.

Inputs: no named parameters. Returns: result

get_txt_schemaGet TXT schemaA

Return the TXT domain's generated JSON Schema (2020-12 dialect) as raw text -- the machine-readable, parsed-JSON view of the document structure, not the markdown view (call get_txt_template for the authoring starting point). Fetch it for constraint discovery, structural questions, or an unresolved validation error -- never as a mandatory pre-authoring fetch. Generated by specmgr schema and kept current by a pre-commit hook/CI step; includes a $comment schema-layout version marker for detecting a shape change without diffing the whole document.

Inputs: no named parameters. Returns: result

get_txt_templateGet TXT templateA

Return a TXT document template -- frontmatter and every body field present, populated with short placeholder ('blind text') content -- as raw markdown, for use as a starting point when drafting a new free-form text document.

Inputs: no named parameters. Returns: result

get_ucGet use caseA

Read, parse, and return a full use-case document (frontmatter and body) by its id. Pass raw=True to return the frontmatter-stripped body text verbatim instead. With raw=True, optional read-style offset/limit window the raw read: offset (1-based, default 1) is the first body line to return, limit (line count, default through end of body) how many; out-of-range values clamp (offset > N returns the empty string), and coordinates with raw=False raise ValueError. A document that exists but fails to parse returns a ParseFailureResult (error/path/id) instead of raising; its error text is byte-identical to the domain's own list tool's failed-row error for the same file (identical field path and cause, including the trailing pydantic documentation line; ADR 9080b37c-82b3-4f63-81f1-79641d0bf14c). An invalid id (path-injection attempt or wrong format) is also a ValueError, raised before any file access. With raw=True, an optional numbered=True prefixes every returned body line with its 1-based absolute body-line number in the "<n>: " form (plain decimal, no padding) -- under offset/limit windowing the numbers start at the clamped offset and never restart at 1, so a number seen in the output can be fed straight back into the generic update tool's offset. numbered is meaningful only with raw=True: combining it with raw=False raises ValueError (like offset/limit), and a numbered read of a document that fails to parse returns the ParseFailureResult described above, never numbered text. Numbered output must never be fed back verbatim into content for update or create_uc -- strip the "<n>: " prefix from each line first (it is likewise never a valid edit old_str). The optional language parameter (default 'en') selects the language variant to read: omitted/'en' = the flat docs/uc/ document, exactly the default behavior; any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = that language's variant at docs/uc/{language}/ (the EN source's exact basename). The language is shape-validated before any file access (a path-injection-shaped payload is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError, also before any file access. A raw=True read of a variant returns the variant's on-disk body verbatim (translation preserved); a raw=False read parses it through the variant's heading table, so the structured model holds canonical heading text. The ParseFailureResult error of a broken variant is byte-identical to list_uc(language=...)'s failed row for the same file.

Inputs: id (required), raw, limit, offset, +2 more. Returns: result

get_uc_diagramGet the per-UC usecase diagramA

Render and return the deterministic PlantUML usecase diagram for one use case (the frozen rulebook specmgr://uc/plantuml §2.5): one usecase node (title + level stereotype), one actor node per distinct cleaned actor label, plain associations. Read-only: the id is path-safety-guarded (a wrong-format id is a ValueError before any file access) and the document is read cache-aware; a document that exists but fails to parse returns the non-raising ParseFailureResult (error/path/id, the feat-150 precedent), and a truly absent id raises the domain's not-found error. Returns PlantUML source (not a specmgr document) -- run it through the validate_plantuml tool for a validation verdict.

Inputs: id (required). Returns: result

get_uc_exampleGet UC exampleA

Return a complete, valid sample use case document as raw markdown -- frontmatter and body -- exercising every section, for use as a learning example.

Inputs: no named parameters. Returns: result

get_uc_plantuml_exampleGet the PlantUML sequence-diagram exampleA

Return the complete, fully attributed PlantUML sequence diagram for the packaged 'Buy Goods' example UC as raw PlantUML source, verbatim -- the model for the generate_uc_sequence_diagram prompt flow (rulebook §2.9 attribution, every UNATTRIBUTED marker resolved). The file is not markdown and not a specmgr document (no frontmatter, not validate-able). Reads the packaged data file fresh on every call.

Inputs: no named parameters. Returns: result

get_uc_plantuml_templateGet the PlantUML sequence-skeleton templateA

Return the packaged PlantUML sequence-skeleton template (placeholder participants and mapping comments -- the shape get_uc_sequence_skeleton emits, rulebook §2.9) as raw PlantUML source, verbatim. The file is not markdown and not a specmgr document (no frontmatter, not validate-able). Reads the packaged data file fresh on every call.

Inputs: no named parameters. Returns: result

get_uc_schemaGet UC schemaA

Return the UC domain's generated JSON Schema (2020-12 dialect) as raw text -- the machine-readable, parsed-JSON view of the document structure, not the markdown view (call get_uc_template for the authoring starting point). Fetch it for constraint discovery, structural questions, or an unresolved validation error -- never as a mandatory pre-authoring fetch. Generated by specmgr schema and kept current by a pre-commit hook/CI step; includes a $comment schema-layout version marker for detecting a shape change without diffing the whole document.

Inputs: no named parameters. Returns: result

get_uc_sequence_skeletonGet the sequence-diagram skeletonA

Render and return the deterministic PlantUML sequence-diagram skeleton for one use case (the frozen rulebook specmgr://uc/plantuml §2.9): participants, the preconditions note, the trigger (virtual step 0, receiver fixed to the system), the ordinal steps, the alt fragments, the final notes -- and every message the §2.9.3 attribution rule cannot pre-fill as one of the three frozen UNATTRIBUTED marker lines. Read-only: the id is path-safety-guarded (a wrong-format id is a ValueError before any file access) and the document is read cache-aware; a document that exists but fails to parse returns the non-raising ParseFailureResult (error/path/id, the feat-150 precedent), and a truly absent id raises the domain's not-found error. The skeleton is the starting point of the generate_uc_sequence_diagram prompt flow (the agent attributes the markers, validates via validate_plantuml, and writes the .puml file host-native; no specmgr tool writes .puml).

Inputs: id (required). Returns: result

get_uc_templateGet UC templateA

Return a UC document template -- frontmatter and every body field present, populated with short placeholder ('blind text') content -- as raw markdown, for use as a starting point when drafting a new use case.

Inputs: no named parameters. Returns: result

get_use_case_package_diagramGet the multi-UC package diagramA

Render and return the deterministic PlantUML package diagram for a set of use cases (the frozen rulebook specmgr://uc/plantuml §2.7): one usecase node per document in the given order, the deduplicated actor union, and the <<include>>/<<extend>>/superordinate edges from Related Use Cases. ids=None = every UC in list_uc order (list_uc failed rows -- existing-but-broken documents -- become skipped slots); an id missing on disk or existing-but-broken becomes the same skipped slot, and any reference that does not resolve to a parsed slot in the package (including legacy UC-NNN tokens) takes the deterministic 'Unresolved UC reference' note -- the render never fails on an id or a reference. Read-only: every given id is path-safety-guarded (a wrong-format id is a ValueError before any file access) and the documents are read cache-aware. Returns PlantUML source -- run it through the validate_plantuml tool for a validation verdict.

Inputs: ids. Returns: result

get_vcrGet verification case recordA

Read, parse, and return a full verification case record document (frontmatter and body) by its id. Pass raw=True to return the frontmatter-stripped body text verbatim instead. With raw=True, optional read-style offset/limit window the raw read: offset (1-based, default 1) is the first body line to return, limit (line count, default through end of body) how many; out-of-range values clamp (offset > N returns the empty string), and coordinates with raw=False raise ValueError. A document that exists but fails to parse returns a ParseFailureResult (error/path/id) instead of raising; its error text is byte-identical to the domain's own list tool's failed-row error for the same file (identical field path and cause, including the trailing pydantic documentation line; ADR 9080b37c-82b3-4f63-81f1-79641d0bf14c). An invalid id (path-injection attempt or wrong format) is also a ValueError, raised before any file access. With raw=True, an optional numbered=True prefixes every returned body line with its 1-based absolute body-line number in the "<n>: " form (plain decimal, no padding) -- under offset/limit windowing the numbers start at the clamped offset and never restart at 1, so a number seen in the output can be fed straight back into the generic update tool's offset. numbered is meaningful only with raw=True: combining it with raw=False raises ValueError (like offset/limit), and a numbered read of a document that fails to parse returns the ParseFailureResult described above, never numbered text. Numbered output must never be fed back verbatim into content for update or create_vcr -- strip the "<n>: " prefix from each line first (it is likewise never a valid edit old_str). The optional language parameter (default 'en') selects the language variant to read: omitted/'en' = the flat docs/vcr/ document, exactly the default behavior; any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = that language's variant at docs/vcr/{language}/ (the EN source's exact basename). The language is shape-validated before any file access (a path-injection-shaped payload is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError, also before any file access. A raw=True read of a variant returns the variant's on-disk body verbatim (translation preserved); a raw=False read parses it through the variant's heading table, so the structured model holds canonical heading text. The ParseFailureResult error of a broken variant is byte-identical to list_vcr(language=...)'s failed row for the same file.

Inputs: id (required), raw, limit, offset, +2 more. Returns: result

get_vcr_exampleGet VCR exampleA

Return a complete, valid sample verification case record document as raw markdown -- frontmatter and body -- exercising every section, for use as a learning example.

Inputs: no named parameters. Returns: result

get_vcr_schemaGet VCR schemaA

Return the VCR domain's generated JSON Schema (2020-12 dialect) as raw text -- the machine-readable, parsed-JSON view of the document structure, not the markdown view (call get_vcr_template for the authoring starting point). Fetch it for constraint discovery, structural questions, or an unresolved validation error -- never as a mandatory pre-authoring fetch. Generated by specmgr schema and kept current by a pre-commit hook/CI step; includes a $comment schema-layout version marker for detecting a shape change without diffing the whole document.

Inputs: no named parameters. Returns: result

get_vcr_templateGet VCR templateA

Return a VCR document template -- frontmatter and every body field present, populated with short placeholder ('blind text') content -- as raw markdown, for use as a starting point when drafting a new verification case record.

Inputs: no named parameters. Returns: result

list_adrList ADRsA

Ids, titles, statuses, and refs of ADRs in the configured ADR base directory (SPECMGR_ADR_DIR), one page at a time, for context before addressing one by id. 'ref' is an opaque, extensionless identifier -- not a filename to read from disk -- for documents that have no assigned id; use get_adr with it instead. max_results/offset control paging (default page size 25, capped at 100); out-of-range values are clamped, not errored.

Inputs: offset, max_results. Returns: total, offset, results, truncated, +2 more

list_decList decisionsA

Ids, titles, statuses, and refs of decisions in the configured decision base directory, one page at a time, for context before addressing one by id. 'ref' is an opaque, extensionless identifier -- not a filename to read from disk -- for documents that have no assigned id; use it with the get_dec tool instead. The optional language parameter (default 'en') selects the directory scanned: omitted/'en' = the flat docs/dec/ directory (the default behavior, unchanged); any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = only that language's docs/dec/{language}/ subdirectory, listing exactly the existing variants of that language (there is deliberately no 'all languages' mode). The language is shape-validated before any file access (an invalid-shaped value is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError. An optional glob pattern (e.g. 'dead*') filters the listing to the decisions whose id matches it, case-insensitively; glob=None (the default) lists everything, and an empty string matches nothing. max_results/offset control paging (default page size 25, capped at 100); out-of-range values are clamped, not errored.

Inputs: glob, offset, language, max_results. Returns: total, offset, results, truncated, +2 more

list_featList featuresA

Ids, titles, statuses, and refs of features in the configured feature base directory, one page at a time, for context before addressing one by id. 'ref' is an opaque, extensionless identifier -- not a filename to read from disk -- for documents that have no assigned id; use it with the get_feat tool instead. An optional glob pattern (e.g. 'feat-7*') filters the listing to the features whose id matches it, case-insensitively; glob=None (the default) lists everything, and an empty string matches nothing. max_results/offset control paging (default page size 25, capped at 100); out-of-range values are clamped, not errored. The request path never fully parses a document's body (GitHub issue #187): a folder's row is resolved from its frontmatter+H1 alone until a background warmup (or an on-demand get_feat read) has fully parsed it, so a body-level parse failure may transiently appear healthy, and a missing/wrong-shape H1's error text may transiently differ from get_feat's -- both converge to get_feat's exact error once that file has been fully parsed. Set SPECMGR_FEAT_WARMUP_DISABLED to turn the background warmup off entirely (convergence then depends solely on on-demand reads).

Inputs: glob, offset, max_results. Returns: total, offset, results, truncated, +2 more

list_golList goalsA

Ids, titles, statuses, and refs of goals in the configured goal base directory, one page at a time, for context before addressing one by id. 'ref' is an opaque, extensionless identifier -- not a filename to read from disk -- for documents that have no assigned id; use it with the get_gol tool instead. An optional glob pattern (e.g. 'dead*') filters the listing to the goals whose id matches it, case-insensitively; glob=None (the default) lists everything, and an empty string matches nothing. max_results/offset control paging (default page size 25, capped at 100); out-of-range values are clamped, not errored. The optional language parameter (default 'en') selects the directory scanned: omitted/'en' = the flat docs/gol/ directory (the default behavior, unchanged); any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = only that language's docs/gol/{language}/ subdirectory, listing exactly the existing variants of that language (there is deliberately no 'all languages' mode). The language is shape-validated before any file access (an invalid-shaped value is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError.

Inputs: glob, offset, language, max_results. Returns: total, offset, results, truncated, +2 more

list_languagesList the languages an artifact exists inA

Read-only. Report the language codes one artifact currently exists in. type is one of req, uc, tsk, qa, prb, gol, rsk, dec, sop, vcr, sysrs, txt (feat and adr documents are always English and are rejected with a ValueError) and id the artifact's id, shared by every language variant. Returns a LanguageListResult: languages -- 'en' first when the EN source exists (it is never invented: an id with variants but no EN source lists no 'en'), then every other language holding a variant, sorted by code -- and one entries row per language with language, path (the resolved absolute file path), parseable, and error; a variant that exists but fails to parse is listed with parseable=false and the parse error (byte-identical to the domain's list_/get_ failure text for that file), never dropped and never raised. Only .md documents under docs/{type}/ (EN) and docs/{type}/{language}/ participate; non-.md siblings and directory names that are not a two-letter lowercase language code are ignored. An id that exists in no language raises the domain's own not-found error; an unknown type or a path-injection/wrong-format id is a ValueError raised before any file access. It writes, creates and invalidates nothing. To translate use the translate_artifact prompt; to compare a variant with its source use check_translation_drift.

Inputs: id (required), type (required). Returns: The languages one artifact (``type``/``id``) currently exists in. Parameters --…

list_prbList problem statementsA

Ids, titles, statuses, and refs of problem statements in the configured problem statement base directory, one page at a time, for context before addressing one by id. 'ref' is an opaque, extensionless identifier -- not a filename to read from disk -- for documents that have no assigned id; use it with the get_prb tool instead. An optional glob pattern (e.g. 'dead*') filters the listing to the problem statements whose id matches it, case-insensitively; glob=None (the default) lists everything, and an empty string matches nothing. max_results/offset control paging (default page size 25, capped at 100); out-of-range values are clamped, not errored. The optional language parameter (default 'en') selects the directory scanned: omitted/'en' = the flat docs/prb/ directory (the default behavior, unchanged); any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = only that language's docs/prb/{language}/ subdirectory, listing exactly the existing variants of that language (there is deliberately no 'all languages' mode). The language is shape-validated before any file access (an invalid-shaped value is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError.

Inputs: glob, offset, language, max_results. Returns: total, offset, results, truncated, +2 more

list_qaList QA documentsA

Ids, titles, statuses, and refs of QA documents in the configured QA base directory, one page at a time, for context before addressing one by id. 'ref' is an opaque, extensionless identifier -- not a filename to read from disk -- for documents that have no assigned id; use it with the get_qa tool instead. An optional glob pattern (e.g. 'dead*') filters the listing to the QA documents whose id matches it, case-insensitively; glob=None (the default) lists everything, and an empty string matches nothing. max_results/offset control paging (default page size 25, capped at 100); out-of-range values are clamped, not errored. The optional language parameter (default 'en') selects the directory scanned: omitted/'en' = the flat docs/qa/ directory (the default behavior, unchanged); any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = only that language's docs/qa/{language}/ subdirectory, listing exactly the existing variants of that language (there is deliberately no 'all languages' mode). The language is shape-validated before any file access (an invalid-shaped value is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError.

Inputs: glob, offset, language, max_results. Returns: total, offset, results, truncated, +2 more

list_referencesList referenced artifactsA

Cross-references of one source document, resolved and paged. type is the source document's domain (one of adr, req, uc, tsk, qa, prb, gol, rsk, dec, sop, feat, vcr, sysrs, txt) and id the source's own identifier. The tool scans the source's frontmatter-stripped body for reference tags (the shared reference-tag vocabulary in general.tools._references: the UUID tags GOL/PRB/QA/UC/REQ/RSK/DEC/ADR/VCR/SYSRS/TXT, each carrying a canonical uuid id, plus FEAT, carrying the full feat-NNN-slug id or the bare feat-NNN number; case-insensitive tag, space/tab/dash separator, anywhere in a line), dedupes repeated occurrences (first-occurrence order preserved), and resolves each unique reference to the referenced document in its own target domain. Returns a PagedResult of one ReferenceRow per unique reference: type, id, title (the referenced document's H1), and path (the referenced document's resolved absolute file path). A reference that cannot be resolved on disk is a row with null title/path and the target domain's not-found message in error -- it never raises. max_results/offset control paging (default page size 25, capped at 100); out-of-range values are clamped, not errored. An invalid id (path-injection attempt or wrong format for type) is a ValueError raised before any file access; a missing source document is the source domain's own XNotFoundError.

Inputs: id (required), type (required), offset, max_results. Returns: total, offset, results, truncated, +2 more

list_reqList requirementsA

Ids, titles, statuses, and refs of requirements in the configured requirement base directory, one page at a time, for context before addressing one by id. 'ref' is an opaque, extensionless identifier -- not a filename to read from disk -- for documents that have no assigned id; use it with the get_req tool instead. An optional glob pattern (e.g. 'dead*') filters the listing to the requirements whose id matches it, case-insensitively; glob=None (the default) lists everything, and an empty string matches nothing. max_results/offset control paging (default page size 25, capped at 100); out-of-range values are clamped, not errored. The optional language parameter (default 'en') selects the directory scanned: omitted/'en' = the flat docs/req/ directory (the default behavior, unchanged); any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = only that language's docs/req/{language}/ subdirectory, listing exactly the existing variants of that language (there is deliberately no 'all languages' mode). The language is shape-validated before any file access (an invalid-shaped value is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError.

Inputs: glob, offset, language, max_results. Returns: total, offset, results, truncated, +2 more

list_rskList risksA

Ids, titles, statuses, and refs of risks in the configured risk base directory, one page at a time, for context before addressing one by id. Each line also carries the initial/residual 5x5 zone levels, the TARA strategy word, the first ## Scope entry, and the residual-risk coordinates (residual_probability/residual_impact/residual_product). 'ref' is an opaque, extensionless identifier -- not a filename to read from disk -- for documents that have no assigned id; use it with the get_rsk tool instead. An optional glob pattern (e.g. 'dead*') filters the listing to the risks whose id matches it, case-insensitively; glob=None (the default) lists everything, and an empty string matches nothing. max_results/offset control paging (default page size 25, capped at 100); out-of-range values are clamped, not errored. The optional language parameter (default 'en') selects the directory scanned: omitted/'en' = the flat docs/rsk/ directory (the default behavior, unchanged); any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = only that language's docs/rsk/{language}/ subdirectory, listing exactly the existing variants of that language (there is deliberately no 'all languages' mode). The language is shape-validated before any file access (an invalid-shaped value is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError.

Inputs: glob, offset, language, max_results. Returns: total, offset, results, truncated, +2 more

list_sopList Standard Operating ProceduresA

Ids, titles, statuses, and refs of Standard Operating Procedures in the configured SOP base directory, one page at a time, for context before addressing one by id. 'ref' is an opaque, extensionless identifier -- not a filename to read from disk -- for documents that have no assigned id; use it with the get_sop tool instead. An optional glob pattern (e.g. 'dead*') filters the listing to the Standard Operating Procedures whose id matches it, case-insensitively; glob=None (the default) lists everything, and an empty string matches nothing. max_results/offset control paging (default page size 25, capped at 100); out-of-range values are clamped, not errored. The optional language parameter (default 'en') selects the directory scanned: omitted/'en' = the flat docs/sop/ directory (the default behavior, unchanged); any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = only that language's docs/sop/{language}/ subdirectory, listing exactly the existing variants of that language (there is deliberately no 'all languages' mode). The language is shape-validated before any file access (an invalid-shaped value is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError.

Inputs: glob, offset, language, max_results. Returns: total, offset, results, truncated, +2 more

list_sysrsList System Requirements SpecificationsA

Ids, titles, statuses, and refs of System Requirements Specifications in the configured System Requirements Specification base directory, one page at a time, for context before addressing one by id. 'ref' is an opaque, extensionless identifier -- not a filename to read from disk -- for documents that have no assigned id; use it with the get_sysrs tool instead. An optional glob pattern (e.g. 'dead*') filters the listing to the System Requirements Specifications whose id matches it, case-insensitively; glob=None (the default) lists everything, and an empty string matches nothing. max_results/offset control paging (default page size 25, capped at 100); out-of-range values are clamped, not errored. The optional language parameter (default 'en') selects the directory scanned: omitted/'en' = the flat docs/sysrs/ directory (the default behavior, unchanged); any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = only that language's docs/sysrs/{language}/ subdirectory, listing exactly the existing variants of that language (there is deliberately no 'all languages' mode). The language is shape-validated before any file access (an invalid-shaped value is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError.

Inputs: glob, offset, language, max_results. Returns: total, offset, results, truncated, +2 more

list_tskList task listsA

Ids, titles, statuses, and refs of task lists in the configured task list base directory, one page at a time, for context before addressing one by id. 'ref' is an opaque, extensionless identifier -- not a filename to read from disk -- for documents that have no assigned id; use it with the get_tsk tool instead. An optional glob pattern (e.g. 'dead*') filters the listing to the task lists whose id matches it, case-insensitively; glob=None (the default) lists everything, and an empty string matches nothing. max_results/offset control paging (default page size 25, capped at 100); out-of-range values are clamped, not errored. The optional language parameter (default 'en') selects the directory scanned: omitted/'en' = the flat docs/tsk/ directory (the default behavior, unchanged); any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = only that language's docs/tsk/{language}/ subdirectory, listing exactly the existing variants of that language (there is deliberately no 'all languages' mode). The language is shape-validated before any file access (an invalid-shaped value is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError.

Inputs: glob, offset, language, max_results. Returns: total, offset, results, truncated, +2 more

list_txtList free-form text documentsA

Ids, titles, statuses, and refs of free-form text documents in the configured free-form text base directory, one page at a time, for context before addressing one by id. 'ref' is an opaque, extensionless identifier -- not a filename to read from disk -- for documents that have no assigned id; use it with the get_txt tool instead. An optional glob pattern (e.g. 'dead*') filters the listing to the free-form text documents whose id matches it, case-insensitively; glob=None (the default) lists everything, and an empty string matches nothing. max_results/offset control paging (default page size 25, capped at 100); out-of-range values are clamped, not errored. The optional language parameter (default 'en') selects the directory scanned: omitted/'en' = the flat docs/txt/ directory (the default behavior, unchanged); any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = only that language's docs/txt/{language}/ subdirectory, listing exactly the existing variants of that language (there is deliberately no 'all languages' mode). The language is shape-validated before any file access (an invalid-shaped value is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError.

Inputs: glob, offset, language, max_results. Returns: total, offset, results, truncated, +2 more

list_ucList use casesA

Ids, titles, statuses, and refs of use cases in the configured use-case base directory, one page at a time, for context before addressing one by id. 'ref' is an opaque, extensionless identifier -- not a filename to read from disk -- for documents that have no assigned id; use it with the get_uc tool instead. An optional glob pattern (e.g. 'dead*') filters the listing to the use cases whose id matches it, case-insensitively; glob=None (the default) lists everything, and an empty string matches nothing. max_results/offset control paging (default page size 25, capped at 100); out-of-range values are clamped, not errored. The optional language parameter (default 'en') selects the directory scanned: omitted/'en' = the flat docs/uc/ directory (the default behavior, unchanged); any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = only that language's docs/uc/{language}/ subdirectory, listing exactly the existing variants of that language (there is deliberately no 'all languages' mode). The language is shape-validated before any file access (an invalid-shaped value is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError.

Inputs: glob, offset, language, max_results. Returns: total, offset, results, truncated, +2 more

list_vcrList verification case recordsA

Ids, titles, statuses, and refs of verification case records in the configured verification case record base directory, one page at a time, for context before addressing one by id. 'ref' is an opaque, extensionless identifier -- not a filename to read from disk -- for documents that have no assigned id; use it with the get_vcr tool instead. An optional glob pattern (e.g. 'dead*') filters the listing to the verification case records whose id matches it, case-insensitively; glob=None (the default) lists everything, and an empty string matches nothing. max_results/offset control paging (default page size 25, capped at 100); out-of-range values are clamped, not errored. The optional language parameter (default 'en') selects the directory scanned: omitted/'en' = the flat docs/vcr/ directory (the default behavior, unchanged); any other ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = only that language's docs/vcr/{language}/ subdirectory, listing exactly the existing variants of that language (there is deliberately no 'all languages' mode). The language is shape-validated before any file access (an invalid-shaped value is a ValueError, like an invalid id), and a shape-valid language without a packaged heading-mapping table is rejected up front with an actionable ValueError.

Inputs: glob, offset, language, max_results. Returns: total, offset, results, truncated, +2 more

mdformatFormat markdown documentA

Format a markdown file in place, preserving any YAML frontmatter. Returns True if the file was changed, False if already formatted.

Inputs: path (required). Returns: result

option_createCreate ADR OptionB

Append a new 'Option N: ...' sub-section, returning its assigned full title.

Inputs: id (required), value (required), partial_title (required). Returns: result

option_deleteDelete ADR OptionC

Remove the option named full_title, returning the remaining full titles.

Inputs: id (required), full_title (required). Returns: result

option_listList ADR OptionsC

Full titles of every current 'Option N: ...' sub-section, in document order.

Inputs: id (required). Returns: result

option_readRead ADR OptionC

Return the current content of the option named full_title.

Inputs: id (required), full_title (required). Returns: result

option_updateUpdate ADR OptionB

Full-content replace of the option named full_title, returning the new content.

Inputs: id (required), value (required), full_title (required). Returns: result

parse_decParse decisionA

Parse a decision markdown file (YAML frontmatter + body) from disk into a structured :class:~biz.dfch.specmgr.dec.models.v1.DecDocument. An existing file that fails to parse returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError/yaml.YAMLError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); a truly-absent or unreadable path still raises the OSError-family file-access error (FileNotFoundError/PermissionError/OSError) from Path.read_text(). The file's language is derived from its path position relative to the decision base directory: a file in the flat docs/dec/ directory parses canonical (no heading-mapping table); a file in a direct two-letter docs/dec/{language}/ subdirectory parses through that language's table (an actionable ValueError when no table exists for the language); any other location parses canonical.

Inputs: path (required). Returns: result

parse_featParse featureA

Parse a feature markdown file (YAML frontmatter + body) from disk into a structured :class:~biz.dfch.specmgr.feat.models.v1.FeatDocument. An existing file that fails to parse returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError/yaml.YAMLError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); a truly-absent or unreadable path still raises the OSError-family file-access error (FileNotFoundError/PermissionError/OSError) from Path.read_text().

Inputs: path (required). Returns: result

parse_golParse goalA

Parse a goal markdown file (YAML frontmatter + body) from disk into a structured :class:~biz.dfch.specmgr.gol.models.v1.GolDocument. An existing file that fails to parse returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError/yaml.YAMLError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); a truly-absent or unreadable path still raises the OSError-family file-access error (FileNotFoundError/PermissionError/OSError) from Path.read_text(). The file's language is derived from its path position relative to the goal base directory: a file in the flat docs/gol/ directory parses canonical (no heading-mapping table); a file in a direct two-letter docs/gol/{language}/ subdirectory parses through that language's table (an actionable ValueError when no table exists for the language); any other location parses canonical.

Inputs: path (required). Returns: result

parse_prbParse problem statementA

Parse a problem statement markdown file (YAML frontmatter + body) from disk into a structured :class:~biz.dfch.specmgr.prb.models.v1.PrbDocument. An existing file that fails to parse returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError/yaml.YAMLError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); a truly-absent or unreadable path still raises the OSError-family file-access error (FileNotFoundError/PermissionError/OSError) from Path.read_text(). The file's language is derived from its path position relative to the problem statement base directory: a file in the flat docs/prb/ directory parses canonical (no heading-mapping table); a file in a direct two-letter docs/prb/{language}/ subdirectory parses through that language's table (an actionable ValueError when no table exists for the language); any other location parses canonical.

Inputs: path (required). Returns: result

parse_qaParse QA documentA

Parse a QA document markdown file (YAML frontmatter + body) from disk into a structured :class:~biz.dfch.specmgr.qa.models.v2.QaDocument. An existing file that fails to parse returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError/yaml.YAMLError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); a truly-absent or unreadable path still raises the OSError-family file-access error (FileNotFoundError/PermissionError/OSError) from Path.read_text(). The file's language is derived from its path position relative to the QA document base directory: a file in the flat docs/qa/ directory parses canonical (no heading-mapping table); a file in a direct two-letter docs/qa/{language}/ subdirectory parses through that language's table (an actionable ValueError when no table exists for the language); any other location parses canonical.

Inputs: path (required). Returns: result

parse_reqParse requirementB

Parse a requirement markdown file (YAML frontmatter + body) from disk into a structured :class:~biz.dfch.specmgr.req.models.v1.ReqDocument. An existing file that fails to parse returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError/yaml.YAMLError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); a truly-absent or unreadable path still raises the OSError-family file-access error (FileNotFoundError/PermissionError/OSError) from Path.read_text(). The file's language is derived from its path position relative to the requirement base directory: a file in the flat docs/req/ directory parses canonical (no heading-mapping table); a file in a direct two-letter docs/req/{language}/ subdirectory parses through that language's table (an actionable ValueError when no table exists for the language); any other location parses canonical.

Inputs: path (required). Returns: result

parse_rskParse riskA

Parse a risk markdown file (YAML frontmatter + body) from disk into a structured :class:~biz.dfch.specmgr.rsk.models.v1.RskDocument. An existing file that fails to parse returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError/yaml.YAMLError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); a truly-absent or unreadable path still raises the OSError-family file-access error (FileNotFoundError/PermissionError/OSError) from Path.read_text(). The file's language is derived from its path position relative to the risk base directory: a file in the flat docs/rsk/ directory parses canonical (no heading-mapping table); a file in a direct two-letter docs/rsk/{language}/ subdirectory parses through that language's table (an actionable ValueError when no table exists for the language); any other location parses canonical.

Inputs: path (required). Returns: result

parse_sopParse Standard Operating ProcedureA

Parse a Standard Operating Procedure markdown file (YAML frontmatter + body) from disk into a structured :class:~biz.dfch.specmgr.sop.models.v1.SopDocument. An existing file that fails to parse returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError/yaml.YAMLError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); a truly-absent or unreadable path still raises the OSError-family file-access error (FileNotFoundError/PermissionError/OSError) from Path.read_text(). The file's language is derived from its path position relative to the Standard Operating Procedure base directory: a file in the flat docs/sop/ directory parses canonical (no heading-mapping table); a file in a direct two-letter docs/sop/{language}/ subdirectory parses through that language's table (an actionable ValueError when no table exists for the language); any other location parses canonical.

Inputs: path (required). Returns: result

parse_sysrsParse System Requirements SpecificationA

Parse a System Requirements Specification markdown file (YAML frontmatter + body) from disk into a structured :class:~biz.dfch.specmgr.sysrs.models.v1.SysrsDocument. An existing file that fails to parse returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError/yaml.YAMLError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); a truly-absent or unreadable path still raises the OSError-family file-access error (FileNotFoundError/PermissionError/OSError) from Path.read_text(). The file's language is derived from its path position relative to the System Requirements Specification base directory: a file in the flat docs/sysrs/ directory parses canonical (no heading-mapping table); a file in a direct two-letter docs/sysrs/{language}/ subdirectory parses through that language's table (an actionable ValueError when no table exists for the language); any other location parses canonical.

Inputs: path (required). Returns: result

parse_tskParse task listA

Parse a task list markdown file (YAML frontmatter + body) from disk into a structured :class:~biz.dfch.specmgr.tsk.models.v1.TskDocument. An existing file that fails to parse returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError/yaml.YAMLError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); a truly-absent or unreadable path still raises the OSError-family file-access error (FileNotFoundError/PermissionError/OSError) from Path.read_text(). The file's language is derived from its path position relative to the task list base directory: a file in the flat docs/tsk/ directory parses canonical (no heading-mapping table); a file in a direct two-letter docs/tsk/{language}/ subdirectory parses through that language's table (an actionable ValueError when no table exists for the language); any other location parses canonical.

Inputs: path (required). Returns: result

parse_txtParse free-form textA

Parse a free-form text markdown file (YAML frontmatter + body) from disk into a structured :class:~biz.dfch.specmgr.txt.models.v1.TxtDocument. An existing file that fails to parse returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError/yaml.YAMLError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); a truly-absent or unreadable path still raises the OSError-family file-access error (FileNotFoundError/PermissionError/OSError) from Path.read_text(). The file's language is derived from its path position relative to the free-form text base directory: a file in the flat docs/txt/ directory parses canonical (no heading-mapping table); a file in a direct two-letter docs/txt/{language}/ subdirectory parses through that language's table (an actionable ValueError when no table exists for the language); any other location parses canonical.

Inputs: path (required). Returns: result

parse_ucParse use caseA

Parse a use-case markdown file (YAML frontmatter + body) from disk into a structured document. An existing file that fails to parse returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError/yaml.YAMLError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); a truly-absent or unreadable path still raises the OSError-family file-access error (FileNotFoundError/PermissionError/OSError) from Path.read_text(). The file's language is derived from its path position relative to the use case base directory: a file in the flat docs/uc/ directory parses canonical (no heading-mapping table); a file in a direct two-letter docs/uc/{language}/ subdirectory parses through that language's table (an actionable ValueError when no table exists for the language); any other location parses canonical.

Inputs: path (required). Returns: result

parse_vcrParse verification case recordA

Parse a verification case record markdown file (YAML frontmatter + body) from disk into a structured :class:~biz.dfch.specmgr.vcr.models.v1.VcrDocument. An existing file that fails to parse returns a non-raising ValidateResult (valid=False, a single errors[].message capped at 300 chars as the generic validate tool caps it, feat-110) instead of raising AssertionError/pydantic.ValidationError/yaml.YAMLError (ADR f14f125e-eaad-4f4f-a6fd-3c931bed726e, GitHub issue #204 -- case 5 of the ADR 519d1206 non-raising-structured-result workaround chain); a truly-absent or unreadable path still raises the OSError-family file-access error (FileNotFoundError/PermissionError/OSError) from Path.read_text(). The file's language is derived from its path position relative to the verification case record base directory: a file in the flat docs/vcr/ directory parses canonical (no heading-mapping table); a file in a direct two-letter docs/vcr/{language}/ subdirectory parses through that language's table (an actionable ValueError when no table exists for the language); any other location parses canonical.

Inputs: path (required). Returns: result

plantuml_encodeEncode text to the PlantUML classic URL encodingA

Encode PlantUML (or any) text to the PlantUML classic URL encoding (custom-base64 0-9A-Za-z-_ over the raw-deflate stream of the text's UTF-8 bytes, no prefix -- the SoWkI... form, rulebook §5.1). Returns the encoding itself, i.e. the {enc} payload of GET {base}/svg/{enc} -- the tool takes no base URL, so prepend the configured server base (SPECMGR_PLANTUML_URL for the url source) to build the renderable URL. Fully offline: no source, subprocess, or network is involved. Decode it back with the library's decode_puml (round-trip lossless).

Inputs: text (required). Returns: result

set_classificationSet document classificationA

Replace the free-text classification frontmatter field of an existing document across the whole-body domains (type is one of req, uc, tsk, qa, prb, gol, rsk, dec, sop, feat, vcr, sysrs, txt; adr is not supported), also bumping updated and leaving the body and every other frontmatter field untouched. classification is fully free-text -- no closed vocabulary; a blank or whitespace-only value clears it back to None/absent. No create_* tool accepts a classification argument at all -- this is the sole classification-change entry point. A document that exists but fails to parse returns a ParseFailureResult (error/path/id) instead of raising the domain's not-found error (a truly-absent id still raises) -- its error text is byte-identical to the domain's list_<d> failed row's error for the same file (identical field path and cause, including the trailing pydantic documentation line; ADR 9080b37c-82b3-4f63-81f1-79641d0bf14c; ADR b8c9bfea-6dcf-4158-bfc5-4ec17abb842f, GitHub issue #170 -- case 4 of the ADR 519d1206 non-raising-structured-result workaround chain). An invalid id (path-injection attempt or wrong format for type) or an unknown type is a ValueError raised before any file access; type="adr" passes the id validation (a well-formed UUID id) and raises the plain KeyError inherited from the dispatch-table lookup instead. Returns the updated frontmatter only (no body) on success; use the corresponding get_<d> tool to fetch the full document afterward. The optional language parameter (default 'en', feat-197-i18n) selects the language variant to act on: omitted/'en' = the flat default-language document, exactly the default behavior; a non-default ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = that language's variant at docs//{language}/ (found by id-scan of that subdirectory), acting on that file ONLY -- the EN file's content and updated timestamp stay untouched. Every whole-body type except feat supports a non-default language; for feat (always English) a non-default language raises a ValueError, as does a malformed language or one with no packaged heading-mapping table -- all raised before any file access.

Inputs: id (required), type (required), classification (required), language. Returns: result

set_feat_idRename feature idA

Rename an existing feature's id: validates new_id's feat-NNN-slug shape, refuses if new_id's folder already exists, renames // to /<new_id>/, rewrites the README frontmatter id to new_id, bumps updated, and leaves the body byte-identical. Does not update or search for references to the old id in any other document. Returns the renamed document's frontmatter only (no body); use the corresponding get_feat tool to fetch the full document afterward.

Inputs: id (required), new_id (required). Returns: Feature (FEAT) frontmatter: `MarkdownFrontmatter` narrowed for the ``feat`` doc…

set_statusSet document statusA

Replace the status of an existing document across every domain (type is one of adr, req, uc, tsk, qa, prb, gol, rsk, dec, sop, feat, vcr, sysrs, txt), also bumping updated (the whole-body domains) and leaving the body untouched. When status (or, for type="adr" with superseded_by given, the composed "superseded by X" value) already equals the document's current status, this is a no-op -- no write, no updated bump -- and still returns the same value shape a changed-status call would (issue #109). The new status must be one of the domain's own closed vocabulary values (see the domain's XFrontmatter.status field); an out-of-vocabulary status does NOT raise -- it returns a structured, non-raising InvalidStatusResult ({valid: false, type, status, allowed_values, message}) instead, so the allowed-values detail survives MCP clients that truncate error content. superseded_by is accepted only for type="adr" -- it composes the status as "superseded by {superseded_by}"; with any other type it is a ValueError. A document that exists but fails to parse returns a ParseFailureResult (error/path/id) instead of raising the domain's not-found error (a truly-absent id still raises, and the out-of-vocabulary-status InvalidStatusResult check above still runs first, pre-lock/pre-load) -- its error text is byte-identical to the domain's list_<d> failed row's error for the same file (identical field path and cause, including the trailing pydantic documentation line; ADR 9080b37c-82b3-4f63-81f1-79641d0bf14c; ADR b8c9bfea-6dcf-4158-bfc5-4ec17abb842f, GitHub issue #170 -- case 4 of the ADR 519d1206 non-raising-structured-result workaround chain). Neither create_* nor the generic update tool accepts a status argument at all -- this is the sole status-change entry point. An invalid id (path-injection attempt or wrong format for type) is a ValueError raised before any file access. Returns the updated frontmatter only (no body, except for the unchanged adr branch) on success; use the corresponding get_<d> tool to fetch the full document afterward. The optional language parameter (default 'en', feat-197-i18n) selects the language variant to act on: omitted/'en' = the flat default-language document, exactly the default behavior; a non-default ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = that language's variant at docs//{language}/ (found by id-scan of that subdirectory), acting on that file ONLY -- the EN file's content and updated timestamp stay untouched. Every whole-body type except feat supports a non-default language; for feat and adr (always English) a non-default language raises a ValueError, as does a malformed language or one with no packaged heading-mapping table -- all raised before any file access.

Inputs: id (required), type (required), status (required), language, +1 more. Returns: result

updateUpdate documentA

Whole-body or line-range replace of an existing document's content across the whole-body domains (type is one of req, uc, tsk, qa, prb, gol, rsk, dec, sop, feat, vcr, sysrs, txt), preserving its id/type/status/created/version; only updated changes. With no offset/limit, content is the full replacement body (body markdown only, no frontmatter block). With offset, content replaces the body line(s) starting at 1-based line offset of the current on-disk body: limit is the number of lines to replace (offset..offset+limit-1; limit omitted = through the last body line, limit=0 = pure insert), and offset=N+1 (one past the last body line) appends after it; the spliced result is validated as a whole document before anything is written. offset/limit address the frontmatter-stripped body, never the raw on-disk .md file: the YAML frontmatter block is variable-length, so a raw file read's line numbers are never the same as body-line coordinates -- the only safe source of coordinates is a get_<d>(id, raw=True) read, never a raw file read minus an assumed constant. A get_<d>(raw=True, numbered=True) read's numbered output must never be fed back verbatim into content -- strip the "<n>: " prefix from each line first (it is likewise never a valid edit old_str). status is never settable -- use the generic set_status tool. A document that exists but fails to parse returns a ParseFailureResult (error/path/id) instead of raising the domain's not-found error -- its error text is byte-identical to the domain's list_<d> failed row's error for the same file (identical field path and cause, including the trailing pydantic documentation line; ADR 9080b37c-82b3-4f63-81f1-79641d0bf14c) -- and a truly-absent id still raises the domain's not-found error. A content-validation failure on the submitted new content (or, in range mode, on the spliced result) returns a non-raising ValidateResult (valid=False, errors=[{message}]) with the message capped at 300 chars as the generic validate tool caps it, instead of raising AssertionError/pydantic.ValidationError (ADR b8c9bfea-6dcf-4158-bfc5-4ec17abb842f, GitHub issue #170 -- case 4 of the ADR 519d1206 non-raising-structured-result workaround chain). An invalid id (path-injection attempt or wrong format for type) or misused range coordinates (limit without offset) is a ValueError raised before any file access. Each domain's structure reference is its own get_<d>_schema tool (the generated JSON Schema), with get_<d>_template/get_<d>_example as the authoring starting points. Returns an UpdateResult: frontmatter, the updated frontmatter (no body) of the dispatched domain, plus snippet -- in range mode, the before/after window of the touched range (dropped lines numbered pre-splice, inserted lines numbered post-splice, up to 2 unchanged context lines per side, each line <marker> <n>: <line text>); snippet is None in whole-body mode and for the whole-body-equivalent range (offset=1 with limit omitted). Use the corresponding get_<d> tool to fetch the full document afterward. The optional language parameter (default 'en', feat-197-i18n) selects the language variant to act on: omitted/'en' = the flat default-language document, exactly the default behavior; a non-default ISO 639-1-shaped lowercase two-letter code (e.g. 'de') = that language's variant at docs//{language}/ (found by id-scan of that subdirectory), acting on that file ONLY -- the EN file's content and updated timestamp stay untouched -- with the submitted content validated through that language's heading-mapping table (translated headings are accepted). Every whole-body type except feat supports a non-default language; for feat (always English) a non-default language raises a ValueError, as does a malformed language or one with no packaged heading-mapping table -- all raised before any file access.

Inputs: id (required), type (required), content (required), limit, +2 more. Returns: result

update_frontmatterUpdate ADR FrontmatterA

Whole-object replace of an ADR's frontmatter, preserving its existing id.

Inputs: id (required), frontmatter (required). Returns: A full ADR document: YAML frontmatter and body. This is the structured object t…

update_sectionUpdate ADR SectionC

Whole-section replace/delete of one AdrBody field.

Inputs: id (required), key (required), value (required). Returns: A full ADR document: YAML frontmatter and body. This is the structured object t…

validateValidate document contentA

Disk-free, id-free dry run validating document content across the whole-body domains (type is one of req, uc, tsk, qa, prb, gol, rsk, dec, sop, feat, vcr, sysrs, txt; adr is not supported -- use validate_adr instead). full=False (default) validates body-only content (no frontmatter); full=True validates a complete document (frontmatter + body). Never raises for a content-validation failure: always returns {valid: bool, errors: list[{message: str}]} -- errors is empty when valid is True, and each message is truncated to at most _MAX_VALIDATE_ERROR_CHARS (300) characters (plus an "... (truncated)" suffix when truncation occurred) rather than returned verbatim without limit. A full/content-shape mismatch, or an unsupported type, is a caller-usage error and still raises ValueError before any validation runs. This is the sole validate entry point for these domains -- the former per-domain validate_<d> tools are removed; validate_adr remains a separate, unchanged, id-based tool. Each domain's structure reference is its own get_<d>_schema tool (the generated JSON Schema), with get_<d>_template/get_<d>_example as the authoring starting points.

Inputs: type (required), content (required), full. Returns: The generic ``validate`` tool's return shape: never raises for a content-valida…

validate_adrValidate ADRB

Re-read and re-parse an ADR by id, letting the models' own Pydantic validators run.

Inputs: id (required). Returns: result

validate_plantumlValidate PlantUML diagram textA

Validate PlantUML diagram text through the strict validation chain (the frozen rulebook specmgr://uc/plantuml §3): the structure check always runs first (on a structure red the parser is never called -- no subprocess, no network), then the single configured source (the first set of SPECMGR_PLANTUML_JAR/SPECMGR_PLANTUML_BIN/SPECMGR_PLANTUML_URL; no fall-through -- a set-but-unavailable source is a hard failure with reason + fix hint), and the all-unset state degrades to the structure-only floor. A PlantUML server crash page (200 + the embedded Java-exception trace -- the recognised 1.2026.8 crash line of the rulebook §5.2 matrix, the known self-message/note-left shape bug) is a recognised INVALID row: valid=false with the line-0 crash finding carrying the exception class and a fix hint pointing at the rulebook §2.9 anchored notes. Content-based: pass the diagram text (a CLI reads the file and passes its text). Never raises for validation content -- returns the non-raising §3.6 result model: structure_ok, valid/rendered (None = not run, never run-and-failed), checked_by (jar/bin/url/structure), errors and warnings (1-based line + message + fix hint), source_state (ok/misconfigured/unavailable/inconclusive/none), available, reason, fix_hint.

Inputs: text (required). Returns: valid, errors, reason, fix_hint, +6 more

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.5/5.0

    Scored across 124 tools

    Disambiguation4/5

    Each tool targets a distinct (domain, action) pair — create_prb vs create_req vs get_tsk etc. are clearly separated by domain token, and the generic update/edit/delete/set_status/set_classification are clearly scoped. There is mild ambiguity between parallel families (get_X id-based read vs parse_X path-based parse, update vs edit vs option_update vs update_section, get_reference_diagram vs _all), but descriptions disambiguate well.

    Naming Consistency3/5

    Most tools follow verb_domain (create_prb, get_tsk, list_gol, parse_sysrs), but there is notable inconsistency: bare verbs with no noun (update, edit, delete, validate, mdformat), noun_verb reversal (option_create, option_list, option_read), mixed uc vs use_case spelling (get_uc_schema vs get_use_case_package_diagram), and some domain-in-name vs domain-as-parameter splits.

    Tool Count2/5

    124 tools for a 14-domain spec manager is heavy; while the per-domain parallelism (create/get/list/parse/template/example/schema for ~13 domains) partially justifies the count, it is far past the practical ceiling for agent navigation and surfaces many rarely-needed variants.

    Completeness4/5

    Coverage is very strong: full create/get/list/validate/update/edit/delete lifecycle across 13 whole-body domains plus ADR-specific tools, references, similarity search, diagrams, and translation drift. Minor gaps: ADR is excluded from delete/set_status/set_classification/validate generic tools and has no delete_adr, leaving some ADR lifecycle dead ends.

    Maintenance

    ActivityActive
    ResponsivenessLimited data

    Related MCP Connectors

    Related MCP Servers