Preserve no-AST adapter bindings
This commit is contained in:
parent
bb13258861
commit
6c05607b14
7 changed files with 209 additions and 13 deletions
|
|
@ -112,9 +112,10 @@ class DocForgeService:
|
|||
context_provider: ContextProvider = compile_context,
|
||||
tool_surface: tuple[str, ...] | None = None,
|
||||
binding_metadata: Mapping[str, object] | None = None,
|
||||
no_ast: bool = False,
|
||||
) -> None:
|
||||
self.project = project
|
||||
self.index = ProjectIndex(self.project)
|
||||
self.index = ProjectIndex(self.project, allow_logic=not no_ast)
|
||||
self.changesets = ChangesetStore(self.project, proposal_writer)
|
||||
self.rendering = RenderService(self.project, self.changesets)
|
||||
self.application = CanonicalApplicationService(
|
||||
|
|
@ -125,11 +126,37 @@ class DocForgeService:
|
|||
self.visualization = ViewerManagerClient(self.index)
|
||||
self.context_provider = context_provider
|
||||
self.binding_metadata = dict(binding_metadata or {})
|
||||
self.no_ast = no_ast
|
||||
self.tool_surface = tool_surface or (
|
||||
*ALL_TOOLS,
|
||||
*(APPLICATION_TOOLS if self.application.enabled else ()),
|
||||
)
|
||||
|
||||
def adapter_policy(self) -> dict[str, object]:
|
||||
"""Return the immutable adapter-evolution policy for this MCP binding."""
|
||||
|
||||
if not self.no_ast:
|
||||
return {
|
||||
"mode": "standard",
|
||||
"ast_analysis": "allowed",
|
||||
"logic_projection": "allowed",
|
||||
"incremental_extraction": "allowed",
|
||||
"adapter_rewrite": "not_requested",
|
||||
}
|
||||
return {
|
||||
"mode": "preserve-no-ast",
|
||||
"ast_analysis": "forbidden",
|
||||
"logic_projection": "forbidden",
|
||||
"incremental_extraction": "allowed",
|
||||
"adapter_rewrite": "forbidden",
|
||||
"blocked_tools": ["docforge_get_logic"],
|
||||
"instruction": (
|
||||
"Preserve the existing adapter extraction strategy. Do not add Python AST, "
|
||||
"Tree-sitter, compiler-AST, or function-Logic extraction. Non-AST incremental "
|
||||
"fingerprinting and caching remain allowed."
|
||||
),
|
||||
}
|
||||
|
||||
def invoke(
|
||||
self,
|
||||
operation: Callable[[], dict[str, object]],
|
||||
|
|
@ -238,7 +265,25 @@ class DocForgeService:
|
|||
"index_path": str(snapshot.descriptor.index_path),
|
||||
"changeset_root": str(snapshot.descriptor.changeset_root),
|
||||
**self.binding_metadata,
|
||||
"adapter_policy": self.adapter_policy(),
|
||||
}
|
||||
recommended_workflow = [
|
||||
"docforge_get_context or targeted read tools",
|
||||
"make and verify one coherent implementation slice",
|
||||
"docforge_sync",
|
||||
"docforge_register_changes",
|
||||
"docforge_get_changeset_diff",
|
||||
"docforge_apply_changeset",
|
||||
"docforge_bootstrap",
|
||||
]
|
||||
if self.no_ast:
|
||||
recommended_workflow.insert(
|
||||
1,
|
||||
(
|
||||
"preserve the current adapter; do not add AST, Tree-sitter, "
|
||||
"compiler-AST, or function-Logic extraction"
|
||||
),
|
||||
)
|
||||
return {
|
||||
"status": "ok",
|
||||
"project_id": snapshot.descriptor.project_id,
|
||||
|
|
@ -249,18 +294,11 @@ class DocForgeService:
|
|||
"source_hash": snapshot.source_hash,
|
||||
"binding": binding,
|
||||
"canonical_paths": [str(path) for path in snapshot.descriptor.content_roots],
|
||||
"adapter_policy": self.adapter_policy(),
|
||||
"proposal_access": self.changesets.access(),
|
||||
"canonical_application_access": self.application.access(),
|
||||
"synchronization": synchronized["synchronization"],
|
||||
"recommended_workflow": [
|
||||
"docforge_get_context or targeted read tools",
|
||||
"make and verify one coherent implementation slice",
|
||||
"docforge_sync",
|
||||
"docforge_register_changes",
|
||||
"docforge_get_changeset_diff",
|
||||
"docforge_apply_changeset",
|
||||
"docforge_bootstrap",
|
||||
],
|
||||
"recommended_workflow": recommended_workflow,
|
||||
}
|
||||
|
||||
return self.invoke(operation, synchronize=False)
|
||||
|
|
@ -310,6 +348,7 @@ class DocForgeService:
|
|||
"authority_rule": (
|
||||
"Canonical project files own facts; DocForge results are derived."
|
||||
),
|
||||
"adapter_policy": self.adapter_policy(),
|
||||
"canonical_paths": [
|
||||
*(relative(path) for path in snapshot.descriptor.content_roots),
|
||||
*(relative(path) for path in snapshot.descriptor.authority_files),
|
||||
|
|
@ -349,6 +388,7 @@ class DocForgeService:
|
|||
else ()
|
||||
)
|
||||
+ (READ_ONLY_EXCLUDED_OPERATIONS if self.tool_surface == READ_TOOLS else ())
|
||||
+ (("adapter_ast_upgrade", "function_logic_extraction") if self.no_ast else ())
|
||||
),
|
||||
"proposal_access": self.changesets.access(),
|
||||
"canonical_application_access": self.application.access(),
|
||||
|
|
@ -359,6 +399,23 @@ class DocForgeService:
|
|||
|
||||
return self.invoke(operation)
|
||||
|
||||
def get_logic(self, owner_node_id: str) -> dict[str, object]:
|
||||
"""Return one Logic projection unless the binding preserves a no-AST adapter."""
|
||||
|
||||
if self.no_ast:
|
||||
|
||||
def forbidden() -> dict[str, object]:
|
||||
raise DocForgeError(
|
||||
"adapter_policy_forbids_logic",
|
||||
(
|
||||
"This MCP binding preserves a no-AST adapter and forbids function-Logic "
|
||||
"extraction"
|
||||
),
|
||||
)
|
||||
|
||||
return self.invoke(forbidden, synchronize=False)
|
||||
return self.invoke(lambda: self.index.get_logic(owner_node_id))
|
||||
|
||||
def validate_project(self) -> dict[str, object]:
|
||||
def operation() -> dict[str, object]:
|
||||
snapshot = self.project.load()
|
||||
|
|
@ -437,6 +494,13 @@ def _create_bound_server(service: DocForgeService, *, read_only: bool) -> FastMC
|
|||
"docforge_register_changes creates a complete proposal atomically. "
|
||||
"This server exposes no arbitrary renderer, shell, Git, deployment, publication, "
|
||||
"or project switching."
|
||||
+ (
|
||||
" This binding preserves the existing adapter and forbids AST, Tree-sitter, "
|
||||
"compiler-AST, and function-Logic extraction changes. Do not rewrite or upgrade "
|
||||
"the adapter to add those capabilities."
|
||||
if service.no_ast
|
||||
else ""
|
||||
)
|
||||
),
|
||||
json_response=True,
|
||||
)
|
||||
|
|
@ -475,7 +539,7 @@ def _create_bound_server(service: DocForgeService, *, read_only: bool) -> FastMC
|
|||
def get_logic(owner_node_id: str) -> dict[str, Any]:
|
||||
"""Return the lazy control-flow projection owned by one function or method."""
|
||||
|
||||
return service.invoke(lambda: service.index.get_logic(owner_node_id))
|
||||
return service.get_logic(owner_node_id)
|
||||
|
||||
@server.tool(name="docforge_search")
|
||||
def search(query: str, limit: int | None = None) -> dict[str, Any]:
|
||||
|
|
@ -822,6 +886,7 @@ def create_server(
|
|||
proposal_writer: str | None = None,
|
||||
*,
|
||||
canonical_applier_id: str | None = None,
|
||||
no_ast: bool = False,
|
||||
) -> FastMCP:
|
||||
project = Project.open(project_root)
|
||||
return create_project_server(
|
||||
|
|
@ -835,6 +900,7 @@ def create_server(
|
|||
"server_module": "docforge.mcp_server",
|
||||
"adapter_mode": "generic",
|
||||
},
|
||||
no_ast=no_ast,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -846,6 +912,7 @@ def create_project_server(
|
|||
canonical_applier: CanonicalApplier | None = None,
|
||||
context_provider: ContextProvider = compile_context,
|
||||
binding_metadata: Mapping[str, object] | None = None,
|
||||
no_ast: bool = False,
|
||||
) -> FastMCP:
|
||||
"""Create the full fixed MCP surface for one explicitly configured project service."""
|
||||
|
||||
|
|
@ -856,6 +923,7 @@ def create_project_server(
|
|||
canonical_applier=canonical_applier,
|
||||
context_provider=context_provider,
|
||||
binding_metadata=binding_metadata,
|
||||
no_ast=no_ast,
|
||||
)
|
||||
return _create_bound_server(service, read_only=False)
|
||||
|
||||
|
|
@ -865,6 +933,7 @@ def create_read_only_server(
|
|||
*,
|
||||
context_provider: ContextProvider = compile_context,
|
||||
binding_metadata: Mapping[str, object] | None = None,
|
||||
no_ast: bool = False,
|
||||
) -> FastMCP:
|
||||
"""Create an adapter-capable MCP server exposing only the fixed read tool surface."""
|
||||
|
||||
|
|
@ -873,6 +942,7 @@ def create_read_only_server(
|
|||
context_provider=context_provider,
|
||||
tool_surface=READ_TOOLS,
|
||||
binding_metadata=binding_metadata,
|
||||
no_ast=no_ast,
|
||||
)
|
||||
return _create_bound_server(service, read_only=True)
|
||||
|
||||
|
|
@ -882,11 +952,20 @@ def main() -> None:
|
|||
parser.add_argument("--project-root", type=Path, required=True)
|
||||
parser.add_argument("--proposal-writer")
|
||||
parser.add_argument("--canonical-applier")
|
||||
parser.add_argument(
|
||||
"--no-ast",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Preserve the existing adapter and forbid AST, Tree-sitter, compiler-AST, "
|
||||
"and function-Logic extraction changes"
|
||||
),
|
||||
)
|
||||
arguments = parser.parse_args()
|
||||
create_server(
|
||||
arguments.project_root,
|
||||
arguments.proposal_writer,
|
||||
canonical_applier_id=arguments.canonical_applier,
|
||||
no_ast=arguments.no_ast,
|
||||
).run(transport="stdio")
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue