1
0
Fork 0
Code Issues Pull requests Projects Releases 2 Packages Wiki Activity Actions Pages

Complete DFG-8 adapter proposal support

This commit is contained in:
Andraxion 2026-07-22 11:50:49 -04:00
parent f1e31487c0
commit 591bf0ae7d
14 changed files with 525 additions and 75 deletions

View file

@ -2,6 +2,7 @@ from __future__ import annotations
import tempfile
import unittest
from collections.abc import Mapping
from dataclasses import replace
from pathlib import Path
@ -12,14 +13,27 @@ from docforge.adapter_contract import (
AdapterNode,
AdapterProject,
AdapterProjection,
AdapterProjectSettings,
ShadowArtifact,
compare_artifacts,
validate_projection,
)
from docforge.errors import DocForgeError
from docforge.index import ProjectIndex
from docforge.mcp_server import READ_TOOLS, create_read_only_server
from docforge.models import Edge, Node
from docforge.mcp_server import (
ALL_TOOLS,
READ_TOOLS,
create_project_server,
create_read_only_server,
)
from docforge.models import (
Edge,
Node,
ProjectSnapshot,
ProposalWriter,
RenderConfig,
RenderView,
)
class Loader:
@ -221,6 +235,110 @@ class AdapterReadOnlyMcpTests(unittest.IsolatedAsyncioTestCase):
self.assertEqual([("fixture", 321)], calls)
self.assertFalse(project.descriptor.changeset_root.exists())
async def test_adapter_project_proposals_require_explicit_policy_and_stay_isolated(
self,
) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
(root / ".docforge").mkdir()
descriptor = root / ".docforge" / "project.toml"
descriptor.write_text("adapter fixture\n", encoding="utf-8")
content_root = root / "docs"
content_root.mkdir()
sources = (content_root / "foundation.md", content_root / "workflow.md")
for source in sources:
source.write_text(f"canonical {source.stem}\n", encoding="utf-8")
template_root = root / "templates"
template_root.mkdir()
template = template_root / "review.html"
template.write_text(
"<html><head><title>{{ docforge_title }}</title></head>"
"<body>{{ docforge_content }}</body></html>\n",
encoding="utf-8",
)
validation_calls: list[tuple[Mapping[str, object], ...]] = []
def validate_proposal(
base: ProjectSnapshot,
projected: ProjectSnapshot,
operations: tuple[Mapping[str, object], ...],
) -> None:
self.assertEqual(base.edges, projected.edges)
validation_calls.append(operations)
settings = AdapterProjectSettings(
descriptor_path=descriptor,
content_roots=(content_root,),
canonical_sources=sources,
changeset_root=root / ".cache" / "changesets",
proposal_writers=(ProposalWriter("fixture-writer", ("guide",), ("update",)),),
render=RenderConfig(
template_root=template_root,
preview_root=root / ".cache" / "previews",
views=(
RenderView(
"review",
"generic_html",
template,
root / ".cache" / "rendered" / "review.html",
"Adapter review",
("guide",),
),
),
),
proposal_validator=validate_proposal,
)
project = AdapterProject(
Loader(AdapterContractTests().projection(root)),
cache_root=root / ".cache" / "adapter",
settings=settings,
)
ProjectIndex(project).build()
before = {source: source.read_bytes() for source in sources}
server = create_project_server(project, proposal_writer="fixture-writer")
async with create_connected_server_and_client_session(
server, raise_exceptions=True
) as session:
tools = await session.list_tools()
created = await session.call_tool(
"docforge_create_changeset", {"changeset_id": "adapter-update"}
)
node = await session.call_tool("docforge_get_node", {"node_id": "guide.workflow"})
proposed = await session.call_tool(
"docforge_propose_node_update",
{
"changeset_id": "adapter-update",
"expected_changeset_hash": created.structuredContent["changeset_hash"],
"node_id": "guide.workflow",
"expected_content_hash": node.structuredContent["node"]["content_hash"],
"metadata": None,
"content": "Updated workflow content.",
"relationship_changes": [],
"rationale": "Prove adapter proposal policy.",
},
)
validated = await session.call_tool(
"docforge_validate_changeset",
{"changeset_id": "adapter-update"},
)
diff = await session.call_tool(
"docforge_get_changeset_diff",
{"changeset_id": "adapter-update"},
)
preview = await session.call_tool(
"docforge_preview_changeset",
{"changeset_id": "adapter-update", "view_id": "review"},
)
self.assertEqual(ALL_TOOLS, tuple(tool.name for tool in tools.tools))
self.assertEqual("ok", proposed.structuredContent["status"])
self.assertTrue(validated.structuredContent["valid"])
self.assertEqual("update", diff.structuredContent["changes"][0]["operation"])
preview_path = root / preview.structuredContent["preview"]["path"]
self.assertTrue(preview_path.is_file())
self.assertTrue(validation_calls)
self.assertEqual(before, {source: source.read_bytes() for source in sources})
if __name__ == "__main__":
unittest.main()