katalyst-plugin-context 0.2.1a5__tar.gz → 0.2.1a7__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.2.1a5 → katalyst_plugin_context-0.2.1a7}/PKG-INFO +1 -1
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/pyproject.toml +1 -1
- katalyst_plugin_context-0.2.1a7/xspec/.gitignore +8 -0
- katalyst_plugin_context-0.2.1a7/xspec/features/cli/52_kata_context.feature +274 -0
- katalyst_plugin_context-0.2.1a7/xspec/features/cli/53_kata_context_budget_policy.feature +265 -0
- katalyst_plugin_context-0.2.1a7/xspec/features/mcp/09_mcp_context.feature +190 -0
- katalyst_plugin_context-0.2.1a7/xspec/fixtures/context-catalogue/catalogue/areas/payments.yaml +13 -0
- katalyst_plugin_context-0.2.1a7/xspec/fixtures/context-catalogue/catalogue/checks/encrypt-card-data.scan-for-card-numbers.yaml +12 -0
- katalyst_plugin_context-0.2.1a7/xspec/fixtures/context-catalogue/catalogue/components/payments.checkout.web-api.yaml +15 -0
- katalyst_plugin_context-0.2.1a7/xspec/fixtures/context-catalogue/catalogue/decisions/async-events.yaml +10 -0
- katalyst_plugin_context-0.2.1a7/xspec/fixtures/context-catalogue/catalogue/decisions/pin-httpx.yaml +10 -0
- katalyst_plugin_context-0.2.1a7/xspec/fixtures/context-catalogue/catalogue/decisions/use-postgres.yaml +10 -0
- katalyst_plugin_context-0.2.1a7/xspec/fixtures/context-catalogue/catalogue/packages/httpx.yaml +11 -0
- katalyst_plugin_context-0.2.1a7/xspec/fixtures/context-catalogue/catalogue/rules/encrypt-card-data.yaml +10 -0
- katalyst_plugin_context-0.2.1a7/xspec/fixtures/context-catalogue/catalogue/rules/log-correlation-id.yaml +10 -0
- katalyst_plugin_context-0.2.1a7/xspec/fixtures/context-catalogue/catalogue/services/payments.checkout.yaml +13 -0
- katalyst_plugin_context-0.2.1a7/xspec/package.json +15 -0
- katalyst_plugin_context-0.2.1a7/xspec/playwright.config.ts +5 -0
- katalyst_plugin_context-0.2.1a7/xspec/steps/context.ts +216 -0
- katalyst_plugin_context-0.2.1a7/xspec/steps/steps.ts +5 -0
- katalyst_plugin_context-0.2.1a7/xspec/tsconfig.json +4 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/.gitignore +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/__init__.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/adapters/__init__.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/adapters/runtime_graph.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/cli.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/composition.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/domain/__init__.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/domain/context.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/domain/graph.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/domain/markdown.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/domain/paths.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/domain/policy.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/domain/settings.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/lint.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/mcp_tools.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/plugin.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/py.typed +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/rest.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/schema.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/src/katalyst_plugin_context/service.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/tests/conftest.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/tests/context_support.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/tests/test_context.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/tests/test_lint.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/tests/test_shared_names.py +0 -0
- {katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/tests/test_surfaces.py +0 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
@cli @area-context
|
|
2
|
+
Feature: Kata CLI - Context: what an agent working on a node gets
|
|
3
|
+
As an agent about to change a node
|
|
4
|
+
I want one call that returns the decisions and rules around it
|
|
5
|
+
So that I meet a rule the node inherits from a parent, not only the ones in its own file
|
|
6
|
+
|
|
7
|
+
# docs/plugins/context/index.md, against the neutral service catalogue of examples/catalogue
|
|
8
|
+
# (installed as docs/user-guide/getting-started.md installs it) with a small payments model:
|
|
9
|
+
#
|
|
10
|
+
# area payments governed by use-postgres, follows the mandatory rule encrypt-card-data
|
|
11
|
+
# service payments.checkout governed by async-events
|
|
12
|
+
# component payments.checkout.web-api follows the recommended rule log-correlation-id,
|
|
13
|
+
# uses the package httpx (governed by pin-httpx)
|
|
14
|
+
# check scan-for-card-numbers, under encrypt-card-data
|
|
15
|
+
#
|
|
16
|
+
# The catalogue declares one view, `governance`, that every `component` always includes, and
|
|
17
|
+
# a second, `package-decisions`, that the caller must ask for. `severity: mandatory` blocks.
|
|
18
|
+
|
|
19
|
+
Background:
|
|
20
|
+
Given the payments catalogue is installed
|
|
21
|
+
|
|
22
|
+
# ─────────────────────────────────────────────────────────────
|
|
23
|
+
# The map
|
|
24
|
+
# ─────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
@smoke
|
|
27
|
+
Scenario: The map shows the ancestors, the relationships, the views and what can be asked next
|
|
28
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --map"
|
|
29
|
+
Then the command should succeed
|
|
30
|
+
And the output should contain "# Context: component `filesystem:payments.checkout.web-api`"
|
|
31
|
+
And the output should contain "Ancestors: service `filesystem:payments.checkout` ← area `filesystem:payments`"
|
|
32
|
+
And the output should contain "- `USES_PACKAGE:out` → package ×1"
|
|
33
|
+
And the output should contain "- `GOVERNED_BY:out` → decision ×1 — inherited from `filesystem:payments.checkout`"
|
|
34
|
+
And the output should contain "- `FOLLOWS_RULE:out` → rule ×1 — inherited from `filesystem:payments`"
|
|
35
|
+
And the output should contain "- **governance** (always included): Governing decisions and rules — 5 item(s)"
|
|
36
|
+
And the output should contain "- **package-decisions**: Decisions on the packages it uses — 1 item(s)"
|
|
37
|
+
And the output should contain "## Always included"
|
|
38
|
+
|
|
39
|
+
Scenario: The map in JSON marks the always-included view and says what policy allows
|
|
40
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --map --format json"
|
|
41
|
+
Then the command should succeed
|
|
42
|
+
And the JSON output at "$.node.qualifiedName" should equal "filesystem:payments.checkout.web-api"
|
|
43
|
+
And the JSON output at "$.ancestors[*].qualifiedName" should contain "filesystem:payments.checkout"
|
|
44
|
+
And the JSON output at "$.ancestors[*].qualifiedName" should contain "filesystem:payments"
|
|
45
|
+
And the JSON output at "$.views[?(@.name=='governance')].always" should equal "true"
|
|
46
|
+
And the JSON output at "$.views[?(@.name=='package-decisions')].always" should equal "false"
|
|
47
|
+
And the JSON output at "$.always.items[*]" should have 5 items
|
|
48
|
+
And the JSON output at "$.options.steps" should contain "USES_PACKAGE:out"
|
|
49
|
+
And the JSON output at "$.options.detail" should contain "full"
|
|
50
|
+
And the JSON output at "$.options.maxDepth" should equal "4"
|
|
51
|
+
|
|
52
|
+
# ─────────────────────────────────────────────────────────────
|
|
53
|
+
# A default call: the always-included view, blocking items first
|
|
54
|
+
# ─────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
@smoke
|
|
57
|
+
Scenario: A call with no path and no view returns the always-included view, the mandatory rule first
|
|
58
|
+
When I run "kata tax context filesystem:payments.checkout.web-api"
|
|
59
|
+
Then the command should succeed
|
|
60
|
+
And the output should contain "## Governing decisions and rules"
|
|
61
|
+
And the output should contain "⛔ blocking · **Encrypt card data** `filesystem:encrypt-card-data` · severity: mandatory"
|
|
62
|
+
And the output should contain "_advisory_ · **Log a correlation id** `filesystem:log-correlation-id` · severity: recommended"
|
|
63
|
+
And the output should contain "why: area filesystem:payments ─FOLLOWS_RULE→ (inherited)"
|
|
64
|
+
And the output should list "Encrypt card data" before "Log a correlation id"
|
|
65
|
+
And the output should list "Encrypt card data" before "Publish events, do not call"
|
|
66
|
+
And the output should not contain "Pin httpx"
|
|
67
|
+
And the output should contain "nothing narrowed"
|
|
68
|
+
|
|
69
|
+
Scenario: The same call in JSON marks the blocking item and where it was inherited from
|
|
70
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --format json"
|
|
71
|
+
Then the command should succeed
|
|
72
|
+
And the JSON output at "$.items[*]" should have 5 items
|
|
73
|
+
And the JSON output at "$.items[0].qualifiedName" should equal "filesystem:encrypt-card-data"
|
|
74
|
+
And the JSON output at "$.items[0].blocking.blocking" should equal "true"
|
|
75
|
+
And the JSON output at "$.items[0].inheritedFrom" should equal "filesystem:payments"
|
|
76
|
+
And the JSON output at "$.items[?(@.qualifiedName=='filesystem:log-correlation-id')].blocking.blocking" should equal "false"
|
|
77
|
+
And the JSON output at "$.items[?(@.qualifiedName=='filesystem:log-correlation-id')].inheritedFrom" should be null
|
|
78
|
+
And the JSON output at "$.items[*].view" should not contain "package-decisions"
|
|
79
|
+
And the JSON output at "$.effective.always" should contain "governance"
|
|
80
|
+
And the JSON output at "$.truncated" should be null
|
|
81
|
+
|
|
82
|
+
# ─────────────────────────────────────────────────────────────
|
|
83
|
+
# #290: naming a view or a path still holds the always-included views
|
|
84
|
+
# ─────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
Scenario: Asking for one view still returns the always-included view first
|
|
87
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --view package-decisions"
|
|
88
|
+
Then the command should succeed
|
|
89
|
+
And the output should contain "## Governing decisions and rules _(view: governance)_"
|
|
90
|
+
And the output should contain "## Decisions on the packages it uses _(view: package-decisions)_"
|
|
91
|
+
And the output should contain "⛔ blocking · **Encrypt card data**"
|
|
92
|
+
And the output should contain "`filesystem:pin-httpx`"
|
|
93
|
+
And the output should list "view: governance" before "view: package-decisions"
|
|
94
|
+
|
|
95
|
+
Scenario: Asking for one view in JSON returns the mandatory rule and the view's own item
|
|
96
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --view package-decisions --format json"
|
|
97
|
+
Then the command should succeed
|
|
98
|
+
And the JSON output at "$.effective.views" should contain "governance"
|
|
99
|
+
And the JSON output at "$.effective.views" should contain "package-decisions"
|
|
100
|
+
And the JSON output at "$.effective.always" should contain "governance"
|
|
101
|
+
And the JSON output at "$.items[*].qualifiedName" should contain "filesystem:encrypt-card-data"
|
|
102
|
+
And the JSON output at "$.items[?(@.view=='package-decisions')]" should have 1 item
|
|
103
|
+
And the JSON output at "$.items[?(@.view=='package-decisions')].qualifiedName" should contain "filesystem:pin-httpx"
|
|
104
|
+
|
|
105
|
+
Scenario: Asking for a path still returns the always-included view
|
|
106
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --path USES_PACKAGE:out --format json"
|
|
107
|
+
Then the command should succeed
|
|
108
|
+
And the JSON output at "$.items[*].qualifiedName" should contain "filesystem:encrypt-card-data"
|
|
109
|
+
And the JSON output at "$.items[*].qualifiedName" should contain "filesystem:httpx"
|
|
110
|
+
And the JSON output at "$.items[?(@.qualifiedName=='filesystem:httpx')].path" should equal "USES_PACKAGE:out"
|
|
111
|
+
|
|
112
|
+
# ─────────────────────────────────────────────────────────────
|
|
113
|
+
# Detail and depth
|
|
114
|
+
# ─────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
Scenario: The detail level decides how much of an item is shown
|
|
117
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --path USES_PACKAGE:out --detail refs --format json"
|
|
118
|
+
Then the command should succeed
|
|
119
|
+
And the JSON output at "$.items[?(@.qualifiedName=='filesystem:httpx')].detail" should equal "refs"
|
|
120
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --path USES_PACKAGE:out --detail summary"
|
|
121
|
+
Then the command should succeed
|
|
122
|
+
And the output should contain "- **httpx** `filesystem:httpx` · version: 0.27"
|
|
123
|
+
And the output should contain "HTTP client"
|
|
124
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --path USES_PACKAGE:out --detail full --format json"
|
|
125
|
+
Then the command should succeed
|
|
126
|
+
And the JSON output at "$.items[?(@.qualifiedName=='filesystem:httpx')].detail" should equal "full"
|
|
127
|
+
And the JSON output at "$.items[?(@.qualifiedName=='filesystem:httpx')].spec.version" should equal "0.27"
|
|
128
|
+
|
|
129
|
+
Scenario: The always-included view keeps its own detail when the request asks for more
|
|
130
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --path USES_PACKAGE:out --detail full --format json"
|
|
131
|
+
Then the command should succeed
|
|
132
|
+
And the JSON output at "$.items[?(@.qualifiedName=='filesystem:encrypt-card-data')].detail" should equal "summary"
|
|
133
|
+
|
|
134
|
+
Scenario: The depth is how many times a single-step path is followed
|
|
135
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --path CHILD_OF:out --no-inherit --depth 1 --detail refs --format json"
|
|
136
|
+
Then the command should succeed
|
|
137
|
+
And the JSON output at "$.items[?(@.path=='CHILD_OF:out')].qualifiedName" should contain "filesystem:payments.checkout"
|
|
138
|
+
And the JSON output at "$.items[?(@.path=='CHILD_OF:out')].qualifiedName" should not contain "filesystem:payments"
|
|
139
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --path CHILD_OF:out --no-inherit --depth 2 --detail refs --format json"
|
|
140
|
+
Then the command should succeed
|
|
141
|
+
And the JSON output at "$.items[?(@.path=='CHILD_OF:out')].qualifiedName" should contain "filesystem:payments.checkout"
|
|
142
|
+
And the JSON output at "$.items[?(@.path=='CHILD_OF:out')].qualifiedName" should contain "filesystem:payments"
|
|
143
|
+
And the JSON output at "$.items[?(@.qualifiedName=='filesystem:payments' && @.path=='CHILD_OF:out')].depth" should equal "2"
|
|
144
|
+
|
|
145
|
+
# katalyst-taxonomy#303: a path that names CHILD_OF returns the ancestors with the default
|
|
146
|
+
# inheritance too; the map's `CHILD_OF:out → service ×1` row is no dead path.
|
|
147
|
+
Scenario: A path that names the parent relationship returns the ancestors with inheritance on
|
|
148
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --path CHILD_OF:out --depth 2 --detail refs --format json"
|
|
149
|
+
Then the command should succeed
|
|
150
|
+
And the JSON output at "$.effective.inherit" should equal "true"
|
|
151
|
+
And the JSON output at "$.items[?(@.path=='CHILD_OF:out')].qualifiedName" should contain "filesystem:payments.checkout"
|
|
152
|
+
And the JSON output at "$.items[?(@.path=='CHILD_OF:out')].qualifiedName" should contain "filesystem:payments"
|
|
153
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --path '*:out' --detail refs --format json"
|
|
154
|
+
Then the command should succeed
|
|
155
|
+
And the JSON output at "$.items[?(@.path=='*:out')].qualifiedName" should not contain "filesystem:payments.checkout"
|
|
156
|
+
|
|
157
|
+
Scenario: A depth above the built-in limit is lowered, and the answer says so
|
|
158
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --path CHILD_OF:out --no-inherit --depth 9 --format json"
|
|
159
|
+
Then the command should succeed
|
|
160
|
+
And the JSON output at "$.effective.depth" should equal "4"
|
|
161
|
+
And the JSON output at "$.policy.clamped[?(@.control=='depth')].requested" should equal "9"
|
|
162
|
+
And the JSON output at "$.policy.clamped[?(@.control=='depth')].limitedBy" should equal "builtin"
|
|
163
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --path CHILD_OF:out --no-inherit --depth 9"
|
|
164
|
+
Then the command should succeed
|
|
165
|
+
And the output should contain "- depth: asked 9, got 4 (builtin)"
|
|
166
|
+
|
|
167
|
+
Scenario: A detail level or step that is not allowed is refused before any reading
|
|
168
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --detail huge"
|
|
169
|
+
Then the command should fail
|
|
170
|
+
And the exit code should be 2
|
|
171
|
+
|
|
172
|
+
# ─────────────────────────────────────────────────────────────
|
|
173
|
+
# Naming the node: FQTN ambiguity
|
|
174
|
+
# ─────────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
Scenario: An FQTN that two kinds share is refused, with the names that disambiguate it
|
|
177
|
+
When I run "kata tax create rule payments --set title=Payments --set severity=recommended -d 'A rule that is also called payments.'"
|
|
178
|
+
Then the command should succeed
|
|
179
|
+
When I run "kata tax context filesystem:payments"
|
|
180
|
+
Then the command should fail
|
|
181
|
+
And the output should contain "'filesystem:payments' names more than one node; use one of: filesystem:payments#area, filesystem:payments#rule"
|
|
182
|
+
When I run "kata tax context filesystem:payments#rule"
|
|
183
|
+
Then the command should succeed
|
|
184
|
+
And the output should contain "# Context: rule `filesystem:payments#rule`"
|
|
185
|
+
When I run "kata tax context filesystem:payments#area --map"
|
|
186
|
+
Then the command should succeed
|
|
187
|
+
And the output should contain "# Context: area `filesystem:payments#area`"
|
|
188
|
+
|
|
189
|
+
Scenario: A node that shares its name with another kind walks its own relationships
|
|
190
|
+
When I run "kata tax create rule payments --set title=Payments --set severity=recommended -d 'A rule that is also called payments.'"
|
|
191
|
+
Then the command should succeed
|
|
192
|
+
When I run "kata tax context 'filesystem:payments#area' --format json"
|
|
193
|
+
Then the command should succeed
|
|
194
|
+
And the JSON output at "$.node.qualifiedName" should equal "filesystem:payments#area"
|
|
195
|
+
And the JSON output at "$.items[*].qualifiedName" should contain "filesystem:encrypt-card-data"
|
|
196
|
+
And the JSON output at "$.items[*].qualifiedName" should contain "filesystem:use-postgres"
|
|
197
|
+
And the JSON output at "$.items[*].qualifiedName" should contain "filesystem:encrypt-card-data.scan-for-card-numbers"
|
|
198
|
+
And the JSON output at "$.dropped.unresolved" should equal "0"
|
|
199
|
+
When I run "kata tax context 'filesystem:payments#rule' --format json"
|
|
200
|
+
Then the command should succeed
|
|
201
|
+
And the JSON output at "$.items[*]" should have 0 items
|
|
202
|
+
|
|
203
|
+
Scenario: The children of a node that shares its name still find their parent
|
|
204
|
+
When I run "kata tax create rule payments --set title=Payments --set severity=recommended -d 'A rule that is also called payments.'"
|
|
205
|
+
Then the command should succeed
|
|
206
|
+
When I run "kata tax context filesystem:payments.checkout --path CHILD_OF:out --no-inherit --detail refs --format json"
|
|
207
|
+
Then the command should succeed
|
|
208
|
+
And the JSON output at "$.items[?(@.path=='CHILD_OF:out')].qualifiedName" should contain "filesystem:payments#area"
|
|
209
|
+
|
|
210
|
+
Scenario: A bare name of a nested node is refused with the name that works
|
|
211
|
+
When I run "kata tax context web-api"
|
|
212
|
+
Then the command should fail
|
|
213
|
+
And the output should contain "a node is named by its qualified name: use 'filesystem:payments.checkout.web-api'"
|
|
214
|
+
|
|
215
|
+
Scenario: An FQTN that one backend holds works without the backend
|
|
216
|
+
When I run "kata tax context payments.checkout.web-api --format json"
|
|
217
|
+
Then the command should succeed
|
|
218
|
+
And the JSON output at "$.node.qualifiedName" should equal "filesystem:payments.checkout.web-api"
|
|
219
|
+
|
|
220
|
+
# ─────────────────────────────────────────────────────────────
|
|
221
|
+
# #306: the markdown answer shows a node's fields
|
|
222
|
+
# ─────────────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
Scenario: At full detail the markdown renders a list field as bullets
|
|
225
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --path USES_PACKAGE:out --detail full"
|
|
226
|
+
Then the command should succeed
|
|
227
|
+
And the output should contain "- **httpx** `filesystem:httpx` · version: 0.27"
|
|
228
|
+
And the output should contain " - decisions:"
|
|
229
|
+
And the output should contain " - pin-httpx"
|
|
230
|
+
And the output should not contain "['pin-httpx']"
|
|
231
|
+
|
|
232
|
+
Scenario: At summary detail the markdown shows only the declared summary fields
|
|
233
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --path USES_PACKAGE:out --detail summary"
|
|
234
|
+
Then the command should succeed
|
|
235
|
+
And the output should contain "- **httpx** `filesystem:httpx` · version: 0.27"
|
|
236
|
+
And the output should not contain "- decisions:"
|
|
237
|
+
|
|
238
|
+
Scenario: The markdown and the JSON hold the same items for the same budget
|
|
239
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --path USES_PACKAGE:out --detail full --budget 120"
|
|
240
|
+
Then the command should succeed
|
|
241
|
+
When I record the item names of the context output
|
|
242
|
+
And I run "kata tax context filesystem:payments.checkout.web-api --path USES_PACKAGE:out --detail full --budget 120 --format json"
|
|
243
|
+
Then the command should succeed
|
|
244
|
+
And the context output should show the recorded item names
|
|
245
|
+
|
|
246
|
+
# ─────────────────────────────────────────────────────────────
|
|
247
|
+
# #307: one item reached by several routes
|
|
248
|
+
# ─────────────────────────────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
Scenario: An item reached by two routes prints its shortest route and counts the other
|
|
251
|
+
When I run "kata tax relate filesystem:payments.checkout.web-api GOVERNED_BY filesystem:pin-httpx"
|
|
252
|
+
Then the command should succeed
|
|
253
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --path USES_PACKAGE:out/GOVERNED_BY:out --detail summary"
|
|
254
|
+
Then the command should succeed
|
|
255
|
+
And the output should contain "why: ─GOVERNED_BY→ (+1 more route)"
|
|
256
|
+
And the output should not contain "package filesystem:httpx ─GOVERNED_BY→"
|
|
257
|
+
|
|
258
|
+
Scenario: At full detail the markdown lists every route
|
|
259
|
+
When I run "kata tax relate filesystem:payments.checkout.web-api GOVERNED_BY filesystem:pin-httpx"
|
|
260
|
+
Then the command should succeed
|
|
261
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --path USES_PACKAGE:out/GOVERNED_BY:out --detail full"
|
|
262
|
+
Then the command should succeed
|
|
263
|
+
And the output should contain "why: ─GOVERNED_BY→"
|
|
264
|
+
And the output should contain "why: ─USES_PACKAGE→ package filesystem:httpx ─GOVERNED_BY→"
|
|
265
|
+
And the output should not contain "more route"
|
|
266
|
+
|
|
267
|
+
Scenario: The JSON keeps every route, the shortest first
|
|
268
|
+
When I run "kata tax relate filesystem:payments.checkout.web-api GOVERNED_BY filesystem:pin-httpx"
|
|
269
|
+
Then the command should succeed
|
|
270
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --path USES_PACKAGE:out/GOVERNED_BY:out --detail summary --format json"
|
|
271
|
+
Then the command should succeed
|
|
272
|
+
And the JSON output at "$.items[?(@.qualifiedName=='filesystem:pin-httpx')].why[*]" should have 2 items
|
|
273
|
+
And the JSON output at "$.items[?(@.qualifiedName=='filesystem:pin-httpx')].why[0].path" should equal "GOVERNED_BY:out"
|
|
274
|
+
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
@cli @area-context
|
|
2
|
+
Feature: Kata CLI - Context: the budget, profile policy, and lint of the policy
|
|
3
|
+
As an operator deciding what each agent role may pull into its context
|
|
4
|
+
I want a size budget that cuts the least important items first, and policy per profile that only narrows
|
|
5
|
+
So that a small answer still holds the rule that blocks, and a profile never widens what the schema allows
|
|
6
|
+
|
|
7
|
+
# docs/plugins/context/index.md ("The budget", "Policy", "What kata tax lint checks"), against the
|
|
8
|
+
# same payments catalogue as 52_kata_context.feature.
|
|
9
|
+
#
|
|
10
|
+
# katalyst-taxonomy#291: the budget measures the content of the items, not the serialisation, so
|
|
11
|
+
# JSON and markdown return the same items for the same budget.
|
|
12
|
+
# katalyst-taxonomy#292: lint warns when a profile's policy makes an always-included view
|
|
13
|
+
# unreachable.
|
|
14
|
+
#
|
|
15
|
+
# The plugin's config.yaml section holds policy for every profile and for one profile. A
|
|
16
|
+
# profile is a role: `reviewer` reads decisions and rules in summary and follows no incoming
|
|
17
|
+
# step and no package; `ci-bot` reads names only, in a small answer.
|
|
18
|
+
|
|
19
|
+
Background:
|
|
20
|
+
Given the payments catalogue is installed
|
|
21
|
+
|
|
22
|
+
# ─────────────────────────────────────────────────────────────
|
|
23
|
+
# The budget
|
|
24
|
+
# ─────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
@smoke
|
|
27
|
+
Scenario: A small budget cuts the items that do not block first, and says what it cut
|
|
28
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --budget 100 --format json"
|
|
29
|
+
Then the command should succeed
|
|
30
|
+
And the JSON output at "$.items[*].qualifiedName" should contain "filesystem:encrypt-card-data"
|
|
31
|
+
And the JSON output at "$.items[?(@.qualifiedName=='filesystem:encrypt-card-data')].blocking.blocking" should equal "true"
|
|
32
|
+
And the JSON output at "$.truncated.cutBlocking" should equal "0"
|
|
33
|
+
And the JSON output at "$.truncated.cut[*].qualifiedNames[*]" should contain "filesystem:log-correlation-id"
|
|
34
|
+
And the JSON output at "$.truncated.cut[*].qualifiedNames[*]" should contain "filesystem:encrypt-card-data.scan-for-card-numbers"
|
|
35
|
+
And the JSON output at "$.truncated.cut[*].qualifiedNames[*]" should not contain "filesystem:encrypt-card-data"
|
|
36
|
+
And the JSON output at "$.truncated.hint.budget" should equal "270"
|
|
37
|
+
|
|
38
|
+
Scenario: The markdown names what the budget cut, and how to get it
|
|
39
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --budget 100"
|
|
40
|
+
Then the command should succeed
|
|
41
|
+
And the output should contain "⛔ blocking · **Encrypt card data** `filesystem:encrypt-card-data`"
|
|
42
|
+
And the output should contain "## Cut by the budget (100 tokens;"
|
|
43
|
+
And the output should contain "view governance, `FOLLOWS_RULE:out`: 1 cut — `filesystem:log-correlation-id`"
|
|
44
|
+
And the output should contain "More: ask again with budget 270."
|
|
45
|
+
|
|
46
|
+
Scenario: The budget returns the same items over JSON and over markdown
|
|
47
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --budget 100 --format json"
|
|
48
|
+
And I record the item names of the context output
|
|
49
|
+
And I run "kata tax context filesystem:payments.checkout.web-api --budget 100 --format markdown"
|
|
50
|
+
Then the command should succeed
|
|
51
|
+
And the context output should show the recorded item names
|
|
52
|
+
|
|
53
|
+
Scenario: Asking again with the hinted budget returns everything
|
|
54
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --budget 270 --format json"
|
|
55
|
+
Then the command should succeed
|
|
56
|
+
And the JSON output at "$.items[*]" should have 5 items
|
|
57
|
+
And the JSON output at "$.truncated" should be null
|
|
58
|
+
|
|
59
|
+
# ─────────────────────────────────────────────────────────────
|
|
60
|
+
# A profile narrows a view
|
|
61
|
+
# ─────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
@smoke
|
|
64
|
+
Scenario: A profile's policy narrows the answer, and the notes say what it hid
|
|
65
|
+
Given I run "kata config profile create reviewer --copy-from local"
|
|
66
|
+
And the context plugin is configured with:
|
|
67
|
+
"""
|
|
68
|
+
policy:
|
|
69
|
+
budget: { max: 8000 }
|
|
70
|
+
profiles:
|
|
71
|
+
reviewer:
|
|
72
|
+
policy:
|
|
73
|
+
detail: [refs, summary]
|
|
74
|
+
paths: { deny: ["*:in", "USES_PACKAGE"] }
|
|
75
|
+
"""
|
|
76
|
+
When I run "kata --profile reviewer tax context filesystem:payments.checkout.web-api --view package-decisions --detail full --format json"
|
|
77
|
+
Then the command should succeed
|
|
78
|
+
And the JSON output at "$.items[*].qualifiedName" should contain "filesystem:encrypt-card-data"
|
|
79
|
+
And the JSON output at "$.items[*].qualifiedName" should not contain "filesystem:encrypt-card-data.scan-for-card-numbers"
|
|
80
|
+
And the JSON output at "$.items[*].qualifiedName" should not contain "filesystem:pin-httpx"
|
|
81
|
+
And the JSON output at "$.policy.layers" should contain "profile:reviewer"
|
|
82
|
+
And the JSON output at "$.policy.notes[*].text" should contain "view 'governance' path 'FOLLOWS_RULE:out/CHILD_OF:in:check' is hidden by profile:reviewer"
|
|
83
|
+
And the JSON output at "$.policy.notes[*].text" should contain "view 'package-decisions' path 'USES_PACKAGE:out/GOVERNED_BY:out' is hidden by profile:reviewer"
|
|
84
|
+
And the JSON output at "$.policy.clamped[?(@.control=='detail')].requested" should equal "full"
|
|
85
|
+
And the JSON output at "$.policy.clamped[?(@.control=='detail')].effective" should equal "summary"
|
|
86
|
+
And the JSON output at "$.policy.clamped[?(@.control=='detail')].limitedBy" should equal "profile:reviewer"
|
|
87
|
+
|
|
88
|
+
Scenario: The same request under the default profile is not narrowed
|
|
89
|
+
Given I run "kata config profile create reviewer --copy-from local"
|
|
90
|
+
And the context plugin is configured with:
|
|
91
|
+
"""
|
|
92
|
+
profiles:
|
|
93
|
+
reviewer:
|
|
94
|
+
policy:
|
|
95
|
+
paths: { deny: ["*:in", "USES_PACKAGE"] }
|
|
96
|
+
"""
|
|
97
|
+
When I run "kata tax context filesystem:payments.checkout.web-api --view package-decisions --detail full --format json"
|
|
98
|
+
Then the command should succeed
|
|
99
|
+
And the JSON output at "$.items[*].qualifiedName" should contain "filesystem:encrypt-card-data.scan-for-card-numbers"
|
|
100
|
+
And the JSON output at "$.items[*].qualifiedName" should contain "filesystem:pin-httpx"
|
|
101
|
+
And the JSON output at "$.policy.layers" should not contain "profile:reviewer"
|
|
102
|
+
And the JSON output at "$.policy.notes[*]" should have 0 items
|
|
103
|
+
|
|
104
|
+
Scenario: The markdown ends with the policy layers and the notes
|
|
105
|
+
Given I run "kata config profile create reviewer --copy-from local"
|
|
106
|
+
And the context plugin is configured with:
|
|
107
|
+
"""
|
|
108
|
+
profiles:
|
|
109
|
+
reviewer:
|
|
110
|
+
policy:
|
|
111
|
+
detail: [refs, summary]
|
|
112
|
+
paths: { deny: ["*:in", "USES_PACKAGE"] }
|
|
113
|
+
"""
|
|
114
|
+
When I run "kata --profile reviewer tax context filesystem:payments.checkout.web-api --view package-decisions --detail full"
|
|
115
|
+
Then the command should succeed
|
|
116
|
+
And the output should contain "_Policy: builtin → kind:component → profile:reviewer_"
|
|
117
|
+
And the output should contain "- view 'governance' path 'FOLLOWS_RULE:out/CHILD_OF:in:check' is hidden by profile:reviewer"
|
|
118
|
+
And the output should contain "- detail: asked full, got summary (profile:reviewer)"
|
|
119
|
+
And the output should not contain "Scan for card numbers"
|
|
120
|
+
|
|
121
|
+
Scenario: A profile cannot widen a limit: a request above its budget is lowered
|
|
122
|
+
Given I run "kata config profile create ci-bot --copy-from local"
|
|
123
|
+
And the context plugin is configured with:
|
|
124
|
+
"""
|
|
125
|
+
profiles:
|
|
126
|
+
ci-bot:
|
|
127
|
+
policy:
|
|
128
|
+
detail: [refs]
|
|
129
|
+
budget: { max: 100 }
|
|
130
|
+
defaults: { budget: 100 }
|
|
131
|
+
"""
|
|
132
|
+
When I run "kata --profile ci-bot tax context filesystem:payments.checkout.web-api --budget 1000 --format json"
|
|
133
|
+
Then the command should succeed
|
|
134
|
+
And the JSON output at "$.effective.budget" should equal "100"
|
|
135
|
+
And the JSON output at "$.policy.clamped[?(@.control=='budget')].requested" should equal "1000"
|
|
136
|
+
And the JSON output at "$.policy.clamped[?(@.control=='budget')].effective" should equal "100"
|
|
137
|
+
And the JSON output at "$.policy.clamped[?(@.control=='budget')].limitedBy" should equal "profile:ci-bot"
|
|
138
|
+
And the JSON output at "$.truncated.hint.budget" should equal "100"
|
|
139
|
+
And the JSON output at "$.items[?(@.qualifiedName=='filesystem:encrypt-card-data')].blocking.blocking" should equal "true"
|
|
140
|
+
|
|
141
|
+
Scenario: A profile can take a view out of the always-included views, and the answer says so
|
|
142
|
+
Given I run "kata config profile create ci-bot --copy-from local"
|
|
143
|
+
And the context plugin is configured with:
|
|
144
|
+
"""
|
|
145
|
+
profiles:
|
|
146
|
+
ci-bot:
|
|
147
|
+
policy:
|
|
148
|
+
always_include: []
|
|
149
|
+
"""
|
|
150
|
+
When I run "kata --profile ci-bot tax context filesystem:payments.checkout.web-api --path USES_PACKAGE:out --format json"
|
|
151
|
+
Then the command should succeed
|
|
152
|
+
And the JSON output at "$.items[*].qualifiedName" should contain "filesystem:httpx"
|
|
153
|
+
And the JSON output at "$.items[*].qualifiedName" should not contain "filesystem:encrypt-card-data"
|
|
154
|
+
And the JSON output at "$.dropped.always[0].view" should equal "governance"
|
|
155
|
+
And the JSON output at "$.dropped.always[0].limitedBy" should equal "profile:ci-bot"
|
|
156
|
+
When I run "kata --profile ci-bot tax context filesystem:payments.checkout.web-api --map --format json"
|
|
157
|
+
Then the command should succeed
|
|
158
|
+
And the JSON output at "$.views[?(@.name=='governance')].always" should equal "false"
|
|
159
|
+
And the JSON output at "$.views[?(@.name=='governance')].droppedBy" should equal "profile:ci-bot"
|
|
160
|
+
|
|
161
|
+
Scenario: The effective policy of a node can be printed
|
|
162
|
+
Given I run "kata config profile create reviewer --copy-from local"
|
|
163
|
+
And the context plugin is configured with:
|
|
164
|
+
"""
|
|
165
|
+
profiles:
|
|
166
|
+
reviewer:
|
|
167
|
+
policy:
|
|
168
|
+
detail: [refs, summary]
|
|
169
|
+
"""
|
|
170
|
+
When I run "kata --profile reviewer tax context filesystem:payments.checkout.web-api --policy"
|
|
171
|
+
Then the command should succeed
|
|
172
|
+
And the output should be valid JSON
|
|
173
|
+
And the JSON output at "$.effective.layers" should contain "kind:component"
|
|
174
|
+
And the JSON output at "$.effective.layers" should contain "profile:reviewer"
|
|
175
|
+
And the JSON output at "$.effective.detail" should not contain "full"
|
|
176
|
+
|
|
177
|
+
# ─────────────────────────────────────────────────────────────
|
|
178
|
+
# #292: kata tax lint warns when a profile hides an always-included view
|
|
179
|
+
# ─────────────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
Scenario: Lint warns when a profile hides a path of an always-included view
|
|
182
|
+
Given I run "kata config profile create reviewer --copy-from local"
|
|
183
|
+
And the context plugin is configured with:
|
|
184
|
+
"""
|
|
185
|
+
profiles:
|
|
186
|
+
reviewer:
|
|
187
|
+
policy:
|
|
188
|
+
paths: { deny: ["*:in", "USES_PACKAGE"] }
|
|
189
|
+
"""
|
|
190
|
+
When I run "kata tax lint"
|
|
191
|
+
Then the command should succeed
|
|
192
|
+
And the output should contain "Rule warnings"
|
|
193
|
+
And the output should contain "[context-policy] kind 'component': profile:reviewer hides always-included view 'governance' path 'FOLLOWS_RULE:out/CHILD_OF:in:check'"
|
|
194
|
+
And the output should contain "[context-policy] kind 'service': profile:reviewer hides always-included view 'governance' path 'FOLLOWS_RULE:out/CHILD_OF:in:check'"
|
|
195
|
+
And the output should contain "[context-policy] kind 'area': profile:reviewer hides always-included view 'governance' path 'FOLLOWS_RULE:out/CHILD_OF:in:check'"
|
|
196
|
+
|
|
197
|
+
Scenario: Lint says when the hidden path can reach a blocking item
|
|
198
|
+
Given I run "kata config profile create triage --copy-from local"
|
|
199
|
+
And the context plugin is configured with:
|
|
200
|
+
"""
|
|
201
|
+
profiles:
|
|
202
|
+
triage:
|
|
203
|
+
policy:
|
|
204
|
+
paths: { deny: ["FOLLOWS_RULE"] }
|
|
205
|
+
inheritance: false
|
|
206
|
+
"""
|
|
207
|
+
When I run "kata tax lint"
|
|
208
|
+
Then the command should succeed
|
|
209
|
+
And the output should contain "[context-policy] kind 'component': profile:triage turns inheritance off, so always-included view 'governance' loses what its ancestors carry, which can include blocking items (rule)"
|
|
210
|
+
And the output should contain "[context-policy] kind 'component': profile:triage hides always-included view 'governance' path 'FOLLOWS_RULE:out'; it can reach blocking items (rule)"
|
|
211
|
+
|
|
212
|
+
Scenario: Lint warns when a profile takes the view out of the always-included views
|
|
213
|
+
Given I run "kata config profile create ci-bot --copy-from local"
|
|
214
|
+
And the context plugin is configured with:
|
|
215
|
+
"""
|
|
216
|
+
profiles:
|
|
217
|
+
ci-bot:
|
|
218
|
+
policy:
|
|
219
|
+
always_include: []
|
|
220
|
+
"""
|
|
221
|
+
When I run "kata tax lint"
|
|
222
|
+
Then the command should succeed
|
|
223
|
+
And the output should contain "[context-policy] kind 'component': profile:ci-bot"
|
|
224
|
+
And the output should contain "always-included view 'governance'"
|
|
225
|
+
|
|
226
|
+
Scenario: Lint warns when a max_depth is shorter than a path of the always-included view
|
|
227
|
+
Given I run "kata config profile create shallow --copy-from local"
|
|
228
|
+
And the context plugin is configured with:
|
|
229
|
+
"""
|
|
230
|
+
profiles:
|
|
231
|
+
shallow:
|
|
232
|
+
policy:
|
|
233
|
+
max_depth: 1
|
|
234
|
+
"""
|
|
235
|
+
When I run "kata tax lint"
|
|
236
|
+
Then the command should succeed
|
|
237
|
+
And the output should contain "[context-policy] kind 'component': profile:shallow sets max_depth 1, shorter than always-included view 'governance' path 'FOLLOWS_RULE:out/CHILD_OF:in:check'"
|
|
238
|
+
|
|
239
|
+
Scenario: Lint is silent when no profile narrows the always-included view
|
|
240
|
+
Given I run "kata config profile create reviewer --copy-from local"
|
|
241
|
+
And the context plugin is configured with:
|
|
242
|
+
"""
|
|
243
|
+
profiles:
|
|
244
|
+
reviewer:
|
|
245
|
+
policy:
|
|
246
|
+
detail: [refs, summary]
|
|
247
|
+
budget: { max: 2000 }
|
|
248
|
+
"""
|
|
249
|
+
When I run "kata tax lint"
|
|
250
|
+
Then the command should succeed
|
|
251
|
+
And the output should not contain "context-policy"
|
|
252
|
+
|
|
253
|
+
Scenario: A policy that breaks the plugin's schema fails closed
|
|
254
|
+
Given I run "kata config profile create reviewer --copy-from local"
|
|
255
|
+
And the context plugin is configured with:
|
|
256
|
+
"""
|
|
257
|
+
profiles:
|
|
258
|
+
reviewer:
|
|
259
|
+
policy:
|
|
260
|
+
budget: { max: lots }
|
|
261
|
+
"""
|
|
262
|
+
When I run "kata --profile reviewer tax context filesystem:payments.checkout.web-api"
|
|
263
|
+
Then the command should fail
|
|
264
|
+
And the output should contain "budget.max"
|
|
265
|
+
And the output should contain "is not of type 'integer'"
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
@mcp @area-context
|
|
2
|
+
Feature: MCP Server - Context: taxonomy_context_map and taxonomy_context
|
|
3
|
+
As an AI agent about to change a node
|
|
4
|
+
I want the map of what surrounds it and one call that returns the decisions and rules that apply
|
|
5
|
+
So that I read a rule the node inherits, and a blocking item, before I act
|
|
6
|
+
|
|
7
|
+
# docs/plugins/context/index.md ("The surfaces"), against the service catalogue of
|
|
8
|
+
# examples/catalogue with the payments model of plugins/context/xspec/fixtures/context-catalogue. Over MCP
|
|
9
|
+
# the format defaults to json and the answer is an envelope: {ok, data}. With
|
|
10
|
+
# format=markdown, data is {markdown, result}. The same service answers the CLI, so the
|
|
11
|
+
# details are in 52_kata_context.feature and 53_kata_context_budget_policy.feature; these
|
|
12
|
+
# scenarios hold the essentials of the tools.
|
|
13
|
+
|
|
14
|
+
Background:
|
|
15
|
+
Given the MCP server is connected
|
|
16
|
+
And the payments catalogue is installed in the MCP workspace
|
|
17
|
+
|
|
18
|
+
Scenario: The server exposes the context tools
|
|
19
|
+
Then the MCP server should expose a tool named "taxonomy_context_map"
|
|
20
|
+
And the MCP server should expose a tool named "taxonomy_context"
|
|
21
|
+
|
|
22
|
+
@smoke
|
|
23
|
+
Scenario: The map lists the ancestors, the views and what can be asked next
|
|
24
|
+
When I call MCP tool "taxonomy_context_map" with:
|
|
25
|
+
| key | value |
|
|
26
|
+
| node | filesystem:payments.checkout.web-api |
|
|
27
|
+
Then the result should succeed
|
|
28
|
+
And the JSON result at "$.ok" should equal "true"
|
|
29
|
+
And the JSON result at "$.data.node.qualifiedName" should equal "filesystem:payments.checkout.web-api"
|
|
30
|
+
And the JSON result at "$.data.ancestors[*].qualifiedName" should contain "filesystem:payments.checkout"
|
|
31
|
+
And the JSON result at "$.data.ancestors[*].qualifiedName" should contain "filesystem:payments"
|
|
32
|
+
And the JSON result at "$.data.views[?(@.name=='governance')].always" should equal "true"
|
|
33
|
+
And the JSON result at "$.data.views[?(@.name=='package-decisions')].always" should equal "false"
|
|
34
|
+
And the JSON result at "$.data.relationships[*].path" should contain "USES_PACKAGE:out"
|
|
35
|
+
And the JSON result at "$.data.options.steps" should contain "GOVERNED_BY:out"
|
|
36
|
+
And the JSON result at "$.data.always.items[*].qualifiedName" should contain "filesystem:encrypt-card-data"
|
|
37
|
+
|
|
38
|
+
@smoke
|
|
39
|
+
Scenario: A call with no path and no view returns the always-included view, the mandatory rule first
|
|
40
|
+
When I call MCP tool "taxonomy_context" with:
|
|
41
|
+
| key | value |
|
|
42
|
+
| node | filesystem:payments.checkout.web-api |
|
|
43
|
+
Then the result should succeed
|
|
44
|
+
And the JSON result at "$.data.items[0].qualifiedName" should equal "filesystem:encrypt-card-data"
|
|
45
|
+
And the JSON result at "$.data.items[0].blocking.blocking" should equal "true"
|
|
46
|
+
And the JSON result at "$.data.items[0].inheritedFrom" should equal "filesystem:payments"
|
|
47
|
+
And the JSON result at "$.data.items[?(@.qualifiedName=='filesystem:log-correlation-id')].blocking.blocking" should equal "false"
|
|
48
|
+
And the JSON result at "$.data.effective.always" should contain "governance"
|
|
49
|
+
And the JSON result at "$.data.truncated" should be null
|
|
50
|
+
|
|
51
|
+
Scenario: A named view still holds the always-included view
|
|
52
|
+
When I call MCP tool "taxonomy_context" with:
|
|
53
|
+
| key | value |
|
|
54
|
+
| node | filesystem:payments.checkout.web-api |
|
|
55
|
+
| views | ["package-decisions"] |
|
|
56
|
+
Then the result should succeed
|
|
57
|
+
And the JSON result at "$.data.items[*].qualifiedName" should contain "filesystem:encrypt-card-data"
|
|
58
|
+
And the JSON result at "$.data.items[?(@.view=='package-decisions')].qualifiedName" should contain "filesystem:pin-httpx"
|
|
59
|
+
|
|
60
|
+
Scenario: A path still holds the always-included view
|
|
61
|
+
When I call MCP tool "taxonomy_context" with:
|
|
62
|
+
| key | value |
|
|
63
|
+
| node | filesystem:payments.checkout.web-api |
|
|
64
|
+
| paths | ["USES_PACKAGE:out"] |
|
|
65
|
+
Then the result should succeed
|
|
66
|
+
And the JSON result at "$.data.items[*].qualifiedName" should contain "filesystem:encrypt-card-data"
|
|
67
|
+
And the JSON result at "$.data.items[*].qualifiedName" should contain "filesystem:httpx"
|
|
68
|
+
|
|
69
|
+
Scenario: The detail and the depth decide how much is returned
|
|
70
|
+
When I call MCP tool "taxonomy_context" with:
|
|
71
|
+
| key | value |
|
|
72
|
+
| node | filesystem:payments.checkout.web-api |
|
|
73
|
+
| paths | ["USES_PACKAGE:out"] |
|
|
74
|
+
| detail | full |
|
|
75
|
+
Then the result should succeed
|
|
76
|
+
And the JSON result at "$.data.items[?(@.qualifiedName=='filesystem:httpx')].detail" should equal "full"
|
|
77
|
+
And the JSON result at "$.data.items[?(@.qualifiedName=='filesystem:httpx')].spec.version" should equal "0.27"
|
|
78
|
+
When I call MCP tool "taxonomy_context" with:
|
|
79
|
+
| key | value |
|
|
80
|
+
| node | filesystem:payments.checkout.web-api |
|
|
81
|
+
| paths | ["CHILD_OF:out"] |
|
|
82
|
+
| depth | 2 |
|
|
83
|
+
| inherit | false |
|
|
84
|
+
| detail | refs |
|
|
85
|
+
Then the result should succeed
|
|
86
|
+
And the JSON result at "$.data.items[?(@.path=='CHILD_OF:out')].qualifiedName" should contain "filesystem:payments"
|
|
87
|
+
|
|
88
|
+
Scenario: A small budget cuts the items that do not block first, over the default JSON format
|
|
89
|
+
When I call MCP tool "taxonomy_context" with:
|
|
90
|
+
| key | value |
|
|
91
|
+
| node | filesystem:payments.checkout.web-api |
|
|
92
|
+
| budget | 100 |
|
|
93
|
+
Then the result should succeed
|
|
94
|
+
And the JSON result at "$.data.items[*].qualifiedName" should contain "filesystem:encrypt-card-data"
|
|
95
|
+
And the JSON result at "$.data.items[?(@.qualifiedName=='filesystem:encrypt-card-data')].blocking.blocking" should equal "true"
|
|
96
|
+
And the JSON result at "$.data.truncated.cutBlocking" should equal "0"
|
|
97
|
+
And the JSON result at "$.data.truncated.cut[*].qualifiedNames[*]" should contain "filesystem:log-correlation-id"
|
|
98
|
+
|
|
99
|
+
Scenario: The budget returns the same items in JSON and in markdown
|
|
100
|
+
When I call MCP tool "taxonomy_context" with:
|
|
101
|
+
| key | value |
|
|
102
|
+
| node | filesystem:payments.checkout.web-api |
|
|
103
|
+
| budget | 100 |
|
|
104
|
+
And I record the item names of the MCP context result
|
|
105
|
+
And I call MCP tool "taxonomy_context" with:
|
|
106
|
+
| key | value |
|
|
107
|
+
| node | filesystem:payments.checkout.web-api |
|
|
108
|
+
| budget | 100 |
|
|
109
|
+
| format | markdown |
|
|
110
|
+
Then the result should succeed
|
|
111
|
+
And the MCP context result should show the recorded item names
|
|
112
|
+
And the markdown of the MCP context result should show the recorded item names
|
|
113
|
+
And the JSON result at "$.data.markdown" should contain "Cut by the budget (100 tokens;"
|
|
114
|
+
|
|
115
|
+
Scenario: A profile's policy narrows the answer, and the notes say what it hid
|
|
116
|
+
Given the MCP workspace has a profile "reviewer" that is active
|
|
117
|
+
And the context plugin is configured in the MCP workspace with:
|
|
118
|
+
"""
|
|
119
|
+
profiles:
|
|
120
|
+
reviewer:
|
|
121
|
+
policy:
|
|
122
|
+
detail: [refs, summary]
|
|
123
|
+
paths: { deny: ["*:in", "USES_PACKAGE"] }
|
|
124
|
+
"""
|
|
125
|
+
When I call MCP tool "taxonomy_context" with:
|
|
126
|
+
| key | value |
|
|
127
|
+
| node | filesystem:payments.checkout.web-api |
|
|
128
|
+
| views | ["package-decisions"] |
|
|
129
|
+
| detail | full |
|
|
130
|
+
Then the result should succeed
|
|
131
|
+
And the JSON result at "$.data.items[*].qualifiedName" should contain "filesystem:encrypt-card-data"
|
|
132
|
+
And the JSON result at "$.data.items[*].qualifiedName" should not include the value "filesystem:pin-httpx"
|
|
133
|
+
And the JSON result at "$.data.items[*].qualifiedName" should not include the value "filesystem:encrypt-card-data.scan-for-card-numbers"
|
|
134
|
+
And the JSON result at "$.data.policy.layers" should contain "profile:reviewer"
|
|
135
|
+
And the JSON result at "$.data.policy.notes[*].text" should contain "view 'package-decisions' path 'USES_PACKAGE:out/GOVERNED_BY:out' is hidden by profile:reviewer"
|
|
136
|
+
And the JSON result at "$.data.policy.clamped[?(@.control=='detail')].effective" should equal "summary"
|
|
137
|
+
|
|
138
|
+
Scenario: An FQTN that two kinds share is refused, with the names that disambiguate it
|
|
139
|
+
Given a config file ".global/taxonomy/catalogue/rules/payments.yaml" in the MCP workspace with:
|
|
140
|
+
"""
|
|
141
|
+
apiVersion: katalyst.taxonomy/v2alpha
|
|
142
|
+
kind: rule
|
|
143
|
+
metadata:
|
|
144
|
+
name: payments
|
|
145
|
+
spec:
|
|
146
|
+
title: Payments
|
|
147
|
+
severity: recommended
|
|
148
|
+
description: A rule that is also called payments.
|
|
149
|
+
owners: [team@example.com]
|
|
150
|
+
"""
|
|
151
|
+
When I call MCP tool "taxonomy_context" with:
|
|
152
|
+
| key | value |
|
|
153
|
+
| node | filesystem:payments |
|
|
154
|
+
Then the JSON result at "$.ok" should equal "false"
|
|
155
|
+
And the JSON result at "$.error.code" should equal "INVALID_INPUT"
|
|
156
|
+
And the result should contain "filesystem:payments#area"
|
|
157
|
+
And the result should contain "filesystem:payments#rule"
|
|
158
|
+
When I call MCP tool "taxonomy_context_map" with:
|
|
159
|
+
| key | value |
|
|
160
|
+
| node | filesystem:payments#rule |
|
|
161
|
+
Then the result should succeed
|
|
162
|
+
And the JSON result at "$.data.node.qualifiedName" should equal "filesystem:payments#rule"
|
|
163
|
+
|
|
164
|
+
Scenario: A node that shares its name with another kind walks its own relationships
|
|
165
|
+
Given a config file ".global/taxonomy/catalogue/rules/payments.yaml" in the MCP workspace with:
|
|
166
|
+
"""
|
|
167
|
+
apiVersion: katalyst.taxonomy/v2alpha
|
|
168
|
+
kind: rule
|
|
169
|
+
metadata:
|
|
170
|
+
name: payments
|
|
171
|
+
spec:
|
|
172
|
+
title: Payments
|
|
173
|
+
severity: recommended
|
|
174
|
+
description: A rule that is also called payments.
|
|
175
|
+
owners: [team@example.com]
|
|
176
|
+
"""
|
|
177
|
+
When I call MCP tool "taxonomy_context" with:
|
|
178
|
+
| key | value |
|
|
179
|
+
| node | filesystem:payments#area |
|
|
180
|
+
Then the result should succeed
|
|
181
|
+
And the JSON result at "$.data.items[*].qualifiedName" should contain "filesystem:use-postgres"
|
|
182
|
+
And the JSON result at "$.data.dropped.unresolved" should equal "0"
|
|
183
|
+
|
|
184
|
+
Scenario: A bare name of a nested node is refused with the name that works
|
|
185
|
+
When I call MCP tool "taxonomy_context" with:
|
|
186
|
+
| key | value |
|
|
187
|
+
| node | web-api |
|
|
188
|
+
Then the JSON result at "$.ok" should equal "false"
|
|
189
|
+
And the JSON result at "$.error.code" should equal "NOT_FOUND"
|
|
190
|
+
And the JSON result at "$.error.message" should contain "filesystem:payments.checkout.web-api"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
apiVersion: katalyst.taxonomy/v2alpha
|
|
2
|
+
kind: check
|
|
3
|
+
metadata:
|
|
4
|
+
name: "scan-for-card-numbers"
|
|
5
|
+
spec:
|
|
6
|
+
title: "Scan for card numbers"
|
|
7
|
+
command: "make scan-pan"
|
|
8
|
+
description: "Scans the code and the log samples for card numbers."
|
|
9
|
+
owners:
|
|
10
|
+
- "team@example.com"
|
|
11
|
+
parents:
|
|
12
|
+
node: "encrypt-card-data"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
apiVersion: katalyst.taxonomy/v2alpha
|
|
2
|
+
kind: component
|
|
3
|
+
metadata:
|
|
4
|
+
name: web-api
|
|
5
|
+
spec:
|
|
6
|
+
language: python
|
|
7
|
+
description: The HTTP API that the checkout flow exposes to the storefront.
|
|
8
|
+
owners:
|
|
9
|
+
- team@example.com
|
|
10
|
+
parents:
|
|
11
|
+
node: payments.checkout
|
|
12
|
+
rules:
|
|
13
|
+
- log-correlation-id
|
|
14
|
+
packages:
|
|
15
|
+
- httpx
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
apiVersion: katalyst.taxonomy/v2alpha
|
|
2
|
+
kind: decision
|
|
3
|
+
metadata:
|
|
4
|
+
name: "async-events"
|
|
5
|
+
spec:
|
|
6
|
+
title: "Publish events, do not call"
|
|
7
|
+
status: "accepted"
|
|
8
|
+
description: "The checkout service tells other services about an order by publishing an event."
|
|
9
|
+
owners:
|
|
10
|
+
- "team@example.com"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
apiVersion: katalyst.taxonomy/v2alpha
|
|
2
|
+
kind: decision
|
|
3
|
+
metadata:
|
|
4
|
+
name: "use-postgres"
|
|
5
|
+
spec:
|
|
6
|
+
title: "Use PostgreSQL"
|
|
7
|
+
status: "accepted"
|
|
8
|
+
description: "Every service in the payments area stores its data in PostgreSQL."
|
|
9
|
+
owners:
|
|
10
|
+
- "team@example.com"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
apiVersion: katalyst.taxonomy/v2alpha
|
|
2
|
+
kind: rule
|
|
3
|
+
metadata:
|
|
4
|
+
name: "encrypt-card-data"
|
|
5
|
+
spec:
|
|
6
|
+
title: "Encrypt card data"
|
|
7
|
+
severity: "mandatory"
|
|
8
|
+
description: "Card numbers are encrypted at rest and in transit. No log line contains a card number."
|
|
9
|
+
owners:
|
|
10
|
+
- "team@example.com"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
apiVersion: katalyst.taxonomy/v2alpha
|
|
2
|
+
kind: rule
|
|
3
|
+
metadata:
|
|
4
|
+
name: "log-correlation-id"
|
|
5
|
+
spec:
|
|
6
|
+
title: "Log a correlation id"
|
|
7
|
+
severity: "recommended"
|
|
8
|
+
description: "Every log line of a request carries the correlation id of the request."
|
|
9
|
+
owners:
|
|
10
|
+
- "team@example.com"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "katalyst-plugin-context-xspec",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "The context plugin's xspec suite. Its dependencies and the shared step library are xspec/'s (npm workspace at the repository root).",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"gen": "bddgen",
|
|
9
|
+
"test": "bddgen && playwright test",
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"smoke": "../../../xspec/scripts/run-tags.sh '@smoke'",
|
|
12
|
+
"tags": "../../../xspec/scripts/run-tags.sh",
|
|
13
|
+
"clean": "rm -rf .features-gen test-results cucumber-report blob-report playwright-report"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// The context plugin's suite: its features (features/<surface>/), fixtures and steps (steps/).
|
|
2
|
+
// The shared step library (xspec/support/) and config (xspec/playwright.base.ts) are the root's.
|
|
3
|
+
import { xspecConfig } from '../../../xspec/playwright.base.js';
|
|
4
|
+
|
|
5
|
+
export default xspecConfig(import.meta.url);
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step definitions for the context plugin features (kata tax context, taxonomy_context_map,
|
|
3
|
+
* taxonomy_context).
|
|
4
|
+
*
|
|
5
|
+
* The fixture is the neutral service catalogue of `examples/catalogue`, installed the way
|
|
6
|
+
* docs/user-guide/getting-started.md installs it (`kata tax bundle add`), with the payments
|
|
7
|
+
* nodes of `plugins/context/xspec/fixtures/context-catalogue` copied in beside it. The nodes are written
|
|
8
|
+
* as files rather than through ~20 `kata tax create` calls, which would be most of every
|
|
9
|
+
* scenario's time.
|
|
10
|
+
*/
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { expect } from '@playwright/test';
|
|
15
|
+
import { createBdd } from 'playwright-bdd';
|
|
16
|
+
import { buildEnv, createTempDir, dedent, runCommand } from '../../../../xspec/support/cli/steps.js';
|
|
17
|
+
import { runKata } from '../../../../xspec/support/mcp/steps.js';
|
|
18
|
+
import { repoCommand } from '../../../../xspec/support/runner.js';
|
|
19
|
+
import type { CliWorld, test as sharedTest } from '../../../../xspec/support/fixtures.js';
|
|
20
|
+
import type { McpPort, McpToolResult } from '../../../../xspec/support/mcp/port.js';
|
|
21
|
+
|
|
22
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const repoRoot = path.resolve(__dirname, '../../../..');
|
|
24
|
+
const catalogueExample = path.join(repoRoot, 'examples', 'catalogue');
|
|
25
|
+
const paymentsFixture = path.join(__dirname, '..', 'fixtures', 'context-catalogue', 'catalogue');
|
|
26
|
+
|
|
27
|
+
/** Copy the payments nodes into the repository, beside what the bundle's kinds store. */
|
|
28
|
+
function copyPayments(repoDir: string): void {
|
|
29
|
+
const target = path.join(repoDir, '.global', 'taxonomy', 'catalogue');
|
|
30
|
+
fs.mkdirSync(target, { recursive: true });
|
|
31
|
+
fs.cpSync(paymentsFixture, target, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The text that goes under `spec:` of config.yaml to configure the context plugin: the
|
|
36
|
+
* docstring is the plugin's `config` mapping.
|
|
37
|
+
*/
|
|
38
|
+
function pluginsBlock(configYaml: string): string {
|
|
39
|
+
const body = dedent(configYaml)
|
|
40
|
+
.split('\n')
|
|
41
|
+
.map((line) => (line.length > 0 ? ` ${line}` : line))
|
|
42
|
+
.join('\n');
|
|
43
|
+
return [' plugins:', ' - name: context', ' config:', body, ''].join('\n');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Append the plugin block to a config file whose `spec:` is its last mapping. */
|
|
47
|
+
function appendPluginConfig(configFile: string, configYaml: string): void {
|
|
48
|
+
const current = fs.readFileSync(configFile, 'utf-8');
|
|
49
|
+
expect(current, `${configFile} already configures plugins`).not.toContain('\n plugins:');
|
|
50
|
+
fs.writeFileSync(configFile, `${current.replace(/\n*$/, '\n')}${pluginsBlock(configYaml)}`, 'utf-8');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The qualified names of the items in a context answer: from the JSON result (a CLI
|
|
55
|
+
* `--format json` answer, or an MCP envelope with or without markdown beside it), or from
|
|
56
|
+
* the markdown, whose item bullets come before the cut report and the policy line.
|
|
57
|
+
*/
|
|
58
|
+
export function contextItemNames(text: string): string[] {
|
|
59
|
+
let parsed: unknown;
|
|
60
|
+
try {
|
|
61
|
+
parsed = JSON.parse(text);
|
|
62
|
+
} catch {
|
|
63
|
+
parsed = undefined;
|
|
64
|
+
}
|
|
65
|
+
if (parsed !== undefined && parsed !== null && typeof parsed === 'object') {
|
|
66
|
+
const root = parsed as Record<string, unknown>;
|
|
67
|
+
const data = (root.data ?? root) as Record<string, unknown>;
|
|
68
|
+
const result = (data.result ?? data) as { items?: Array<{ qualifiedName: string }> };
|
|
69
|
+
expect(Array.isArray(result.items), `no items in ${text.substring(0, 300)}`).toBe(true);
|
|
70
|
+
return result.items!.map((item) => item.qualifiedName);
|
|
71
|
+
}
|
|
72
|
+
const names: string[] = [];
|
|
73
|
+
for (const line of text.split('\n')) {
|
|
74
|
+
if (line.startsWith('## Cut by the budget') || line.startsWith('_Policy:')) break;
|
|
75
|
+
const match = /^- .*?`([^`]+)`/.exec(line);
|
|
76
|
+
if (match) names.push(match[1]);
|
|
77
|
+
}
|
|
78
|
+
return names;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const recorded = new Map<string, string[]>();
|
|
82
|
+
|
|
83
|
+
export function registerContextSteps(test: typeof sharedTest): void {
|
|
84
|
+
const { Given, When, Then } = createBdd(test);
|
|
85
|
+
|
|
86
|
+
// ── CLI ────────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
Given(
|
|
89
|
+
'the payments catalogue is installed',
|
|
90
|
+
{ tags: '@cli' },
|
|
91
|
+
async ({ cliWorld }: { cliWorld: CliWorld }) => {
|
|
92
|
+
cliWorld.workDir = createTempDir();
|
|
93
|
+
runCommand(cliWorld, repoCommand(repoRoot, 'kata tax init -y'));
|
|
94
|
+
expect(cliWorld.success, `init failed: ${cliWorld.stdout}${cliWorld.stderr}`).toBe(true);
|
|
95
|
+
runCommand(cliWorld, repoCommand(repoRoot, `kata tax bundle add -y "${catalogueExample}"`));
|
|
96
|
+
expect(cliWorld.success, `bundle add failed: ${cliWorld.stdout}${cliWorld.stderr}`).toBe(true);
|
|
97
|
+
copyPayments(cliWorld.workDir);
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
Given(
|
|
102
|
+
'the context plugin is configured with:',
|
|
103
|
+
{ tags: '@cli' },
|
|
104
|
+
async ({ cliWorld }: { cliWorld: CliWorld }, docString: string) => {
|
|
105
|
+
const home = buildEnv(cliWorld).KATALYST_HOME as string;
|
|
106
|
+
appendPluginConfig(path.join(home, 'config', 'config.yaml'), docString);
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
When(
|
|
111
|
+
'I record the item names of the context output',
|
|
112
|
+
{ tags: '@cli' },
|
|
113
|
+
async ({ cliWorld }: { cliWorld: CliWorld }) => {
|
|
114
|
+
expect(cliWorld.exitCode, cliWorld.stderr).toBe(0);
|
|
115
|
+
const names = contextItemNames(cliWorld.stdout);
|
|
116
|
+
expect(names.length, 'the answer has no items to record').toBeGreaterThan(0);
|
|
117
|
+
recorded.set(cliWorld.workDir, names);
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
Then(
|
|
122
|
+
'the context output should show the recorded item names',
|
|
123
|
+
{ tags: '@cli' },
|
|
124
|
+
async ({ cliWorld }: { cliWorld: CliWorld }) => {
|
|
125
|
+
const before = recorded.get(cliWorld.workDir);
|
|
126
|
+
expect(before, 'no item names were recorded').toBeTruthy();
|
|
127
|
+
expect(contextItemNames(cliWorld.stdout)).toEqual(before);
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
Then(
|
|
132
|
+
'the output should list {string} before {string}',
|
|
133
|
+
{ tags: '@cli' },
|
|
134
|
+
async ({ cliWorld }: { cliWorld: CliWorld }, first: string, second: string) => {
|
|
135
|
+
const out = cliWorld.stdout;
|
|
136
|
+
const a = out.indexOf(first);
|
|
137
|
+
const b = out.indexOf(second);
|
|
138
|
+
expect(a, `"${first}" is not in the output`).toBeGreaterThanOrEqual(0);
|
|
139
|
+
expect(b, `"${second}" is not in the output`).toBeGreaterThanOrEqual(0);
|
|
140
|
+
expect(a, `"${first}" should come before "${second}"`).toBeLessThan(b);
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// ── MCP ────────────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
Given(
|
|
147
|
+
'the payments catalogue is installed in the MCP workspace',
|
|
148
|
+
{ tags: '@mcp' },
|
|
149
|
+
async ({ mcp }: { mcp: McpPort }) => {
|
|
150
|
+
const workspace = mcp.getWorkspaceDir();
|
|
151
|
+
runKata(`kata tax bundle add -y "${catalogueExample}"`, workspace);
|
|
152
|
+
copyPayments(workspace);
|
|
153
|
+
await mcp.reconnect();
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
Given(
|
|
158
|
+
'the MCP workspace has a profile {string} that is active',
|
|
159
|
+
{ tags: '@mcp' },
|
|
160
|
+
async ({ mcp }: { mcp: McpPort }, name: string) => {
|
|
161
|
+
const workspace = mcp.getWorkspaceDir();
|
|
162
|
+
runKata(`kata config profile create ${name} --copy-from local`, workspace);
|
|
163
|
+
runKata(`kata config profile use ${name}`, workspace);
|
|
164
|
+
await mcp.reconnect();
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
Given(
|
|
169
|
+
'the context plugin is configured in the MCP workspace with:',
|
|
170
|
+
{ tags: '@mcp' },
|
|
171
|
+
async ({ mcp }: { mcp: McpPort }, docString: string) => {
|
|
172
|
+
const workspace = mcp.getWorkspaceDir();
|
|
173
|
+
appendPluginConfig(path.join(`${workspace}-home`, 'config', 'config.yaml'), docString);
|
|
174
|
+
await mcp.reconnect();
|
|
175
|
+
},
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
When(
|
|
179
|
+
'I record the item names of the MCP context result',
|
|
180
|
+
{ tags: '@mcp' },
|
|
181
|
+
async ({ mcp, mcpToolResults }: { mcp: McpPort; mcpToolResults: Map<string, McpToolResult> }) => {
|
|
182
|
+
const result = mcpToolResults.get('_last');
|
|
183
|
+
expect(result, 'no result to record').toBeTruthy();
|
|
184
|
+
expect(result!.isError, result!.text).toBe(false);
|
|
185
|
+
const names = contextItemNames(result!.text);
|
|
186
|
+
expect(names.length, 'the answer has no items to record').toBeGreaterThan(0);
|
|
187
|
+
recorded.set(mcp.getWorkspaceDir(), names);
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
Then(
|
|
192
|
+
'the markdown of the MCP context result should show the recorded item names',
|
|
193
|
+
{ tags: '@mcp' },
|
|
194
|
+
async ({ mcp, mcpToolResults }: { mcp: McpPort; mcpToolResults: Map<string, McpToolResult> }) => {
|
|
195
|
+
const before = recorded.get(mcp.getWorkspaceDir());
|
|
196
|
+
expect(before, 'no item names were recorded').toBeTruthy();
|
|
197
|
+
const result = mcpToolResults.get('_last');
|
|
198
|
+
expect(result, 'no result').toBeTruthy();
|
|
199
|
+
const markdown = (JSON.parse(result!.text) as { data: { markdown?: string } }).data.markdown;
|
|
200
|
+
expect(markdown, 'the result has no markdown').toBeTruthy();
|
|
201
|
+
expect(contextItemNames(markdown!)).toEqual(before);
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
Then(
|
|
206
|
+
'the MCP context result should show the recorded item names',
|
|
207
|
+
{ tags: '@mcp' },
|
|
208
|
+
async ({ mcp, mcpToolResults }: { mcp: McpPort; mcpToolResults: Map<string, McpToolResult> }) => {
|
|
209
|
+
const before = recorded.get(mcp.getWorkspaceDir());
|
|
210
|
+
expect(before, 'no item names were recorded').toBeTruthy();
|
|
211
|
+
const result = mcpToolResults.get('_last');
|
|
212
|
+
expect(result, 'no result').toBeTruthy();
|
|
213
|
+
expect(contextItemNames(result!.text)).toEqual(before);
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/tests/context_support.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{katalyst_plugin_context-0.2.1a5 → katalyst_plugin_context-0.2.1a7}/tests/test_shared_names.py
RENAMED
|
File without changes
|
|
File without changes
|