feat: establish read-only DocForge MCP foundation
This commit is contained in:
commit
9702ed1265
32 changed files with 3323 additions and 0 deletions
120
src/docforge/cli.py
Normal file
120
src/docforge/cli.py
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
"""Deterministic JSON command-line interface for the read-only DocForge core."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from .context import compile_context
|
||||
from .errors import DocForgeError
|
||||
from .index import ProjectIndex
|
||||
from .project import Project, project_root_fingerprint
|
||||
|
||||
|
||||
def _parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(prog="docforge")
|
||||
parser.add_argument("--project-root", type=Path, required=True)
|
||||
commands = parser.add_subparsers(dest="command", required=True)
|
||||
commands.add_parser("info")
|
||||
commands.add_parser("validate")
|
||||
commands.add_parser("build")
|
||||
commands.add_parser("check")
|
||||
commands.add_parser("validate-index")
|
||||
show = commands.add_parser("show")
|
||||
show.add_argument("node_id")
|
||||
search = commands.add_parser("search")
|
||||
search.add_argument("query")
|
||||
search.add_argument("--limit", type=int)
|
||||
filter_command = commands.add_parser("filter")
|
||||
filter_command.add_argument("--family")
|
||||
filter_command.add_argument("--authority")
|
||||
filter_command.add_argument("--status")
|
||||
filter_command.add_argument("--tag")
|
||||
filter_command.add_argument("--limit", type=int)
|
||||
for name in ("backlinks", "dependencies", "impact"):
|
||||
command = commands.add_parser(name)
|
||||
command.add_argument("node_id")
|
||||
if name == "backlinks":
|
||||
command.add_argument("--relation")
|
||||
else:
|
||||
command.add_argument("--depth", type=int, default=2)
|
||||
context = commands.add_parser("context")
|
||||
context.add_argument("profile")
|
||||
context.add_argument("--budget", type=int)
|
||||
return parser
|
||||
|
||||
|
||||
def _run(arguments: argparse.Namespace) -> dict[str, object]:
|
||||
project = Project.open(arguments.project_root)
|
||||
index = ProjectIndex(project)
|
||||
if arguments.command == "info":
|
||||
snapshot = project.load()
|
||||
return {
|
||||
"status": "ok",
|
||||
"project_id": snapshot.descriptor.project_id,
|
||||
"project_root_fingerprint": project_root_fingerprint(snapshot.descriptor.root),
|
||||
"title": snapshot.descriptor.title,
|
||||
"adapter": snapshot.descriptor.adapter,
|
||||
"revision": snapshot.revision,
|
||||
"source_hash": snapshot.source_hash,
|
||||
"node_count": len(snapshot.nodes),
|
||||
"edge_count": len(snapshot.edges),
|
||||
"index": str(snapshot.descriptor.index_path),
|
||||
}
|
||||
if arguments.command == "validate":
|
||||
snapshot = project.load()
|
||||
return {
|
||||
"status": "ok",
|
||||
"project_id": snapshot.descriptor.project_id,
|
||||
"project_root_fingerprint": project_root_fingerprint(snapshot.descriptor.root),
|
||||
"revision": snapshot.revision,
|
||||
"source_hash": snapshot.source_hash,
|
||||
"node_count": len(snapshot.nodes),
|
||||
"edge_count": len(snapshot.edges),
|
||||
}
|
||||
if arguments.command == "build":
|
||||
return index.build()
|
||||
if arguments.command == "check":
|
||||
return index.check()
|
||||
if arguments.command == "validate-index":
|
||||
return index.check()
|
||||
if arguments.command == "show":
|
||||
return index.get_node(arguments.node_id)
|
||||
if arguments.command == "search":
|
||||
return index.search(arguments.query, limit=arguments.limit)
|
||||
if arguments.command == "filter":
|
||||
return index.filter_nodes(
|
||||
family=arguments.family,
|
||||
authority=arguments.authority,
|
||||
status=arguments.status,
|
||||
tag=arguments.tag,
|
||||
limit=arguments.limit,
|
||||
)
|
||||
if arguments.command == "backlinks":
|
||||
return index.backlinks(arguments.node_id, relation=arguments.relation)
|
||||
if arguments.command == "dependencies":
|
||||
return index.dependencies(arguments.node_id, depth=arguments.depth)
|
||||
if arguments.command == "impact":
|
||||
return index.impact(arguments.node_id, depth=arguments.depth)
|
||||
if arguments.command == "context":
|
||||
return compile_context(index, arguments.profile, arguments.budget)
|
||||
raise DocForgeError("invalid_command", "Unknown command")
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = _parser()
|
||||
arguments = parser.parse_args(argv)
|
||||
try:
|
||||
result = _run(arguments)
|
||||
code = 0
|
||||
except DocForgeError as error:
|
||||
result = {"status": "error", "error": error.as_dict()}
|
||||
code = 2
|
||||
print(json.dumps(result, sort_keys=True, indent=2))
|
||||
return code
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue