design-playbook 0.7.0

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.
Files changed (47) hide show
  1. package/LICENSE +28 -0
  2. package/NOTICE +37 -0
  3. package/README.md +143 -0
  4. package/commands/design-io.md +8 -0
  5. package/commands/ui-review.md +8 -0
  6. package/commands/ux-spec.md +8 -0
  7. package/mcp/__init__.py +0 -0
  8. package/mcp/_transport.py +242 -0
  9. package/mcp/evidence/README.md +40 -0
  10. package/mcp/evidence/__init__.py +0 -0
  11. package/mcp/evidence/server.py +450 -0
  12. package/mcp/evidence/test_server_stdio.py +645 -0
  13. package/mcp/preview/__init__.py +0 -0
  14. package/mcp/preview/browser.py +661 -0
  15. package/mcp/preview/confirm.py +255 -0
  16. package/mcp/preview/control.py +1293 -0
  17. package/mcp/preview/i18n.py +162 -0
  18. package/mcp/preview/server.py +126 -0
  19. package/mcp/preview/test_browser_control.py +663 -0
  20. package/mcp/preview/test_server_stdio.py +630 -0
  21. package/mcp/preview/test_transaction.py +436 -0
  22. package/mcp/preview/transaction.py +536 -0
  23. package/mcp/preview/util.py +19 -0
  24. package/mcp/test_transport.py +39 -0
  25. package/package.json +42 -0
  26. package/skills/craft-guard/SKILL.md +59 -0
  27. package/skills/craft-guard/references/craft.md +29 -0
  28. package/skills/craft-guard/references/detectors.md +124 -0
  29. package/skills/design-baseline/SKILL.md +134 -0
  30. package/skills/design-baseline/agents/openai.yaml +4 -0
  31. package/skills/design-baseline/references/design-template.md +73 -0
  32. package/skills/design-baseline/references/extraction-guidance.md +39 -0
  33. package/skills/design-baseline/scripts/design_baseline.py +780 -0
  34. package/skills/design-playbook/SKILL.md +219 -0
  35. package/skills/native-craft/SKILL.md +59 -0
  36. package/skills/native-craft/references/native-feel.md +79 -0
  37. package/skills/reference-intake/SKILL.md +86 -0
  38. package/skills/reference-intake/references/contract-template.md +82 -0
  39. package/skills/ui-evaluator/SKILL.md +110 -0
  40. package/skills/ui-evaluator/references/rubric.md +45 -0
  41. package/skills/ui-picker/SKILL.md +63 -0
  42. package/skills/ui-picker/references/components.md +31 -0
  43. package/skills/ui-picker/references/design.md +21 -0
  44. package/skills/ui-picker/references/domain.md +26 -0
  45. package/skills/ui-picker/references/template.md +24 -0
  46. package/skills/ux-spec/SKILL.md +51 -0
  47. package/skills/ux-spec/references/spec-template.md +43 -0
