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.
- package/LICENSE +28 -0
- package/NOTICE +37 -0
- package/README.md +143 -0
- package/commands/design-io.md +8 -0
- package/commands/ui-review.md +8 -0
- package/commands/ux-spec.md +8 -0
- package/mcp/__init__.py +0 -0
- package/mcp/_transport.py +242 -0
- package/mcp/evidence/README.md +40 -0
- package/mcp/evidence/__init__.py +0 -0
- package/mcp/evidence/server.py +450 -0
- package/mcp/evidence/test_server_stdio.py +645 -0
- package/mcp/preview/__init__.py +0 -0
- package/mcp/preview/browser.py +661 -0
- package/mcp/preview/confirm.py +255 -0
- package/mcp/preview/control.py +1293 -0
- package/mcp/preview/i18n.py +162 -0
- package/mcp/preview/server.py +126 -0
- package/mcp/preview/test_browser_control.py +663 -0
- package/mcp/preview/test_server_stdio.py +630 -0
- package/mcp/preview/test_transaction.py +436 -0
- package/mcp/preview/transaction.py +536 -0
- package/mcp/preview/util.py +19 -0
- package/mcp/test_transport.py +39 -0
- package/package.json +42 -0
- package/skills/craft-guard/SKILL.md +59 -0
- package/skills/craft-guard/references/craft.md +29 -0
- package/skills/craft-guard/references/detectors.md +124 -0
- package/skills/design-baseline/SKILL.md +134 -0
- package/skills/design-baseline/agents/openai.yaml +4 -0
- package/skills/design-baseline/references/design-template.md +73 -0
- package/skills/design-baseline/references/extraction-guidance.md +39 -0
- package/skills/design-baseline/scripts/design_baseline.py +780 -0
- package/skills/design-playbook/SKILL.md +219 -0
- package/skills/native-craft/SKILL.md +59 -0
- package/skills/native-craft/references/native-feel.md +79 -0
- package/skills/reference-intake/SKILL.md +86 -0
- package/skills/reference-intake/references/contract-template.md +82 -0
- package/skills/ui-evaluator/SKILL.md +110 -0
- package/skills/ui-evaluator/references/rubric.md +45 -0
- package/skills/ui-picker/SKILL.md +63 -0
- package/skills/ui-picker/references/components.md +31 -0
- package/skills/ui-picker/references/design.md +21 -0
- package/skills/ui-picker/references/domain.md +26 -0
- package/skills/ui-picker/references/template.md +24 -0
- package/skills/ux-spec/SKILL.md +51 -0
- package/skills/ux-spec/references/spec-template.md +43 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""i18n for the preview adapter UI (ADR: CJK-first, en supported).
|
|
2
|
+
|
|
3
|
+
Locale source: env DPB_PREVIEW_LANG (or LANG), default "zh-CN". Only zh-CN and
|
|
4
|
+
en are provided; unknown locales fall back to zh-CN.
|
|
5
|
+
|
|
6
|
+
CONFIRM/revise labels are locale-aware: DEFAULT_OPTIONS returns the options in
|
|
7
|
+
the active locale, and CONFIRM_LABELS is the union across locales so the
|
|
8
|
+
backend still recognises a confirm regardless of UI language.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
ZH = "zh-CN"
|
|
14
|
+
EN = "en"
|
|
15
|
+
|
|
16
|
+
_STRINGS: dict[str, dict[str, str]] = {
|
|
17
|
+
ZH: {
|
|
18
|
+
"region_label": "预览确认",
|
|
19
|
+
"round_n": "第 {n} 轮",
|
|
20
|
+
"annotate": "批注",
|
|
21
|
+
"pill_open": "评审确认",
|
|
22
|
+
"ready": "可确认",
|
|
23
|
+
"not_ready": "写意见或批注后再确认",
|
|
24
|
+
"drawer_aria": "批注与确认",
|
|
25
|
+
"collapse": "收起",
|
|
26
|
+
"pin_toggle": "点选批注",
|
|
27
|
+
"pin_count": "已选 {n} 处",
|
|
28
|
+
"pin_on": "点选中 · 再点关闭",
|
|
29
|
+
"pin_off": "点选批注",
|
|
30
|
+
"anchor_num_pre": "锚点 ",
|
|
31
|
+
"anchor_num_post": " 的修改意见",
|
|
32
|
+
"anchor_placeholder": "这条要改什么",
|
|
33
|
+
"remove_num_pre": "移除锚点 ",
|
|
34
|
+
"pin_count_pre": "已选 ",
|
|
35
|
+
"pin_count_post": " 处",
|
|
36
|
+
# JS-side dynamic anchors (split pre/post because idx is a JS value):
|
|
37
|
+
"anchors_head": "选中批注",
|
|
38
|
+
"anchors_empty": "尚未选中元素。点上方「点选批注」后,再点页面上的元素。",
|
|
39
|
+
"field_label": "整体批注",
|
|
40
|
+
"field_hint": "请写整体意见或点选元素加批注后确认;空反馈会被拦下(ADR-0008)",
|
|
41
|
+
"field_placeholder": "对整页的总意见;页面元素的局部问题用「点选批注」",
|
|
42
|
+
"close": "关闭",
|
|
43
|
+
"terminate": "终止评审",
|
|
44
|
+
"terminate_confirm": "再点一次确认终止",
|
|
45
|
+
"terminate_desc": "终止整次预览会话(不是通过/修改);需再点一次确认",
|
|
46
|
+
"abort_cancelled": "已取消",
|
|
47
|
+
"confirm_confirm": "再点一次确认通过",
|
|
48
|
+
"confirm_cancelled": "已取消确认",
|
|
49
|
+
"draft": "保留批注,暂不决定",
|
|
50
|
+
"draft_desc": "关闭抽屉并保留批注,不提交本轮决定",
|
|
51
|
+
"locate": "定位到该元素",
|
|
52
|
+
"locate_anchor": "定位到该元素",
|
|
53
|
+
"remove": "移除",
|
|
54
|
+
"selected_n": "已选 {n} 处",
|
|
55
|
+
"anchor_note_label": "锚点批注 ({n}):",
|
|
56
|
+
"no_text": "(无文字)",
|
|
57
|
+
"done_title": "已记录",
|
|
58
|
+
"done_body": "窗口即将自动关闭。",
|
|
59
|
+
"confirm": "确认通过",
|
|
60
|
+
"confirm_desc": "确认本轮通过并进入实现(Fill);需有批注或整体意见",
|
|
61
|
+
"revise": "需要修改",
|
|
62
|
+
"revise_desc": "提交修改意见,进入下一轮原型",
|
|
63
|
+
"floor_failure_empty": "confirm with no substantive feedback: empty feedback and no anchor with a non-empty comment",
|
|
64
|
+
"floor_failure_selector": "anchor missing non-empty selector and comment: selector={sel!r} comment={note!r}",
|
|
65
|
+
"log_anchor_missing": "锚点 #{n} 缺 selector 或 comment",
|
|
66
|
+
"self_check_passed": "FLOOR SELF-CHECK PASSED",
|
|
67
|
+
},
|
|
68
|
+
EN: {
|
|
69
|
+
"region_label": "Preview confirm",
|
|
70
|
+
"round_n": "Round {n}",
|
|
71
|
+
"annotate": "Annotate",
|
|
72
|
+
"pill_open": "Review & confirm",
|
|
73
|
+
"ready": "Ready",
|
|
74
|
+
"not_ready": "Add notes or pin to confirm",
|
|
75
|
+
"drawer_aria": "Annotate & confirm",
|
|
76
|
+
"collapse": "Collapse",
|
|
77
|
+
"pin_toggle": "Pick to annotate",
|
|
78
|
+
"pin_count": "{n} selected",
|
|
79
|
+
"pin_on": "Picking · click again to close",
|
|
80
|
+
"pin_off": "Pick to annotate",
|
|
81
|
+
"anchor_num_pre": "Anchor ",
|
|
82
|
+
"anchor_num_post": " revision note",
|
|
83
|
+
"anchor_placeholder": "What to change here",
|
|
84
|
+
"remove_num_pre": "Remove anchor ",
|
|
85
|
+
"pin_count_pre": "",
|
|
86
|
+
"pin_count_post": " selected",
|
|
87
|
+
"anchors_head": "Selected annotations",
|
|
88
|
+
"anchors_empty": "No element selected. Click \"Pick to annotate\" above, then click an element on the page.",
|
|
89
|
+
"field_label": "Overall feedback",
|
|
90
|
+
"field_hint": "Write overall notes or pick an element to annotate before confirming; empty feedback is blocked (ADR-0008)",
|
|
91
|
+
"field_placeholder": "Overall notes for the page; for a specific element use \"Pick to annotate\"",
|
|
92
|
+
"close": "Close",
|
|
93
|
+
"terminate": "Abort preview",
|
|
94
|
+
"terminate_confirm": "Click again to abort",
|
|
95
|
+
"terminate_desc": "Stops the whole preview session (not pass/fail); click again to confirm",
|
|
96
|
+
"abort_cancelled": "Cancelled",
|
|
97
|
+
"confirm_confirm": "Click again to confirm",
|
|
98
|
+
"confirm_cancelled": "Confirm cancelled",
|
|
99
|
+
"draft": "Keep notes, decide later",
|
|
100
|
+
"draft_desc": "Close drawer and keep notes; do not submit a decision yet",
|
|
101
|
+
"locate": "Locate this element",
|
|
102
|
+
"locate_anchor": "Locate this element",
|
|
103
|
+
"anchor_aria": "Revision note for anchor \"{label}\"",
|
|
104
|
+
"remove": "Remove",
|
|
105
|
+
"selected_n": "{n} selected",
|
|
106
|
+
"anchor_note_label": "Anchor notes ({n}):",
|
|
107
|
+
"no_text": "(no text)",
|
|
108
|
+
"done_title": "Recorded",
|
|
109
|
+
"done_body": "This window will close shortly.",
|
|
110
|
+
"confirm": "Confirm",
|
|
111
|
+
"confirm_desc": "Approve this round and proceed to Fill; needs notes or annotations",
|
|
112
|
+
"revise": "Needs changes",
|
|
113
|
+
"revise_desc": "Submit revision notes and open the next prototype round",
|
|
114
|
+
"floor_failure_empty": "confirm with no substantive feedback: empty feedback and no anchor with a non-empty comment",
|
|
115
|
+
"floor_failure_selector": "anchor missing non-empty selector and comment: selector={sel!r} comment={note!r}",
|
|
116
|
+
"log_anchor_missing": "anchor #{n} missing selector or comment",
|
|
117
|
+
"self_check_passed": "FLOOR SELF-CHECK PASSED",
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def lang() -> str:
|
|
123
|
+
"""Active locale (zh-CN or en). Env DPB_PREVIEW_LANG, then LANG, default zh-CN."""
|
|
124
|
+
raw = os.environ.get("DPB_PREVIEW_LANG") or os.environ.get("LANG") or ZH
|
|
125
|
+
low = raw.replace("_", "-").lower()
|
|
126
|
+
if low.startswith("en"):
|
|
127
|
+
return EN
|
|
128
|
+
return ZH
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def t(key: str, **kw: object) -> str:
|
|
132
|
+
"""Translate a key in the active locale, with {kw} interpolation."""
|
|
133
|
+
table = _STRINGS[lang()]
|
|
134
|
+
val = table.get(key)
|
|
135
|
+
if val is None:
|
|
136
|
+
return key
|
|
137
|
+
if kw:
|
|
138
|
+
try:
|
|
139
|
+
return val.format(**kw)
|
|
140
|
+
except (KeyError, IndexError):
|
|
141
|
+
return val
|
|
142
|
+
return val
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def default_options() -> list[str]:
|
|
146
|
+
"""[confirm_label, revise_label] in the active locale."""
|
|
147
|
+
return [t("confirm"), t("revise")]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# Union of confirm labels across ALL locales - backend must recognise a confirm
|
|
151
|
+
# regardless of which locale the UI rendered in.
|
|
152
|
+
CONFIRM_LABELS: set[str] = {
|
|
153
|
+
_STRINGS[ZH]["confirm"], _STRINGS[EN]["confirm"],
|
|
154
|
+
"confirm", "confirmed", "pass", "ok",
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# Union of revise labels across ALL locales - frontend must classify a revise
|
|
158
|
+
# regardless of which locale the UI rendered in.
|
|
159
|
+
REVISE_LABELS: set[str] = {
|
|
160
|
+
_STRINGS[ZH]["revise"], _STRINGS[EN]["revise"],
|
|
161
|
+
"revise", "needs changes", "needs-changes",
|
|
162
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Minimal stdio MCP server: single tool ``preview_prototype``.
|
|
3
|
+
|
|
4
|
+
Entry point only: tool schema + handler. JSON-RPC stdio framing lives in
|
|
5
|
+
``mcp/_transport.py``; browser HTTP collection lives in ``browser.py``;
|
|
6
|
+
decision authority and persistence live in ``transaction.py``. Control,
|
|
7
|
+
confirm, utility, and locale details stay in their sibling modules. No
|
|
8
|
+
third-party deps.
|
|
9
|
+
|
|
10
|
+
Run (plugin-bundled MCP config uses ${CLAUDE_PLUGIN_ROOT}):
|
|
11
|
+
{ "command": "python", "args": ["<plugin>/mcp/preview/server.py"] }
|
|
12
|
+
Compatibility launcher remains at packages/design-playbook-preview/server.py.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
# Shared stdio JSON-RPC framing + single-tool dispatch live one level up in
|
|
21
|
+
# mcp/_transport.py (both bundled adapters speak the same wire format and
|
|
22
|
+
# run the same JSON-RPC protocol; ADR-0009).
|
|
23
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
24
|
+
from _transport import ToolError, serve_stdio # noqa: E402
|
|
25
|
+
|
|
26
|
+
import browser
|
|
27
|
+
from confirm import _self_check_floor
|
|
28
|
+
from i18n import default_options
|
|
29
|
+
from transaction import PreviewTransactionError, run_preview_transaction
|
|
30
|
+
|
|
31
|
+
TOOL_NAME = "preview_prototype"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _tool_schema() -> dict[str, Any]:
|
|
36
|
+
return {
|
|
37
|
+
"name": TOOL_NAME,
|
|
38
|
+
"description": (
|
|
39
|
+
"Show a disposable HTML prototype in a centered app window, collect "
|
|
40
|
+
"user confirm/revise feedback, and write preview confirm records."
|
|
41
|
+
),
|
|
42
|
+
"inputSchema": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"properties": {
|
|
45
|
+
"path": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"description": "Absolute path to prototype HTML (preferred).",
|
|
48
|
+
},
|
|
49
|
+
"html": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"description": "Inline full-page HTML when path is absent.",
|
|
52
|
+
},
|
|
53
|
+
"summary": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"description": "Decision-report summary / change note.",
|
|
56
|
+
},
|
|
57
|
+
"round": {
|
|
58
|
+
"type": "integer",
|
|
59
|
+
"description": "Loop round number; first round = 1.",
|
|
60
|
+
},
|
|
61
|
+
"report_ref": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"description": "Path or version id of the decision report.",
|
|
64
|
+
},
|
|
65
|
+
"options": {
|
|
66
|
+
"type": "array",
|
|
67
|
+
"items": {"type": "string"},
|
|
68
|
+
"description": (
|
|
69
|
+
"Confirm/revise labels. Omit to use the adapter locale's "
|
|
70
|
+
"defaults; known confirm/revise labels are rendered in "
|
|
71
|
+
"the adapter locale either way."
|
|
72
|
+
),
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
"required": ["summary", "round", "report_ref"],
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def handle_preview_prototype(args: dict[str, Any]) -> dict[str, Any]:
|
|
82
|
+
path_arg = args.get("path")
|
|
83
|
+
html = args.get("html")
|
|
84
|
+
summary = args.get("summary")
|
|
85
|
+
round_n = args.get("round")
|
|
86
|
+
report_ref = args.get("report_ref")
|
|
87
|
+
options = args.get("options") or default_options()
|
|
88
|
+
|
|
89
|
+
if not isinstance(summary, str) or not summary.strip():
|
|
90
|
+
raise ValueError("summary is required")
|
|
91
|
+
if not isinstance(round_n, int) or isinstance(round_n, bool) or round_n < 1:
|
|
92
|
+
raise ValueError("round must be a positive integer")
|
|
93
|
+
if not isinstance(report_ref, str) or not report_ref.strip():
|
|
94
|
+
raise ValueError("report_ref is required")
|
|
95
|
+
if path_arg is not None and not isinstance(path_arg, str):
|
|
96
|
+
raise ValueError("path must be a string")
|
|
97
|
+
if html is not None and not isinstance(html, str):
|
|
98
|
+
raise ValueError("html must be a string")
|
|
99
|
+
if not isinstance(options, list) or not all(isinstance(o, str) for o in options):
|
|
100
|
+
raise ValueError("options must be string[]")
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
return run_preview_transaction(
|
|
104
|
+
path_arg=path_arg,
|
|
105
|
+
html=html,
|
|
106
|
+
summary=summary,
|
|
107
|
+
round_n=round_n,
|
|
108
|
+
report_ref=report_ref,
|
|
109
|
+
options=options,
|
|
110
|
+
collect=browser._collect_via_browser,
|
|
111
|
+
)
|
|
112
|
+
except PreviewTransactionError as exc:
|
|
113
|
+
raise ToolError(str(exc), exc.details) from exc
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
if len(sys.argv) > 1 and sys.argv[1] == "--self-check":
|
|
118
|
+
_self_check_floor()
|
|
119
|
+
else:
|
|
120
|
+
serve_stdio(
|
|
121
|
+
"design-playbook-preview",
|
|
122
|
+
"0.1.0",
|
|
123
|
+
_tool_schema(),
|
|
124
|
+
handle_preview_prototype,
|
|
125
|
+
recover_from_malformed=False,
|
|
126
|
+
)
|