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

Preserve no-AST adapter bindings

This commit is contained in:
Andraxion 2026-07-29 02:59:15 -04:00
parent bb13258861
commit 6c05607b14
7 changed files with 209 additions and 13 deletions

View file

@ -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")