biz.dfch.SpecMgr
Provides a tool to fetch content from Confluence instances using bearer-token authentication, with requests filtered to a configured base URL.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@biz.dfch.SpecMgr list all architecture decision records"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
biz.dfch.SpecMgr
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 |
SOP — Standard Operating Procedure | Step-by-step operational procedure with RASCI responsibility assignment and approval lifecycle |
FEAT — Feature | Development work-unit plan/progress tracking ( |
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. |
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 |
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-specmgrWith 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… encodingValidation 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 theSPECMGR_ADR_DIRenvironment 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_DIRenvironment variable (defaultdocs), with each type's own subdirectory appended automatically (e.g.docs/reqfor requirements).Features (FEAT): base directory defaults to
.specmgr/feat, configurable via theSPECMGR_FEAT_DIRenvironment variable. This is FEAT-specific, like ADRs above, and not shared viaSPECMGR_DOCS_DIR.PlantUML validation source (UC diagrams):
SPECMGR_PLANTUML_JAR(a plantuml.jar path),SPECMGR_PLANTUML_BIN(an executable speaking the PlantUML CLI contract), andSPECMGR_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, default40) — the maximum length of a resolved node's H1 label in theget_reference_diagram/get_reference_diagram_allPlantUML diagrams (word-boundary cut +…; the diagram title itself is never truncated). A set-but-invalid value (empty, non-int, or < 1) falls back to40. Its set/valid/default/resolved status is reported by thediagramsection of thespecmgr://configresource (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.
Semantic Similarity Search
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 mcpBy 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 8000Or 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 8000Option | Env var | Default | Description |
|
|
| Transport mode: |
|
|
| Bind address (SSE/streamable-http mode only) |
|
|
| TCP port (SSE/streamable-http mode only) |
Add to OpenCode
To add the specmgr MCP server to your OpenCode configuration:
Open your OpenCode config file (typically
~/.config/opencode/opencode.jsonor~/.config/opencode/opencode.jsonc)Add a configuration to the
mcpsection (and use it viastdio).A bare
uvx --from biz-dfch-specmgr[mcp] specmgr mcpcommand with no--directoryand noSPECMGR_*_DIRenv 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 globaluv/uvxflag, 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--fromvalue 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--fromvalue 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://configresource, 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 resolvesbiz-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 ownpyproject.toml, lockfile, and source tree directly, including unreleased/dev-branch commits — useuv run --directory <path-to-your-checkout>(no--from, notuvx):"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
--extraflags (or--all-extrasfor everything) control which optional dependencies get synced into that local run, independent of what's published on PyPI — drop--extra similarityif you don't need Semantic Similarity Search.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>andcreate_<d>, and the genericupdate,edit,set_status,set_classificationanddeletetools take an optionallanguage(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>).validatestays 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, RSKStrategy/Probability/Impact, VCRCoverageandAC-NNN (Method),Option N:/Step N:tokens,[ ]/[x], cross-reference tags, SOP lifecycle values) are never translated, and neither is the PRBproblem_statementsentence.Translating. The MCP prompt
translate_artifact(type,id,language, optionalsource_language) drives the translation; the resourcesspecmgr://i18nandspecmgr://i18n/headings/{language}document the rules and return a language's heading table. In opencode: thetranslate-artifactskill 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: thetranslation-statusskill and/translation-status <type> <id> [language].English-only search and references.
find_related,find_similar_text,list_referencesandget_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 testtests/general/models/test_heading_table_drift.pychecks 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 titleQuellestaysQuelle).
Development
Install dev dependencies
uv sync --all-extrasInstall pre-commit hooks (one-time)
uv run --frozen pre-commit installuv 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
mcpextra installed (see Installation), so.venv/bin/specmgrexists.npx(ships with Node.js, version 22.19.0 or newer) — no separate Inspector install is required, it runs on demand vianpx @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/listCLI 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://versionList task lists via the list_tsk tool:
npx @modelcontextprotocol/inspector --cli .venv/bin/specmgr mcp \
--method tools/call --tool-name list_tskGet 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 mcpThis 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:stderrfrom the connectedspecmgr mcpprocess (tracebacks land here).c/d— connect / disconnect;EscorCtrl+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.
Using the OpenCode command (recommended)
/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
Available Tools
124 toolscheck_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
Scored across 124 tools
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.
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.
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.
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
Related MCP Connectors
MCP server for Product Management
A MCP server built for developers enabling Git based project management with project and personal…
MCP server for innovationlab documentation, generated by doc2mcp.
MCP server for mandates, delegation, policy-gated execution, credential grants, and audit.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceShared project memory MCP server for storing and retrieving technical decisions, lessons learned, ADRs, and cross-links between projects.-
- AlicenseNot gradedqualityCmaintenanceMCP server for managing an OKF markdown knowledge base, enabling AI agents to query, read, write, and auto-sync architectural decisions and rules via Git.MIT
- AlicenseNot gradedqualityCmaintenanceMCP server that manages C4 architecture models, ADRs, and flow diagrams as TOML files in a docs directory, with git versioning and an HTTP viewer.113 PyPIMIT
- AlicenseNot gradedqualityCmaintenanceAn MCP server that turns a folder of Markdown ADRs into live tools for AI agents: search, author, validate, link, and trace architectural decisions, with preview-by-default writes.1MIT