mcp: consolidate lot 1 metadata and planning

This commit is contained in:
MaksTinyWorkshop
2026-03-31 15:39:20 +02:00
parent 547ffb8e6f
commit ff8eac0dfb
20 changed files with 788 additions and 8 deletions

View File

@@ -10,9 +10,10 @@ from leadtech_bmad_mcp.knowledge import (
search_knowledge,
search_global_docs,
read_knowledge_doc,
read_knowledge_document,
_extract_excerpt,
parse_front_matter,
LeadtechPaths,
get_paths,
)
@@ -122,6 +123,30 @@ def test_search_knowledge_single_bucket(tmp_path):
assert all(r["bucket"] == "risques" for r in results)
def test_search_knowledge_uses_front_matter_tags(tmp_path):
knowledge = tmp_path / "knowledge"
(knowledge / "backend" / "patterns").mkdir(parents=True)
(knowledge / "backend" / "patterns" / "nestjs.md").write_text(
"---\n"
"title: Backend — Patterns : NestJS\n"
"tags: [guards, auth]\n"
"---\n\n"
"# NestJS\n\n"
"Texte volontairement sans le mot cle demande.\n",
encoding="utf-8",
)
paths = LeadtechPaths(
root=tmp_path,
knowledge=knowledge,
capitalisation=tmp_path / "95_a_capitaliser.md",
projects_conf=tmp_path / "_projects.conf",
)
with patch("leadtech_bmad_mcp.knowledge.get_paths", return_value=paths):
results = search_knowledge("backend", "guards")
assert results
assert results[0]["title"] == "Backend — Patterns : NestJS"
# ---------------------------------------------------------------------------
# read_knowledge_doc
# ---------------------------------------------------------------------------
@@ -147,6 +172,23 @@ def test_read_knowledge_doc_traversal_blocked(tmp_path):
read_knowledge_doc("backend", "patterns", "../../etc/passwd")
def test_read_knowledge_document_splits_metadata_and_body(tmp_path):
file_path = tmp_path / "doc.md"
file_path.write_text(
"---\n"
"title: Test Doc\n"
"tags: [alpha, beta]\n"
"---\n\n"
"# Heading\n\n"
"Contenu principal.\n",
encoding="utf-8",
)
doc = read_knowledge_document(file_path)
assert doc.metadata["title"] == "Test Doc"
assert doc.metadata["tags"] == ["alpha", "beta"]
assert doc.body.startswith("# Heading")
# ---------------------------------------------------------------------------
# _extract_excerpt
# ---------------------------------------------------------------------------
@@ -176,6 +218,31 @@ def test_extract_excerpt_length_bounded():
assert len(excerpt) <= 500
def test_parse_front_matter_returns_body_unchanged_without_header():
content = "# Heading\n\nPlain body"
metadata, body = parse_front_matter(content)
assert metadata == {}
assert body == content
def test_parse_front_matter_parses_lists_and_scalars():
content = (
"---\n"
"title: Demo\n"
"tags: [alpha, beta]\n"
"severity: high\n"
"enabled: true\n"
"---\n\n"
"Body\n"
)
metadata, body = parse_front_matter(content)
assert metadata["title"] == "Demo"
assert metadata["tags"] == ["alpha", "beta"]
assert metadata["severity"] == "high"
assert metadata["enabled"] is True
assert body == "Body\n"
# ---------------------------------------------------------------------------
# search_global_docs
# ---------------------------------------------------------------------------

View File

@@ -32,7 +32,7 @@ def _mock_mcp_module():
_mock_mcp_module()
from leadtech_bmad_mcp.server import validate_plan, validate_patch # noqa: E402
from leadtech_bmad_mcp.server import get_guidance, validate_plan, validate_patch # noqa: E402
from leadtech_bmad_mcp.knowledge import LeadtechPaths # noqa: E402
@@ -88,6 +88,41 @@ class TestValidatePlanContracts:
assert not contract_blocks
class TestGetGuidanceMetadata:
def test_exposes_matched_docs_with_metadata(self, tmp_path):
paths = _fake_paths(tmp_path)
with patch(
"leadtech_bmad_mcp.server.search_knowledge",
return_value=[
{
"path": str(tmp_path / "knowledge" / "backend" / "patterns" / "nestjs.md"),
"bucket": "patterns",
"title": "Backend — Patterns : NestJS",
"score": "9",
"excerpt": "Pattern sur les guards globaux.",
"severity": "medium",
"applies_to": "analysis, implementation, review",
"tags": "nestjs, guards, auth",
}
],
), patch(
"leadtech_bmad_mcp.server.search_global_docs",
return_value=[],
), patch(
"leadtech_bmad_mcp.server.get_paths",
return_value=paths,
):
result = get_guidance("backend", "implementation", story_text="Ajouter un guard NestJS")
assert result["matched_docs"]
matched = result["matched_docs"][0]
assert matched["bucket"] == "patterns"
assert matched["severity"] == "medium"
assert "implementation" in matched["applies_to"]
assert matched["read_uri"] == "leadtech://knowledge/backend/patterns/nestjs"
assert any("severity=medium" in item for item in result["must_do"])
class TestValidatePlanRequestId:
def test_suggests_requestid_when_error_without_requestid(self, tmp_path):
paths = _fake_paths(tmp_path)