package/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bandersnatch0x and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ This LICENSE covers only the authored materials in this package (skills,
26
+ commands, plugin metadata, and self-written examples). It does not grant
27
+ rights to any third-party playbook manuscript, figures, or trademarks that may
28
+ exist elsewhere in a parent monorepo for learning purposes.
package/NOTICE ADDED
@@ -0,0 +1,37 @@
1
+ # NOTICE
2
+
3
+ design-playbook is an original agent plugin (skills, commands, plugin metadata,
4
+ workflow docs, and self-authored examples), licensed under MIT (see `LICENSE`).
5
+
6
+ It is **not** a port, overlay, or redistribution of any third-party design
7
+ playbook's manuscript, figures, brand marks, or demo site. No rights to such
8
+ materials are claimed or granted here.
9
+
10
+ The `native-craft` skill is a derivative of yetone/native-feel-skill (MIT). We
11
+ authored our own Design I/O leaf and condensed its decision gate and native
12
+ conventions audit in our own voice; the full depth (WebView survival, IPC
13
+ contract, memory truths, Raycast evidence) remains in the original skill, which
14
+ users may install separately. The MIT License of the original is reproduced
15
+ below in full, as required for derivative works:
16
+
17
+ The MIT License (MIT)
18
+
19
+ Copyright (c) 2026 yetone
20
+
21
+ Permission is hereby granted, free of charge, to any person obtaining a copy
22
+ of this software and associated documentation files (the "Software"), to deal
23
+ in the Software without restriction, including without limitation the rights
24
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25
+ copies of the Software, and to permit persons to whom the Software is
26
+ furnished to do so, subject to the following conditions:
27
+
28
+ The above copyright notice and this permission notice shall be included in all
29
+ copies or substantial portions of the Software.
30
+
31
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # design-playbook
2
+
3
+ Agent plugin: **Design I/O** for product UI (Claude Code / Codex).
4
+
5
+ Declarations + contracts — not a style CSV pack. Compose with [ui-ux-pro-max](https://github.com/nextlevelbuilder/ui-ux-pro-max-skill) and Anthropic `frontend-design` for aesthetics; this package owns pipeline and acceptance.
6
+
7
+ ## Install (Claude Code)
8
+
9
+ Path of record (published) - the marketplace catalog lives at the **repo root**, not in this package:
10
+
11
+ ```text
12
+ /plugin marketplace add <owner>/<repo>
13
+ /plugin install design-playbook@design-playbook
14
+ ```
15
+
16
+ Local dev / self-test:
17
+
18
+ ```bash
19
+ claude --plugin-dir <abs-path>/packages/design-playbook # dev load, no install
20
+ # or local marketplace (point at the repo root, where the catalog lives)
21
+ /plugin marketplace add <abs-path-to-repo-root>
22
+ /plugin install design-playbook@design-playbook
23
+ ```
24
+
25
+ ## Install (Codex)
26
+
27
+ Same GitHub repo / monorepo root catalog. Codex-native manifest lives at `.codex-plugin/` (MCP uses relative paths).
28
+
29
+ ```bash
30
+ codex plugin marketplace add Bandersnatch0x/design-playbook
31
+ codex plugin add design-playbook@design-playbook
32
+ ```
33
+
34
+ Local monorepo:
35
+
36
+ ```bash
37
+ codex plugin marketplace add <abs-path-to-repo-root>
38
+ codex plugin add design-playbook@design-playbook
39
+ ```
40
+
41
+ Details + skills-only fallback: [`codex/AGENTS.md`](codex/AGENTS.md).
42
+
43
+ After install, skills and commands are **namespaced** by the plugin name:
44
+
45
+ | Invoke | Role |
46
+ | --- | --- |
47
+ | `/design-playbook:design-playbook` | Orchestrator skill (model-invoked) |
48
+ | `/design-playbook:design-baseline` | Discover/validate/draft the project `DESIGN.md` baseline |
49
+ | `/design-playbook:reference-intake` | Reference contract skill (screenshot/URL/analogy) |
50
+ | `/design-playbook:ux-spec` | Six-layer spec skill |
51
+ | `/design-playbook:ui-picker` | Shell + components skill |
52
+ | `/design-playbook:craft-guard` | Craft / anti-slop skill |
53
+ | `/design-playbook:native-craft` | Native-feel desktop declaration skill |
54
+ | `/design-playbook:ui-evaluator` | Point-back acceptance skill |
55
+ | `/design-playbook:design-io` | Full pipeline command |
56
+ | `/design-playbook:ux-spec` | Spec-only command |
57
+ | `/design-playbook:ui-review` | Review command |
58
+
59
+ Bare `/design-io` is **not** the installed name — always use the `design-playbook:` prefix.
60
+
61
+ ## Install (pi)
62
+
63
+ Published to npm, listed in the [pi package gallery](https://pi.dev/packages).
64
+
65
+ ```bash
66
+ pi install npm:design-playbook
67
+ ```
68
+
69
+ pi has no plugin namespace — skills are `/skill:<name>`, commands are bare `/<name>`:
70
+
71
+ | Invoke | Role |
72
+ | --- | --- |
73
+ | `/skill:design-playbook` | Orchestrator skill (model-invoked) |
74
+ | `/skill:ux-spec` … `/skill:ui-evaluator` | Same eight skills as above |
75
+ | `/design-io` · `/ux-spec` · `/ui-review` | Pipeline / spec-only / review commands |
76
+
77
+ pi ships no built-in MCP, so `preview*` and `observe*` skip by default (ADR-0009 absent→skip; the pipeline still runs spec → picker → fill → craft → accept). To enable both gates, install an MCP adapter and register the bundled servers in your project `.mcp.json`:
78
+
79
+ ```bash
80
+ pi install npm:pi-mcp-adapter
81
+ ```
82
+
83
+ ```json
84
+ {
85
+ "mcpServers": {
86
+ "design-playbook-preview": {
87
+ "command": "python",
88
+ "args": ["<pkg>/mcp/preview/server.py"],
89
+ "timeout": 3600000
90
+ },
91
+ "design-playbook-evidence": {
92
+ "command": "python",
93
+ "args": ["<pkg>/mcp/evidence/server.py"],
94
+ "env": { "DESIGN_PLAYBOOK_RUN_ROOT": "." },
95
+ "timeout": 3600000
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ `<pkg>` is the installed package root — `~/.pi/agent/npm/node_modules/design-playbook` for a user install, `.pi/npm/node_modules/design-playbook` for a project install. Evidence also needs `pip install playwright && playwright install chromium`.
102
+
103
+ ## Stack with other skills
104
+
105
+ | Package | Use for |
106
+ | --- | --- |
107
+ | **design-playbook** | Baseline? → Reference? → Spec? → plan? → shell → optional preview* → fill → craft → optional observe* → evaluate / recirculate |
108
+ | ui-ux-pro-max | Style / palette / type search |
109
+ | frontend-design | Anti-template visual direction |
110
+
111
+ ## Layout
112
+
113
+ ```text
114
+ .claude-plugin/
115
+ plugin.json ← plugin manifest (the marketplace catalog lives at the repo root)
116
+ .mcp.json ← bundled MCP servers, launched via ${CLAUDE_PLUGIN_ROOT} (ADR-0009)
117
+ mcp/{preview,evidence}/← MCP adapter runtimes (preview_prototype / execute_capture_plan)
118
+ skills/<name>/SKILL.md ← model-invoked skills
119
+ commands/<name>.md ← slash commands (design-io, ux-spec, ui-review only)
120
+ codex/AGENTS.md ← Codex bridge notes
121
+ examples/ ← self-authored onboarding samples
122
+ LICENSE · NOTICE ← authored-only scope
123
+ ```
124
+
125
+ ## What ships
126
+
127
+ Only authored content in this package (skills, pipeline commands, metadata, self-written examples, self-authored bundled MCP adapters). See `NOTICE` and repo ADRs 0003–0006, 0009. Repo-maintainer polish commands live in the monorepo root `.claude/commands/`, not in this package.
128
+
129
+ ## Contract vs enforcement
130
+
131
+ Evidence exists only to satisfy a declared criterion — an observation without a binding to an L6 acceptance item is telemetry, not evidence. Runtime capture is done by external providers; design-playbook owns the binding (manifest) and the verdict (ledger), never the runtime.
132
+
133
+ The Design I/O run is a **declared, host-neutral contract** over plain-Markdown artifacts (`DESIGN.md`, spec, decision report, point-back ledger). Any coding agent that emits that shape can be checked; Claude Code and Codex are adapters over the same artifacts. Generators and bridges remain optional; existing-product UI work must bind a valid/accepted project baseline or record an explicit waiver.
134
+
135
+ Run artifacts land under `.scratch/<run>/` (`design-baseline/`, `plan.md`, `preview/`, `evidence/manifest.jsonl`, `point-back.md`); see the orchestrator skill for what lands when. That is where to look — and manually intervene — when a run stalls.
136
+
137
+ **Bundled MCP (v0.3+):** Preview (`mcp/preview/`) and Evidence (`mcp/evidence/`) runtimes ship inside this package and are registered by `.mcp.json` (`${CLAUDE_PLUGIN_ROOT}`). Sibling monorepo dirs remain compatibility launchers/docs. The orchestrator still **probes** MCP `tools/list` and skips `preview*` / `observe*` when tools are absent. Evidence provider writes artifacts only — never the manifest. **`DESIGN_PLAYBOOK_RUN_ROOT`:** default `"."` in `.mcp.json` is the **MCP process cwd**, not the chat workspace — for a host-app dogfood, set an **absolute** path to `.scratch/<run>/` (see [`mcp/evidence/README.md`](mcp/evidence/README.md)). Capture responses include `written_path` (absolute) so mis-rooted writes are visible without a filesystem search.
138
+
139
+ What is **deterministically enforced** today: plugin install/structure (`scripts/validate.py`) and the run-artifact shape (`scripts/validate_run.py` — L1–L6 present; every top-level L6 item ordered `Given -> When -> Then`; one non-empty four-field evidence ledger row per `L6.<n>` with allowed results; four non-empty finding fields with non-empty source; exactly one explicit `## Verdict` of `Pass` or `Recirculate`; Pass requires every evidence result to be `pass` and exactly one issue-linked `0 blocking` closure per blocking finding; exit 0/`RUN OK`, exit 1/`RUN INVALID`, exit 2/`RUN ERROR`; regression-tested by `tests/test_validate_run.py`, which also validates the showcase artifacts directly; **G5** is a *conditional* preview-confirm gate — enforced only when preview artifacts exist / `--preview-dir` is used; **G6** is a *conditional* evidence-binding gate — enforced only when a ledger `observed` references an `evidence/` artifact / `--evidence-dir` is used; opt-in **strict mode** via `--require-preview` / `--require-evidence` / `--strict`). The `observe*` step probes MCP tool `execute_capture_plan` and is skipped when absent. Everything else in the pipeline is agent-executed craft judgment, not a machine gate.
140
+
141
+ ## Codex
142
+
143
+ See `codex/AGENTS.md`.
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Run Design I/O end-to-end (reference-intake? → spec? → plan? → shell → preview* → fill → craft → accept)
3
+ ---
4
+
5
+ Run skill **design-playbook** in full. Honor each step’s completion criterion before the next. Recirculate blocking evaluator findings to the owning declaration. Entry routing and plan/preview orchestration live in that skill (not here).
6
+
7
+ User request:
8
+ $ARGUMENTS
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Declaration-backed UI review with point-back findings
3
+ ---
4
+
5
+ Run skill **ui-evaluator** (pull craft-guard checks when AI slop/motion/loading is in scope). Output issue/source/fix/severity; blocking first.
6
+
7
+ Scope:
8
+ $ARGUMENTS
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Six-layer spec.md only (stop before UI shell/code)
3
+ ---
4
+
5
+ Run skill **ux-spec** only. Emit complete `spec.md`. Do not pick templates or write UI.
6
+
7
+ Request:
8
+ $ARGUMENTS
File without changes
@@ -0,0 +1,242 @@
1
+ """Shared stdio JSON-RPC framing + single-tool dispatch for the bundled MCP servers.
2
+
3
+ Both the preview and evidence adapters speak the same wire format
4
+ (Content-Length- or newline-delimited JSON-RPC over stdio) and run the same
5
+ JSON-RPC dispatch (initialize / tools/list / tools/call / ping /
6
+ method-not-found). This module owns both once so the two servers keep them in
7
+ lockstep (ADR-0009 bundled layout). Each server runs in its own process, so
8
+ the module-level framing state is per-process and never shared across servers.
9
+
10
+ The one policy that is deliberately per-server is malformed-input recovery,
11
+ expressed as the ``recover_from_malformed`` flag on :func:`serve_stdio`:
12
+ ``read_message`` always raises on a bad frame; preview re-raises it
13
+ (fail-fast — the server ends), while evidence catches it, replies ``-32700``
14
+ / ``-32600``, and keeps serving (fail-soft — one bad client frame cannot abort
15
+ a capture run).
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import sys
21
+ from typing import Any, Callable
22
+
23
+ STDIO_FRAMING_CONTENT_LENGTH = "content-length"
24
+ STDIO_FRAMING_NEWLINE = "newline"
25
+ _stdio_framing: str | None = None
26
+
27
+
28
+ class ToolError(Exception):
29
+ """Recoverable domain error with MCP structured error content."""
30
+
31
+ def __init__(self, message: str, structured_content: dict[str, Any]):
32
+ super().__init__(message)
33
+ self.structured_content = structured_content
34
+
35
+
36
+ def read_message() -> dict[str, Any] | None:
37
+ """Read one Content-Length- or newline-delimited JSON-RPC message.
38
+
39
+ Returns None at EOF. Raises json/unicode/value/EOF errors on a bad
40
+ frame; the caller decides the recovery policy (see module docstring).
41
+ """
42
+ global _stdio_framing
43
+
44
+ while True:
45
+ first_line = sys.stdin.buffer.readline()
46
+ if not first_line:
47
+ return None
48
+ if first_line not in (b"\r\n", b"\n"):
49
+ break
50
+
51
+ if not first_line.lower().startswith(b"content-length:"):
52
+ _stdio_framing = STDIO_FRAMING_NEWLINE
53
+ return json.loads(first_line.decode("utf-8"))
54
+
55
+ _stdio_framing = STDIO_FRAMING_CONTENT_LENGTH
56
+ headers: dict[str, str] = {}
57
+ line = first_line
58
+ while line not in (b"\r\n", b"\n"):
59
+ key, separator, value = line.decode("utf-8").partition(":")
60
+ if not separator:
61
+ raise ValueError(f"invalid MCP stdio header: {line!r}")
62
+ headers[key.strip().lower()] = value.strip()
63
+ line = sys.stdin.buffer.readline()
64
+ if not line:
65
+ raise EOFError("MCP stdio headers ended before the blank line")
66
+ length = int(headers.get("content-length", "0"))
67
+ if length <= 0:
68
+ raise ValueError("MCP stdio Content-Length must be positive")
69
+ body = sys.stdin.buffer.read(length)
70
+ if len(body) != length:
71
+ raise EOFError(
72
+ f"MCP stdio body ended early: expected {length}, got {len(body)}"
73
+ )
74
+ return json.loads(body.decode("utf-8"))
75
+
76
+
77
+ def write_message(payload: dict[str, Any]) -> None:
78
+ """Write one JSON-RPC message in the framing detected by read_message."""
79
+ raw = json.dumps(payload, ensure_ascii=False).encode("utf-8")
80
+ if _stdio_framing == STDIO_FRAMING_NEWLINE:
81
+ sys.stdout.buffer.write(raw + b"\n")
82
+ else:
83
+ sys.stdout.buffer.write(
84
+ f"Content-Length: {len(raw)}\r\n\r\n".encode("ascii") + raw
85
+ )
86
+ sys.stdout.buffer.flush()
87
+
88
+
89
+ def _result_text(payload: dict[str, Any]) -> dict[str, Any]:
90
+ return {
91
+ "content": [
92
+ {
93
+ "type": "text",
94
+ "text": json.dumps(payload, ensure_ascii=False, indent=2),
95
+ }
96
+ ],
97
+ "structuredContent": payload,
98
+ "isError": False,
99
+ }
100
+
101
+
102
+ def _error_result(
103
+ message: str, structured_content: dict[str, Any] | None = None
104
+ ) -> dict[str, Any]:
105
+ result: dict[str, Any] = {
106
+ "content": [{"type": "text", "text": message}],
107
+ "isError": True,
108
+ }
109
+ if structured_content is not None:
110
+ result["structuredContent"] = structured_content
111
+ return result
112
+
113
+
114
+ def _exception_result(exc: Exception) -> dict[str, Any]:
115
+ structured = exc.structured_content if isinstance(exc, ToolError) else None
116
+ return _error_result(str(exc), structured)
117
+
118
+
119
+ def serve_stdio(
120
+ server_name: str,
121
+ server_version: str,
122
+ tool_schema: dict[str, Any],
123
+ handle_tool: Callable[[dict[str, Any]], dict[str, Any]],
124
+ *,
125
+ recover_from_malformed: bool) -> None:
126
+ """Run the shared single-tool MCP stdio dispatch loop (ADR-0009).
127
+
128
+ Both bundled adapters speak the same JSON-RPC protocol; owning the
129
+ dispatch once here keeps initialize / tools/list / tools/call / ping /
130
+ method-not-found in lockstep instead of copy-pasted in each server.
131
+
132
+ ``tool_schema`` is the single advertised tool (its ``name`` is the
133
+ accepted ``tools/call`` name); ``handle_tool`` maps the call's
134
+ ``arguments`` to the payload returned to the client. A raised exception
135
+ becomes a tool-level error result (``isError: true``); a returned dict
136
+ is the structured success payload.
137
+
138
+ The one deliberately per-server policy is malformed-input recovery (see
139
+ module docstring):
140
+
141
+ * ``recover_from_malformed=False`` (preview): a bad frame or a
142
+ non-object request propagates and ends the server (fail-fast).
143
+ * ``recover_from_malformed=True`` (evidence): reply ``-32700`` (bad
144
+ frame) or ``-32600`` (non-object request) and keep serving, so one
145
+ bad client frame cannot abort a capture run.
146
+ """
147
+ print(f"{server_name} MCP server starting (stdio)",
148
+ file=sys.stderr, flush=True)
149
+ while True:
150
+ try:
151
+ msg = read_message()
152
+ except (json.JSONDecodeError, UnicodeDecodeError,
153
+ ValueError, EOFError) as exc:
154
+ if not recover_from_malformed:
155
+ raise
156
+ print(f"MCP parse error: {exc}", file=sys.stderr, flush=True)
157
+ write_message({
158
+ "jsonrpc": "2.0",
159
+ "id": None,
160
+ "error": {"code": -32700, "message": f"Parse error: {exc}"},
161
+ })
162
+ continue
163
+ if msg is None:
164
+ break
165
+ if recover_from_malformed and not isinstance(msg, dict):
166
+ write_message({
167
+ "jsonrpc": "2.0",
168
+ "id": None,
169
+ "error": {"code": -32600, "message": "Invalid Request"},
170
+ })
171
+ continue
172
+
173
+ method = msg.get("method")
174
+ msg_id = msg.get("id")
175
+ params = msg.get("params") or {}
176
+
177
+ if method == "initialize":
178
+ write_message({
179
+ "jsonrpc": "2.0",
180
+ "id": msg_id,
181
+ "result": {
182
+ "protocolVersion": params.get(
183
+ "protocolVersion", "2024-11-05"),
184
+ "capabilities": {"tools": {}},
185
+ "serverInfo": {
186
+ "name": server_name,
187
+ "version": server_version,
188
+ },
189
+ },
190
+ })
191
+ continue
192
+
193
+ if method == "notifications/initialized":
194
+ continue
195
+
196
+ if method == "tools/list":
197
+ write_message({
198
+ "jsonrpc": "2.0",
199
+ "id": msg_id,
200
+ "result": {"tools": [tool_schema]},
201
+ })
202
+ continue
203
+
204
+ if method == "tools/call":
205
+ name = params.get("name")
206
+ arguments = params.get("arguments") or {}
207
+ if name != tool_schema["name"]:
208
+ write_message({
209
+ "jsonrpc": "2.0",
210
+ "id": msg_id,
211
+ "result": _error_result(f"unknown tool: {name}"),
212
+ })
213
+ continue
214
+ try:
215
+ payload = handle_tool(arguments)
216
+ write_message({
217
+ "jsonrpc": "2.0",
218
+ "id": msg_id,
219
+ "result": _result_text(payload),
220
+ })
221
+ except Exception as exc: # noqa: BLE001 — return to client
222
+ print(f"tools/call error: {exc}", file=sys.stderr, flush=True)
223
+ write_message({
224
+ "jsonrpc": "2.0",
225
+ "id": msg_id,
226
+ "result": _exception_result(exc),
227
+ })
228
+ continue
229
+
230
+ if method == "ping":
231
+ write_message({"jsonrpc": "2.0", "id": msg_id, "result": {}})
232
+ continue
233
+
234
+ if msg_id is not None:
235
+ write_message({
236
+ "jsonrpc": "2.0",
237
+ "id": msg_id,
238
+ "error": {
239
+ "code": -32601,
240
+ "message": f"Method not found: {method}",
241
+ },
242
+ })
@@ -0,0 +1,40 @@
1
+ # Evidence MCP adapter (`execute_capture_plan`)
2
+
3
+ Runtime for the optional **observe\*** step. Writes capture artifacts only — **never** `manifest.jsonl`, never judges L6.
4
+
5
+ ## `DESIGN_PLAYBOOK_RUN_ROOT`
6
+
7
+ | Setting | Meaning |
8
+ | --- | --- |
9
+ | Unset | Artifact paths resolve under the **MCP process cwd** |
10
+ | `"."` (default in package `.mcp.json`) | Same — relative to process cwd, **not** the chat workspace root |
11
+ | Absolute path | Preferred for cross-repo dogfood: set to the run root (e.g. `D:/…/nmg_bup_h5/.scratch/playbook-smoke/<run>`) so `evidence/L6.*.png` lands next to `manifest.jsonl` |
12
+
13
+ Relative values are resolved with `Path(value).resolve()` at process start semantics (cwd-relative). If captures appear under the plugin monorepo instead of the host run, check cwd and this env — the tool also returns **`written_path`** (absolute) so mis-roots are obvious without a filesystem search.
14
+
15
+ Example (host run):
16
+
17
+ ```json
18
+ "env": {
19
+ "DESIGN_PLAYBOOK_RUN_ROOT": "D:/code_space/app/.scratch/my-run"
20
+ }
21
+ ```
22
+
23
+ Plugin auto-load: [`../.mcp.json`](../.mcp.json) (under `packages/design-playbook/`).
24
+ Codex / manual: sibling [`mcp.example.toml`](../../../design-playbook-evidence/mcp.example.toml).
25
+
26
+ ## Return shape
27
+
28
+ `artifact` (run-root-relative) · `observed_state` (from page `data-state`, else `unknown`) · `result` · `error` · **`written_path`** (absolute).
29
+
30
+ Orchestrator bind rules (verbatim `observed_state`, per-capture append, mirror surface notes): `skills/design-playbook/SKILL.md` step 8.
31
+
32
+ ## Mirror pages and `data-state`
33
+
34
+ When capture uses a semantic mirror (not the live Fill host), set a root marker the provider can read:
35
+
36
+ ```html
37
+ <body data-state="error">…</body>
38
+ ```
39
+
40
+ `observed_state` comes only from that probe (else `unknown`). Do not overwrite it in the manifest with the request's `state` field.
File without changes