katalyst-plugin-context 0.1.1a1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- katalyst_plugin_context-0.1.1a1/.gitignore +123 -0
- katalyst_plugin_context-0.1.1a1/PKG-INFO +13 -0
- katalyst_plugin_context-0.1.1a1/pyproject.toml +36 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/__init__.py +8 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/adapters/__init__.py +0 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/adapters/runtime_graph.py +126 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/cli.py +139 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/composition.py +49 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/domain/__init__.py +0 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/domain/context.py +762 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/domain/graph.py +97 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/domain/markdown.py +201 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/domain/paths.py +114 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/domain/policy.py +345 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/domain/settings.py +92 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/lint.py +149 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/mcp_tools.py +132 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/plugin.py +64 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/py.typed +0 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/rest.py +133 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/schema.py +200 -0
- katalyst_plugin_context-0.1.1a1/src/katalyst_plugin_context/service.py +319 -0
- katalyst_plugin_context-0.1.1a1/tests/conftest.py +20 -0
- katalyst_plugin_context-0.1.1a1/tests/context_support.py +263 -0
- katalyst_plugin_context-0.1.1a1/tests/test_context.py +354 -0
- katalyst_plugin_context-0.1.1a1/tests/test_lint.py +71 -0
- katalyst_plugin_context-0.1.1a1/tests/test_surfaces.py +229 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
# Not a Python build directory: Bard's SPA keeps shared helpers (utils.ts, download.ts) in
|
|
19
|
+
# src/lib, and ignoring it left a fresh clone unable to build or test the SPA.
|
|
20
|
+
!plugins/bard/web/src/lib/
|
|
21
|
+
lib64/
|
|
22
|
+
parts/
|
|
23
|
+
sdist/
|
|
24
|
+
var/
|
|
25
|
+
wheels/
|
|
26
|
+
*.egg-info/
|
|
27
|
+
.installed.cfg
|
|
28
|
+
*.egg
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
*.manifest
|
|
32
|
+
*.spec
|
|
33
|
+
|
|
34
|
+
# Installer logs
|
|
35
|
+
pip-log.txt
|
|
36
|
+
pip-delete-this-directory.txt
|
|
37
|
+
|
|
38
|
+
# Unit test / coverage reports
|
|
39
|
+
htmlcov/
|
|
40
|
+
.tox/
|
|
41
|
+
.nox/
|
|
42
|
+
.coverage
|
|
43
|
+
.coverage.*
|
|
44
|
+
.cache
|
|
45
|
+
nosetests.xml
|
|
46
|
+
coverage.xml
|
|
47
|
+
*.cover
|
|
48
|
+
*.py,cover
|
|
49
|
+
.hypothesis/
|
|
50
|
+
.pytest_cache/
|
|
51
|
+
|
|
52
|
+
# Translations
|
|
53
|
+
*.mo
|
|
54
|
+
*.pot
|
|
55
|
+
|
|
56
|
+
# Environments
|
|
57
|
+
.env
|
|
58
|
+
.venv
|
|
59
|
+
env/
|
|
60
|
+
venv/
|
|
61
|
+
ENV/
|
|
62
|
+
env.bak/
|
|
63
|
+
venv.bak/
|
|
64
|
+
|
|
65
|
+
# Spyder project settings
|
|
66
|
+
.spyderproject
|
|
67
|
+
.spyproject
|
|
68
|
+
|
|
69
|
+
# Rope project settings
|
|
70
|
+
.ropeproject
|
|
71
|
+
|
|
72
|
+
# mkdocs documentation
|
|
73
|
+
/site
|
|
74
|
+
|
|
75
|
+
# mypy / pyright
|
|
76
|
+
.mypy_cache/
|
|
77
|
+
.dmypy.json
|
|
78
|
+
dmypy.json
|
|
79
|
+
.pyright/
|
|
80
|
+
|
|
81
|
+
# Ruff
|
|
82
|
+
.ruff_cache/
|
|
83
|
+
|
|
84
|
+
# IDE
|
|
85
|
+
.idea/
|
|
86
|
+
.vscode/
|
|
87
|
+
*.swp
|
|
88
|
+
*.swo
|
|
89
|
+
*~
|
|
90
|
+
|
|
91
|
+
# OS
|
|
92
|
+
.DS_Store
|
|
93
|
+
Thumbs.db
|
|
94
|
+
|
|
95
|
+
# Local configuration
|
|
96
|
+
*.local
|
|
97
|
+
|
|
98
|
+
# Browser/Playwright state files
|
|
99
|
+
browser-state-*.json
|
|
100
|
+
|
|
101
|
+
# Agent harness scratch (run exhaust, never the design bundle)
|
|
102
|
+
.opencode/sessions/
|
|
103
|
+
.opencode/analysis/
|
|
104
|
+
.opencode/package-lock.json
|
|
105
|
+
|
|
106
|
+
# Build artifacts
|
|
107
|
+
site/
|
|
108
|
+
stack-tests/cucumber-report/
|
|
109
|
+
stack-tests/test-results/
|
|
110
|
+
|
|
111
|
+
# Generated artifacts (rebuild with `just generate-schemas`)
|
|
112
|
+
taxonomy/src/katalyst_taxonomy/engine/generated/
|
|
113
|
+
.worktrees/
|
|
114
|
+
.plan-74-75-76-77.md
|
|
115
|
+
.HANDOFF-scaffolding-wiring.md
|
|
116
|
+
.HANDOFF-2-cicd-migration.md
|
|
117
|
+
.HANDOFF-action-execution-fixes.md
|
|
118
|
+
.HANDOFF-bdd-coverage.md
|
|
119
|
+
.HANDOFF-*.md
|
|
120
|
+
docs/superpowers/
|
|
121
|
+
|
|
122
|
+
# Node dependencies — installed, never committed
|
|
123
|
+
node_modules/
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: katalyst-plugin-context
|
|
3
|
+
Version: 0.1.1a1
|
|
4
|
+
Summary: Katalyst context plugin — the right context from the taxonomy for an agent working on a node
|
|
5
|
+
Author: eSimplicity
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: click>=8.0
|
|
9
|
+
Requires-Dist: jsonschema>=4
|
|
10
|
+
Requires-Dist: katalyst-engine<6,>=5
|
|
11
|
+
Requires-Dist: katalyst-taxonomy-cli<3,>=2
|
|
12
|
+
Requires-Dist: katalyst-taxonomy-mcp<5,>=4
|
|
13
|
+
Requires-Dist: katalyst-taxonomy<6,>=5
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "katalyst-plugin-context"
|
|
3
|
+
version = "0.1.1a1"
|
|
4
|
+
description = "Katalyst context plugin — the right context from the taxonomy for an agent working on a node"
|
|
5
|
+
license = { text = "Proprietary" }
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "eSimplicity" }
|
|
9
|
+
]
|
|
10
|
+
dependencies = [
|
|
11
|
+
# Bounded to the config-rooted runtime this plugin is written against.
|
|
12
|
+
"katalyst-taxonomy>=5,<6",
|
|
13
|
+
"katalyst-engine>=5,<6",
|
|
14
|
+
# Its MCP tools and REST routes use the server's response helpers; its command, the CLI context.
|
|
15
|
+
"katalyst-taxonomy-mcp>=4,<5",
|
|
16
|
+
"katalyst-taxonomy-cli>=2,<3",
|
|
17
|
+
# Settings sections and the plugin's config are checked against its JSON Schemas.
|
|
18
|
+
"jsonschema>=4",
|
|
19
|
+
"click>=8.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.entry-points."katalyst_taxonomy.plugins"]
|
|
23
|
+
context = "katalyst_plugin_context:ContextPlugin"
|
|
24
|
+
|
|
25
|
+
[project.entry-points."katalyst_taxonomy.cli"]
|
|
26
|
+
context = "katalyst_plugin_context.cli:create_command"
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["hatchling"]
|
|
30
|
+
build-backend = "hatchling.build"
|
|
31
|
+
|
|
32
|
+
[tool.hatch.build.targets.wheel]
|
|
33
|
+
packages = ["src/katalyst_plugin_context"]
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.wheel.sources]
|
|
36
|
+
"src" = ""
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Katalyst context plugin: the right context from the taxonomy for an agent working on a
|
|
2
|
+
node, over MCP (``taxonomy_context_map``, ``taxonomy_context``), REST (``/ext/context``) and
|
|
3
|
+
the CLI (``kata tax context``).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from katalyst_plugin_context.plugin import ContextPlugin
|
|
7
|
+
|
|
8
|
+
__all__ = ["ContextPlugin"]
|
|
File without changes
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""The graph one call walks, read from the profile's backends through the port.
|
|
2
|
+
|
|
3
|
+
Each backend is read once (``load_documents`` and ``list_relationships``, the two calls every
|
|
4
|
+
backend type implements, through the profile's permission gate) and the corpus is narrowed by
|
|
5
|
+
the profile's filters (:func:`katalyst_taxonomy.services.filters.visible_nodes`). An edge is
|
|
6
|
+
kept only when both its ends are visible. A reference into another backend is stored as
|
|
7
|
+
``backend:fqtn`` and keyed that way, so an edge held by one backend can reach a node of
|
|
8
|
+
another.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from collections.abc import Iterable
|
|
14
|
+
from typing import Any, cast
|
|
15
|
+
|
|
16
|
+
from katalyst_engine.model.node import Node
|
|
17
|
+
from katalyst_taxonomy.engine.accessors import (
|
|
18
|
+
get_extensions_map,
|
|
19
|
+
get_markdown_body,
|
|
20
|
+
get_node_backend,
|
|
21
|
+
)
|
|
22
|
+
from katalyst_taxonomy.engine.identity import split_backend_prefix
|
|
23
|
+
from katalyst_taxonomy.ports.backend import TaxonomyBackend
|
|
24
|
+
|
|
25
|
+
from katalyst_plugin_context.domain.graph import Edge, Graph, GraphNode
|
|
26
|
+
|
|
27
|
+
__all__ = ["build_graph", "graph_from"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _graph_node(doc: Node, backend: str, key: str) -> GraphNode:
|
|
31
|
+
spec = {k: v for k, v in doc.spec.items() if not str(k).startswith("_")}
|
|
32
|
+
return GraphNode(
|
|
33
|
+
key=key,
|
|
34
|
+
kind=doc.kind,
|
|
35
|
+
name=doc.name,
|
|
36
|
+
fqn=doc.fqn or doc.name,
|
|
37
|
+
backend=backend,
|
|
38
|
+
spec=spec,
|
|
39
|
+
description=doc.description or str(spec.get("description") or ""),
|
|
40
|
+
body=get_markdown_body(doc),
|
|
41
|
+
extensions=cast("dict[str, Any]", get_extensions_map(doc)),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def graph_from(
|
|
46
|
+
documents: Iterable[tuple[str, Node]],
|
|
47
|
+
relationships: Iterable[tuple[str, str, str, str]],
|
|
48
|
+
*,
|
|
49
|
+
hidden: Iterable[tuple[str, str]] = (),
|
|
50
|
+
) -> Graph:
|
|
51
|
+
"""A graph over *documents* (``(holder backend, node)``) and *relationships*
|
|
52
|
+
(``(holder backend, source fqtn, rel, target fqtn)``).
|
|
53
|
+
|
|
54
|
+
An edge to an address in *hidden* (``(backend, fqtn)`` of nodes the profile's filters
|
|
55
|
+
hide) is left out silently. Any other edge that reaches no node is counted as dropped.
|
|
56
|
+
Nothing about a hidden node is counted.
|
|
57
|
+
"""
|
|
58
|
+
hidden_addresses = set(hidden)
|
|
59
|
+
by_address: dict[tuple[str, str], list[str]] = {}
|
|
60
|
+
nodes: list[GraphNode] = []
|
|
61
|
+
pending: list[tuple[str, str, Node]] = []
|
|
62
|
+
for holder, doc in documents:
|
|
63
|
+
backend = get_node_backend(doc) or holder
|
|
64
|
+
fqn = doc.fqn or doc.name
|
|
65
|
+
pending.append((backend, fqn, doc))
|
|
66
|
+
by_address.setdefault((backend, fqn), []).append(doc.kind)
|
|
67
|
+
for backend, fqn, doc in pending:
|
|
68
|
+
shared = len(by_address[(backend, fqn)]) > 1
|
|
69
|
+
key = f"{backend}:{fqn}#{doc.kind}" if shared else f"{backend}:{fqn}"
|
|
70
|
+
nodes.append(_graph_node(doc, backend, key))
|
|
71
|
+
|
|
72
|
+
def key_of(holder: str, address: str) -> str | None:
|
|
73
|
+
prefix, rest = split_backend_prefix(address)
|
|
74
|
+
backend = prefix or holder
|
|
75
|
+
kinds = by_address.get((backend, rest))
|
|
76
|
+
if kinds is None or len(kinds) != 1:
|
|
77
|
+
return None
|
|
78
|
+
return f"{backend}:{rest}"
|
|
79
|
+
|
|
80
|
+
edges: list[Edge] = []
|
|
81
|
+
dropped = 0
|
|
82
|
+
for holder, source, rel, target in relationships:
|
|
83
|
+
source_key, target_key = key_of(holder, source), key_of(holder, target)
|
|
84
|
+
if source_key is None or target_key is None:
|
|
85
|
+
ends = (_address(holder, source), _address(holder, target))
|
|
86
|
+
if not any(end in hidden_addresses for end in ends):
|
|
87
|
+
dropped += 1
|
|
88
|
+
continue
|
|
89
|
+
edges.append(Edge(rel=rel, source=source_key, target=target_key))
|
|
90
|
+
return Graph(nodes, edges, dropped=dropped)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _address(holder: str, value: str) -> tuple[str, str]:
|
|
94
|
+
prefix, rest = split_backend_prefix(value)
|
|
95
|
+
return (prefix or holder, rest)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def build_graph(
|
|
99
|
+
sources: Iterable[tuple[str, TaxonomyBackend]], *, profile: str | None = None
|
|
100
|
+
) -> Graph:
|
|
101
|
+
"""Read every backend in *sources* once and build the graph the profile may see.
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
PermissionDeniedError: If the profile may not read a backend (as every read does).
|
|
105
|
+
"""
|
|
106
|
+
from katalyst_taxonomy.services.filters import visible_nodes
|
|
107
|
+
|
|
108
|
+
loaded: list[tuple[str, Node]] = []
|
|
109
|
+
relationships: list[tuple[str, str, str, str]] = []
|
|
110
|
+
for name, backend in sources:
|
|
111
|
+
loaded.extend((name, doc) for doc in backend.load_documents())
|
|
112
|
+
relationships.extend(
|
|
113
|
+
(name, rel.source_fqtn, rel.rel_type, rel.target_fqtn)
|
|
114
|
+
for rel in backend.list_relationships()
|
|
115
|
+
)
|
|
116
|
+
visible = {id(doc) for doc in visible_nodes(None, [doc for _, doc in loaded], profile=profile)}
|
|
117
|
+
hidden = (
|
|
118
|
+
(get_node_backend(doc) or holder, doc.fqn or doc.name)
|
|
119
|
+
for holder, doc in loaded
|
|
120
|
+
if id(doc) not in visible
|
|
121
|
+
)
|
|
122
|
+
return graph_from(
|
|
123
|
+
((holder, doc) for holder, doc in loaded if id(doc) in visible),
|
|
124
|
+
relationships,
|
|
125
|
+
hidden=list(hidden),
|
|
126
|
+
)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# pyright: reportUnusedFunction=false
|
|
2
|
+
"""``kata tax context NODE`` — what an agent working on a node is given, from the shell."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from katalyst_taxonomy_cli.context import CliContext
|
|
13
|
+
|
|
14
|
+
from katalyst_plugin_context.service import ContextService
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _service(ctx: CliContext, backend: str | None) -> ContextService:
|
|
18
|
+
from katalyst_taxonomy.config.backends import load_backends
|
|
19
|
+
from katalyst_taxonomy.config.routing import BackendAddressError
|
|
20
|
+
from katalyst_taxonomy.engine.bridge import get_default_registry
|
|
21
|
+
|
|
22
|
+
from katalyst_plugin_context.composition import service_for
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
backends = load_backends(None, profile=ctx.profile)
|
|
26
|
+
sources = backends.read_set(backend)
|
|
27
|
+
except (BackendAddressError, ValueError) as exc:
|
|
28
|
+
raise click.ClickException(str(exc)) from exc
|
|
29
|
+
return service_for(get_default_registry(), sources, profile=backends.profile)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _emit(payload: Any, fmt: str, markdown: str | None = None) -> None:
|
|
33
|
+
if fmt == "json":
|
|
34
|
+
click.echo(json.dumps(payload, indent=2, default=str))
|
|
35
|
+
else:
|
|
36
|
+
click.echo(markdown if markdown is not None else json.dumps(payload, indent=2), nl=False)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def create_command() -> click.Command:
|
|
40
|
+
"""Entry point for ``katalyst_taxonomy.cli``: contributes ``kata tax context``."""
|
|
41
|
+
|
|
42
|
+
@click.command("context")
|
|
43
|
+
@click.argument("node")
|
|
44
|
+
@click.option(
|
|
45
|
+
"--map", "show_map", is_flag=True, help="Show the map: relationships, views, options."
|
|
46
|
+
)
|
|
47
|
+
@click.option(
|
|
48
|
+
"--policy",
|
|
49
|
+
"show_policy",
|
|
50
|
+
is_flag=True,
|
|
51
|
+
help="Show the effective policy for NODE: layers, limits and notes.",
|
|
52
|
+
)
|
|
53
|
+
@click.option(
|
|
54
|
+
"--path",
|
|
55
|
+
"paths",
|
|
56
|
+
multiple=True,
|
|
57
|
+
metavar="REL[:in|out[:KIND,...]][/...]",
|
|
58
|
+
help="A path to walk; repeatable.",
|
|
59
|
+
)
|
|
60
|
+
@click.option("--view", "views", multiple=True, metavar="NAME", help="A view; repeatable.")
|
|
61
|
+
@click.option("--depth", type=int, default=None, help="Repetitions of a single-step path.")
|
|
62
|
+
@click.option(
|
|
63
|
+
"--detail",
|
|
64
|
+
type=click.Choice(["refs", "summary", "full"]),
|
|
65
|
+
default=None,
|
|
66
|
+
help="Item detail.",
|
|
67
|
+
)
|
|
68
|
+
@click.option("--budget", type=int, default=None, metavar="TOKENS", help="Approximate tokens.")
|
|
69
|
+
@click.option("--inherit/--no-inherit", default=None, help="Also walk from NODE's ancestors.")
|
|
70
|
+
@click.option("--backend", default=None, help="Read only this backend.")
|
|
71
|
+
@click.option(
|
|
72
|
+
"--format",
|
|
73
|
+
"fmt",
|
|
74
|
+
type=click.Choice(["markdown", "json"]),
|
|
75
|
+
default="markdown",
|
|
76
|
+
show_default=True,
|
|
77
|
+
)
|
|
78
|
+
@click.pass_obj
|
|
79
|
+
def context(
|
|
80
|
+
ctx: CliContext,
|
|
81
|
+
node: str,
|
|
82
|
+
show_map: bool,
|
|
83
|
+
show_policy: bool,
|
|
84
|
+
paths: tuple[str, ...],
|
|
85
|
+
views: tuple[str, ...],
|
|
86
|
+
depth: int | None,
|
|
87
|
+
detail: str | None,
|
|
88
|
+
budget: int | None,
|
|
89
|
+
inherit: bool | None,
|
|
90
|
+
backend: str | None,
|
|
91
|
+
fmt: str,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""What the taxonomy holds around NODE, and why each item is relevant.
|
|
94
|
+
|
|
95
|
+
NODE is a qualified name, 'backend:fqtn' (or the FQTN when one backend holds it).
|
|
96
|
+
Without --map or --policy, prints the views NODE's kind always includes, or the
|
|
97
|
+
whole neighbourhood at depth 1 if it declares none; with --path or --view, exactly
|
|
98
|
+
those. Policy from the schema, the node and the profile can only narrow a request.
|
|
99
|
+
|
|
100
|
+
Examples:
|
|
101
|
+
|
|
102
|
+
kata tax context repo:shop.api.web --map
|
|
103
|
+
|
|
104
|
+
kata tax context repo:shop.api.web --view some-view --budget 8000
|
|
105
|
+
|
|
106
|
+
kata tax context repo:shop.api.web --path 'SOME_REL:in' --detail full --format json
|
|
107
|
+
"""
|
|
108
|
+
from katalyst_plugin_context.domain.context import ContextError, ContextRequest
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
service = _service(ctx, backend)
|
|
112
|
+
if show_policy:
|
|
113
|
+
_emit(service.explain(node), "json")
|
|
114
|
+
return
|
|
115
|
+
if show_map:
|
|
116
|
+
payload = service.map(node)
|
|
117
|
+
_emit(
|
|
118
|
+
payload, fmt, service.markdown_for_map(payload) if fmt == "markdown" else None
|
|
119
|
+
)
|
|
120
|
+
return
|
|
121
|
+
request = ContextRequest(
|
|
122
|
+
paths=paths or None,
|
|
123
|
+
views=views or None,
|
|
124
|
+
depth=depth,
|
|
125
|
+
detail=detail,
|
|
126
|
+
budget=budget,
|
|
127
|
+
inherit=inherit,
|
|
128
|
+
format=fmt,
|
|
129
|
+
)
|
|
130
|
+
payload = service.query(node, request)
|
|
131
|
+
_emit(
|
|
132
|
+
payload,
|
|
133
|
+
fmt,
|
|
134
|
+
service.markdown_for_result(node, payload) if fmt == "markdown" else None,
|
|
135
|
+
)
|
|
136
|
+
except ContextError as exc:
|
|
137
|
+
raise click.ClickException(exc.message) from exc
|
|
138
|
+
|
|
139
|
+
return context
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Composition: a :class:`ContextService` for one call, from each host's own objects.
|
|
2
|
+
|
|
3
|
+
Every host reads the same way: the profile's backends (through its permission gate), its
|
|
4
|
+
filters, the schema registry, and the plugin's config for the active profile, read **per
|
|
5
|
+
call** from the current configuration, so a Settings save or a profile switch takes effect
|
|
6
|
+
on the next call.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Iterable, Mapping
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from katalyst_engine.schema.registry import SchemaRegistry
|
|
15
|
+
from katalyst_taxonomy.ports.backend import TaxonomyBackend
|
|
16
|
+
|
|
17
|
+
from katalyst_plugin_context.adapters.runtime_graph import build_graph
|
|
18
|
+
from katalyst_plugin_context.service import NAMESPACE, ContextService
|
|
19
|
+
|
|
20
|
+
__all__ = ["plugin_config", "service_for"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def plugin_config(profile: str | None) -> tuple[Mapping[str, Any], str | None]:
|
|
24
|
+
"""This plugin's ``spec.plugins[].config`` and the active profile's name.
|
|
25
|
+
|
|
26
|
+
``({}, profile)`` when no configuration resolves (nothing configured yet): with nothing
|
|
27
|
+
declared, nothing narrows.
|
|
28
|
+
"""
|
|
29
|
+
from katalyst_taxonomy.config.backends import resolve_effective_config
|
|
30
|
+
from katalyst_taxonomy.config.plugins import selection_from_config
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
effective = resolve_effective_config(None, profile=profile)
|
|
34
|
+
except Exception: # noqa: BLE001 - no config is a normal first-run state
|
|
35
|
+
return {}, profile
|
|
36
|
+
selection = selection_from_config(effective.config, effective.selection.profile)
|
|
37
|
+
return selection.config_for(NAMESPACE), effective.selection.name or profile
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def service_for(
|
|
41
|
+
registry: SchemaRegistry,
|
|
42
|
+
sources: Iterable[tuple[str, TaxonomyBackend]],
|
|
43
|
+
*,
|
|
44
|
+
profile: str | None,
|
|
45
|
+
) -> ContextService:
|
|
46
|
+
"""The service over what *sources* hold, under the profile named *profile*."""
|
|
47
|
+
config, active = plugin_config(profile)
|
|
48
|
+
graph = build_graph(sources, profile=active)
|
|
49
|
+
return ContextService(registry, graph, config=config, profile=active)
|
|
File without changes
|