design-playbook 0.21.1 → 0.22.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/README.md +15 -4
- package/codex/AGENTS.md +11 -13
- package/commands/doctor.md +2 -0
- package/commands/run-handoff.md +27 -0
- package/commands/run-status.md +4 -2
- package/commands/ui-review.md +1 -1
- package/examples/dogfood/run/craft-guard.md +9 -0
- package/examples/export-entry/run/craft-guard.md +9 -0
- package/examples/export-pointfix-upgrade/run/craft-guard.md +9 -0
- package/examples/export-upgrade/run/craft-guard.md +9 -0
- package/lib/index.js +7 -6
- package/mcp/evidence/capture_runtime.py +9 -9
- package/mcp/evidence/disclosure.py +4 -2
- package/mcp/evidence/handoff.py +113 -29
- package/mcp/evidence/handoff_i18n.py +103 -0
- package/mcp/evidence/static_handoff_page.html +25 -22
- package/mcp/evidence/test_handoff.py +113 -4
- package/mcp/evidence/test_handoff_i18n.py +115 -0
- package/mcp/preview/control.css +17 -14
- package/mcp/preview/control.html +58 -55
- package/mcp/preview/control.js +37 -17
- package/mcp/preview/control.py +25 -143
- package/mcp/preview/control.review.js +91 -31
- package/mcp/preview/i18n.py +32 -12
- package/mcp/preview/integrity.py +27 -19
- package/mcp/preview/review_session.py +7 -6
- package/mcp/preview/test_browser_control.py +6 -3
- package/mcp/preview/test_i18n_labels.py +3 -0
- package/mcp/preview/test_integrity.py +29 -0
- package/mcp/preview/test_preview_usability.py +247 -0
- package/mcp/run_console/actions.py +31 -4
- package/mcp/run_console/app.css +73 -1
- package/mcp/run_console/app.html +1 -0
- package/mcp/run_console/app.js +526 -8
- package/mcp/run_console/http_server.py +10 -0
- package/mcp/run_console/projection.py +35 -5
- package/mcp/run_console/repair_packet.py +379 -0
- package/mcp/run_console/snapshot_builder.py +8 -1
- package/mcp/run_console/test_actions.py +52 -9
- package/mcp/run_console/test_parity.py +84 -0
- package/mcp/run_console/test_read_only_trial.py +3 -1
- package/mcp/run_console/test_repair_packet.py +550 -0
- package/mcp/run_console/test_snapshot_builder.py +118 -0
- package/mcp/run_console/test_ui_browser.py +17 -8
- package/mcp/ui_locale.py +20 -0
- package/package.json +1 -1
- package/scripts/adapter_matrix.py +9 -2
- package/scripts/adapter_templates/codex-agents.md +10 -12
- package/scripts/capability_receipt.py +291 -0
- package/scripts/generate_adapter.py +122 -88
- package/scripts/rules_registry.py +6 -3
- package/scripts/run_continuation.py +462 -0
- package/scripts/run_facts.py +35 -15
- package/scripts/run_handoff.py +257 -0
- package/scripts/run_profile.py +3 -3
- package/scripts/run_status.py +17 -2
- package/scripts/status_projection.py +21 -7
- package/skills/craft-guard/references/craft.md +2 -0
- package/skills/design-playbook/SKILL.md +22 -6
- package/skills/design-playbook/references/rules.md +211 -0
- package/skills/ui-evaluator/SKILL.md +5 -3
- package/skills/ui-picker/SKILL.md +2 -0
- package/skills/ux-spec/SKILL.md +3 -3
package/README.md
CHANGED
|
@@ -56,6 +56,9 @@ After install, skills and commands are **namespaced** by the plugin name:
|
|
|
56
56
|
| `/design-playbook:ux-spec` | Spec-only command |
|
|
57
57
|
| `/design-playbook:ui-review` | Review command |
|
|
58
58
|
| `/design-playbook:run-review` | Cross-run review command |
|
|
59
|
+
| `/design-playbook:run-status` | Run phase, blocker, and resume/continuation narration |
|
|
60
|
+
| `/design-playbook:run-handoff` | Static delivery package for one reviewed run |
|
|
61
|
+
| `/design-playbook:doctor` | Install health diagnosis |
|
|
59
62
|
|
|
60
63
|
Bare `/design-io` is **not** the installed name — always use the `design-playbook:` prefix.
|
|
61
64
|
|
|
@@ -73,7 +76,7 @@ pi has no plugin namespace — skills are `/skill:<name>`, commands are bare `/<
|
|
|
73
76
|
| --- | --- |
|
|
74
77
|
| `/skill:design-playbook` | Orchestrator skill (model-invoked) |
|
|
75
78
|
| `/skill:ux-spec` … `/skill:ui-evaluator` | Same eight skills as above |
|
|
76
|
-
| `/design-io` · `/ux-spec` · `/ui-review` · `/run-review` | Pipeline / spec-only / review / cross-run commands |
|
|
79
|
+
| `/design-io` · `/ux-spec` · `/ui-review` · `/run-review` · `/run-status` · `/run-handoff` · `/doctor` | Pipeline / spec-only / review / cross-run / status / handoff / health commands |
|
|
77
80
|
|
|
78
81
|
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`:
|
|
79
82
|
|
|
@@ -128,7 +131,7 @@ See the root [README](../../README.md#install-on-other-agents) for the tier tabl
|
|
|
128
131
|
.mcp.json ← bundled MCP servers, launched via ${CLAUDE_PLUGIN_ROOT} (ADR-0009)
|
|
129
132
|
mcp/{preview,evidence}/← MCP adapter runtimes (preview_prototype / execute_capture_plan)
|
|
130
133
|
skills/<name>/SKILL.md ← model-invoked skills
|
|
131
|
-
commands/<name>.md ← slash commands (design-io, ux-spec, ui-review, run-review)
|
|
134
|
+
commands/<name>.md ← slash commands (design-io, ux-spec, ui-review, run-review, run-status, run-handoff, doctor)
|
|
132
135
|
codex/AGENTS.md ← Codex bridge notes
|
|
133
136
|
examples/ ← self-authored onboarding samples
|
|
134
137
|
LICENSE · NOTICE ← authored-only scope
|
|
@@ -154,7 +157,15 @@ python <pkg>/scripts/run_status.py .scratch/<run> --json # machine-readable
|
|
|
154
157
|
python <pkg>/scripts/run_status.py --list # newest runs under .scratch/
|
|
155
158
|
```
|
|
156
159
|
|
|
157
|
-
The status command reuses the packaged validator’s G5 confirm rules. It is part of the installed package — not monorepo-only tooling.
|
|
160
|
+
The status command reuses the packaged validator’s G5 confirm rules. It is part of the installed package — not monorepo-only tooling. For an eligible run it also reports an explicit `open-console` continuation command for the local Run Console (it never starts a server itself), with the blocking reason and a safe fallback when the run or Console prerequisites are ineligible. The Console’s current claim is **local, experimental, and trial-gated** — `run-status --json` reports the same capability receipt (`publicClaim: experimental`) — and no authorized external trial or public release is claimed until the separately authorized read-only trial gate passes (ADR-0043).
|
|
161
|
+
|
|
162
|
+
### Static handoff
|
|
163
|
+
|
|
164
|
+
```text
|
|
165
|
+
python <pkg>/scripts/run_handoff.py <run> [--fill <declared-path>] [--round N] [--json]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Builds the existing static delivery package for one explicit run from its declared `fill:` path. Multiple declarations need an explicit `--fill`; a missing declaration fails with repair guidance instead of guessing. `verdict` / `authority` / `confirmationSource` are reported verbatim — a `Pending` handoff stays `Pending` and never becomes acceptance.
|
|
158
169
|
|
|
159
170
|
### Doctor
|
|
160
171
|
|
|
@@ -163,7 +174,7 @@ python <pkg>/scripts/doctor.py
|
|
|
163
174
|
python <pkg>/scripts/doctor.py --json
|
|
164
175
|
```
|
|
165
176
|
|
|
166
|
-
One packaged diagnosis for interpreter, package surface, optional Playwright, and run-root configuration. Distinguishes `ok` / `degraded` / `broken` with repair actions.
|
|
177
|
+
One packaged diagnosis for interpreter, package surface, optional Playwright, and run-root configuration. Distinguishes `ok` / `degraded` / `broken` with repair actions. Those three states describe **install and runtime health** of what is present locally — they are not a public capability-maturity verdict; maturity vocabulary (`stable` / `experimental` / `blocked-by-gate` / `not-shipped`) stays with the `run-status` capability receipt, and doctor reads existing facts rather than adding a new health or capability-state authority.
|
|
167
178
|
|
|
168
179
|
**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.
|
|
169
180
|
|
package/codex/AGENTS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- generated-by design-playbook v0.
|
|
1
|
+
<!-- generated-by design-playbook v0.22.0 -->
|
|
2
2
|
# design-playbook for Codex
|
|
3
3
|
|
|
4
4
|
## Install (path of record)
|
|
@@ -58,25 +58,23 @@ args = ["<abs>/packages/design-playbook/mcp/evidence/server.py"]
|
|
|
58
58
|
Verify: `codex mcp list` should list both. `preview*` needs a system Edge/Chrome (the
|
|
59
59
|
adapter spawns it via `--app=`); `observe*` needs Playwright + Chromium.
|
|
60
60
|
|
|
61
|
-
> **`preview*`
|
|
62
|
-
>
|
|
63
|
-
>
|
|
61
|
+
> **`preview*` skips when `preview_prototype` is absent; G5 is not triggered.**
|
|
62
|
+
> Follow the orchestrator's skip narration to report the reason and enable path.
|
|
63
|
+
> This is designed skip behaviour, not a crash. Confirm the tool is registered
|
|
64
64
|
> (`codex mcp list`) before treating it as a preview failure. Codex end-to-end preview
|
|
65
65
|
> smoke is not yet validated (v0.4.4 deferred the codex E2E smoke; only evidence/G6 was
|
|
66
66
|
> server-level smoked).
|
|
67
67
|
|
|
68
68
|
## Load order
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
Read `skills/design-playbook/SKILL.md` first. Its **Run profile** and **Steps**
|
|
71
|
+
sections are the sole authority for stage order, conditional entry, and completion
|
|
72
|
+
criteria. Consume the `run_profile.py route` result as directed there; this bridge
|
|
73
|
+
does not define a second route or a host-specific sequence.
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
Conditional entry `reference-intake?` (screenshot/URL/design/product analogy, ADR-0011) runs **before** `ux-spec?` when the router returns `requires_reference_contract` — fixed orchestrator order, not reorderable. Run `native-craft` only for an explicit native-desktop/native-feel target. Web and mobile Web skip `native-craft`; if the platform is unclear, ask before choosing the order. The orchestrator owns the decision gate, render-surface seam handoff, and fail-closed behavior.
|
|
78
|
-
|
|
79
|
-
Mirror the orchestrator's skip narration (SKILL.md Steps preamble): when a step is skipped, output one line — step name + reason + how to enable, with the gate label when one applies, e.g. `-> preview*: adapter absent, skipped (G5 not triggered; enable via packages/design-playbook/mcp/preview/ or host MCP)`.
|
|
75
|
+
For optional adapters, follow the orchestrator's **Steps** preamble and
|
|
76
|
+
`skills/design-playbook/references/load-map.md` pointers. Use its one-line skip
|
|
77
|
+
narration when a stage does not run, including the reason and enable path.
|
|
80
78
|
|
|
81
79
|
Audit preferences (ADR-0033) apply identically on Codex. Follow `skills/design-playbook/SKILL.md` § *Audit preferences* as sole authority; this bridge adds no host-specific preference rules.
|
|
82
80
|
|
package/commands/doctor.md
CHANGED
|
@@ -14,3 +14,5 @@ python <plugin>/scripts/doctor.py --repo-root <target-repo>
|
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
Reports `ok` / `degraded` / `broken`. Failed checks include a concrete repair. Optional adapters (Playwright, run-root env) degrade rather than hard-fail the install. Audit-preference state shows effective stage values, sources, asked status, and corrupt layers for target repository.
|
|
17
|
+
|
|
18
|
+
`ok` / `degraded` / `broken` describe install and runtime health of the local surface only — they are not a public capability-maturity verdict. Maturity vocabulary (`stable` / `experimental` / `blocked-by-gate` / `not-shipped`) stays with the `run-status` capability receipt; doctor reads existing facts and adds no new health or capability-state authority.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Build a static run handoff from an explicit run and its declared Fill
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# run-handoff
|
|
6
|
+
|
|
7
|
+
Build the existing static delivery package for one explicit Design I/O run. Not a pipeline obligation and not acceptance.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
python <plugin>/scripts/run_handoff.py <run> [--fill <declared-path>] [--round N] [--summary <text>] [--lang zh-CN|en] [--json]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- `<run>` is required. Do not discover a run from `.scratch/`.
|
|
16
|
+
- Read `fill:` declarations from that run's `plan.md` (unfenced column-0 lines). One declared Fill is used automatically; multiple require `--fill` with one of those declared paths; none or a missing/ineligible declaration fails with repair guidance.
|
|
17
|
+
- Do not scan the project for HTML, and do not use preview or reference assets as the reviewed Fill.
|
|
18
|
+
- The existing Evidence builder writes `evidence/static-handoff/` (delivery page, Fill copy, archive, disclosure, snapshots). Report `verdict`, `authority`, and `confirmationSource` verbatim, including `Pending`, `blocked`, `not-applicable`, and `unsubstantiated`.
|
|
19
|
+
- Do not write acceptance or convert a non-Pass state into Pass.
|
|
20
|
+
- Page language: explicit `--lang`, then `DPB_PREVIEW_LANG`, then `LANG`, otherwise Chinese. Unknown environment locales use the same Chinese default as Preview. Language affects UI labels only; machine fields, original diagnostics, and user-authored content remain verbatim.
|
|
21
|
+
|
|
22
|
+
## Done when
|
|
23
|
+
|
|
24
|
+
The user asked for a handoff; `index.html` exists under the selected run tree; and `verdict` / `authority` / `confirmationSource` were reported verbatim.
|
|
25
|
+
|
|
26
|
+
Scope:
|
|
27
|
+
$ARGUMENTS
|
package/commands/run-status.md
CHANGED
|
@@ -13,9 +13,11 @@ python <plugin>/scripts/run_status.py [.scratch/<run>] [--json] [--list] [--scra
|
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
- Omit the run path to pick the newest directory under `--scratch` (default `.scratch/`).
|
|
16
|
-
- `--json` emits machine-readable stages + `next` action.
|
|
16
|
+
- `--json` emits machine-readable stages + `next` action, plus the continuation block: selected run, current phase, blocker, run integrity, and the Console capability receipt (`implementation` / `validation` / `availability` / `publicClaim`).
|
|
17
17
|
- `--list` only lists discovered runs.
|
|
18
18
|
|
|
19
|
+
For an eligible run the continuation names an explicit `open-console` command for the local, experimental, trial-gated Run Console — no authorized external trial or public release is claimed until the separately authorized read-only trial gate passes (ADR-0043). `run-status` never starts a server, daemon, or background process itself; an ineligible run reports the blocking reason and a safe fallback instead.
|
|
20
|
+
|
|
19
21
|
## Done when
|
|
20
22
|
|
|
21
|
-
The command names completed stage markers, any active blocker (preview floor, baseline gate, recirculate verdict), and the single next valid resume action. It reuses `validate_run` judgments for G5 confirm validity rather than inventing a second state machine.
|
|
23
|
+
The command names completed stage markers, any active blocker (preview floor, baseline gate, recirculate verdict), and the single next valid resume action. It reuses `validate_run` judgments for G5 confirm validity rather than inventing a second state machine. Stale, partial, hash-mismatched, malformed, or inconsistent runs stay visible as those states and are never replaced by an older successful snapshot.
|
package/commands/ui-review.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
description: Dual-track UI review emitting the six-block point-back report (ledger / findings / positives / coverage / limitations / verdict)
|
|
3
3
|
---
|
|
4
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.
|
|
5
|
+
Run skill **ui-evaluator** (pull craft-guard checks when AI slop/motion/loading is in scope). Output issue/source/fix/severity/track; blocking first.
|
|
6
6
|
|
|
7
7
|
Scope:
|
|
8
8
|
$ARGUMENTS
|
|
@@ -17,3 +17,12 @@ Registry: `skills/design-playbook/references/rules.md`, full catalog (P3 run:
|
|
|
17
17
|
| I18N-01@1 | not-applicable | 单语控制台(zh-CN),无 i18n 声明(无 i18n.* 契约字段,L1 未声明多语言用户群) | - | - | - | 单语声明成立 | - |
|
|
18
18
|
| PERF-01@1 | applicable | - | clear | 长运行有持续进度感(feed 条目级进度逐 tick 更新;全局暂停 busy 即时反馈) | evidence/L6.2-pause-trace.json 反馈序列 | 反馈相称性未承诺耗时阈值(契约无阈值声明) | - |
|
|
19
19
|
| SEC-01@1 | not-applicable | 声明范围无敏感操作新增(敏感模拟参数默认脱敏沿用;全局暂停非敏感操作) | - | - | - | 无敏感面可查 | - |
|
|
20
|
+
| COPY-01@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档(2026-08-14);主动语态与动作命名一致性审查所需的全流程文案清单未采集 | - | - | - | - | - |
|
|
21
|
+
| COPY-02@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;用户侧命名审查所需的界面名词与实现命名对照未采集 | - | - | - | - | - |
|
|
22
|
+
| COPY-03@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;错误信息语气审查所需的错误态文案样本未采集 | - | - | - | - | - |
|
|
23
|
+
| A11Y-02@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;可见键盘焦点判定所需的聚焦态截图与键盘走查未采集(a11y 树无法证明视觉属性) | - | - | - | - | - |
|
|
24
|
+
| CRAFT-09@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;归档缺审计面样式源——filled-ui.md 为声明性产物索引不含样式源,candidates/preview 为一次性原型资产而非填充面源码 | - | - | - | - | - |
|
|
25
|
+
| CRAFT-10@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;归档缺审计面源标记与结构装置的视觉捕捉——evidence 仅交互轨迹与 a11y 树 JSON,无填充面标记源归档 | - | - | - | - | - |
|
|
26
|
+
| DECIDE-01@1 | applicable | - | clear | 选中方向为全局 run console 构成重组(candidates/console-region.html 草图 + preview round 1/2 用户确认),非未审视的默认外观收敛 | DD-0001 理由可回溯 l1.scenes(切页续读)与 PERF-01 比较轴及经用户确认的布局段突破;DD-0002 理由可回溯 l6.c4(跨视图状态闭环)与运行中心第一步方向——均引用 brief 具体事实 | 常规方向经比较矩阵沿 brief 轴证成,非未审视默认;无基线默认方向身份声明 | - |
|
|
27
|
+
|
|
28
|
+
注:2026-08-28 注册批(COPY-01/02/03、A11Y-02、CRAFT-09/10、DECIDE-01)晚于本 run 存档;按三态谓词补记——blocked 行在理由列点名缺失的证据面;DECIDE-01 依归档内可读的决策报告求值为 applicable。
|
|
@@ -17,3 +17,12 @@ Registry: `skills/design-playbook/references/rules.md`, full catalog (P2 run). S
|
|
|
17
17
|
| I18N-01@1 | not-applicable | 单语控制台,无 i18n 声明(无 i18n.* 契约字段,L1 未声明多语言用户群) | - | - | - | 单语声明成立 | - |
|
|
18
18
|
| PERF-01@1 | blocked | 性能感知需运行时度量,本 run provider 缺度量面(measurement 层不可采) | - | 导出等待仅观察到 busy 态 | 度量面缺席,无法判定反馈与耗时的相称性 | 无法在不承诺阈值的情况下检查例外 | 补采运行时度量后重评;缺口的证据语义见 point-back 覆盖声明 |
|
|
19
19
|
| SEC-01@1 | not-applicable | 声明范围无敏感操作新增(导出非敏感数据;隐藏敏感列由 column_scope 假设排除) | - | - | - | 无敏感面可查 | - |
|
|
20
|
+
| COPY-01@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档(2026-08-14);主动语态与动作命名一致性审查所需的全流程文案清单未采集 | - | - | - | - | - |
|
|
21
|
+
| COPY-02@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;用户侧命名审查所需的界面名词与实现命名对照未采集 | - | - | - | - | - |
|
|
22
|
+
| COPY-03@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;错误信息语气审查所需的错误态文案样本未采集 | - | - | - | - | - |
|
|
23
|
+
| A11Y-02@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;可见键盘焦点判定所需的聚焦态截图与键盘走查未采集(a11y 树无法证明视觉属性) | - | - | - | - | - |
|
|
24
|
+
| CRAFT-09@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;归档缺审计面样式源——filled-ui.md 为静态替身描述,无填充面样式源文件归档 | - | - | - | - | - |
|
|
25
|
+
| CRAFT-10@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;归档缺审计面源标记——filled-ui.md 为静态替身描述;既有截图仅覆盖错误态,无结构装置的视觉捕捉 | - | - | - | - | - |
|
|
26
|
+
| DECIDE-01@1 | applicable | - | clear | 选中方向为按周命名模式(compare 档轻量比较),不落入 2026-08 记录的自默认外观样貌;DD-0001 为 record 档,不在本规则范围 | DD-0002 理由可回溯 l1.target_user 比较轴(周频归档场景下检索是主任务)——引用 brief 具体事实而非通用措辞 | 常规方向经比较矩阵沿 brief 轴证成,非未审视默认;无基线默认方向身份声明 | - |
|
|
27
|
+
|
|
28
|
+
注:2026-08-28 注册批(COPY-01/02/03、A11Y-02、CRAFT-09/10、DECIDE-01)晚于本 run 存档;按三态谓词补记——blocked 行在理由列点名缺失的证据面;DECIDE-01 依归档内可读的决策报告求值为 applicable。
|
|
@@ -17,3 +17,12 @@ Registry: `skills/design-playbook/references/rules.md`. Seven-column rows; after
|
|
|
17
17
|
| I18N-01@1 | not-applicable | 单语控制台,无 i18n 声明(无 i18n.* 契约字段,L1 未声明多语言用户群) | - | - | - | 单语声明成立 | - |
|
|
18
18
|
| PERF-01@1 | blocked | 性能感知需运行时度量,本 run provider 缺度量面(measurement 层不可采) | - | 导出等待仅观察到 busy 态 | 度量面缺席,无法判定反馈与耗时的相称性 | 无法在不承诺阈值的情况下检查例外 | 补采运行时度量后重评;缺口的证据语义见 point-back 覆盖声明 |
|
|
19
19
|
| SEC-01@1 | not-applicable | 声明范围无敏感操作新增(导出非敏感数据;隐藏敏感列由 column_scope 假设排除) | - | - | - | 无敏感面可查 | - |
|
|
20
|
+
| COPY-01@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档(2026-08-14);主动语态与动作命名一致性审查所需的全流程文案清单未采集 | - | - | - | - | - |
|
|
21
|
+
| COPY-02@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;用户侧命名审查所需的界面名词与实现命名对照未采集 | - | - | - | - | - |
|
|
22
|
+
| COPY-03@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;错误信息语气审查所需的错误态文案样本未采集 | - | - | - | - | - |
|
|
23
|
+
| A11Y-02@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;可见键盘焦点判定所需的聚焦态截图与键盘走查未采集(a11y 树无法证明视觉属性) | - | - | - | - | - |
|
|
24
|
+
| CRAFT-09@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;选择器优先级冲突审查所需的样式源走查未执行 | - | - | - | - | - |
|
|
25
|
+
| CRAFT-10@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;结构装置与内容属性对应关系的走查未执行 | - | - | - | - | - |
|
|
26
|
+
| DECIDE-01@1 | not-applicable | 决策报告仅含 record 档 DD-0101,无 compare/explore 档方向决策条目 | - | - | - | - | - |
|
|
27
|
+
|
|
28
|
+
注:2026-08-28 注册批(COPY-01/02/03、A11Y-02、CRAFT-09/10、DECIDE-01)晚于本 run 存档;按三态谓词补记,blocked 行在理由列记缺失证据面。
|
|
@@ -17,3 +17,12 @@ Registry: `skills/design-playbook/references/rules.md`, full catalog (P3 run:
|
|
|
17
17
|
| I18N-01@1 | not-applicable | 单语控制台,无 i18n 声明(无 i18n.* 契约字段,L1 未声明多语言用户群) | - | - | - | 单语声明成立 | - |
|
|
18
18
|
| PERF-01@1 | applicable | - | clear | 长导出有持续进度感(30s 窗口 5 次采样,条目级进度持续更新) | evidence/L6.1-status-trace.json 进度采样序列 | 反馈相称性未承诺耗时阈值(契约无阈值声明) | - |
|
|
19
19
|
| SEC-01@1 | not-applicable | 声明范围无敏感操作新增(导出非敏感数据;隐藏敏感列由 column_scope 假设排除) | - | - | - | 无敏感面可查 | - |
|
|
20
|
+
| COPY-01@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档(2026-08-14);主动语态与动作命名一致性审查所需的全流程文案清单未采集 | - | - | - | - | - |
|
|
21
|
+
| COPY-02@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;用户侧命名审查所需的界面名词与实现命名对照未采集 | - | - | - | - | - |
|
|
22
|
+
| COPY-03@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;错误信息语气审查所需的错误态文案样本未采集 | - | - | - | - | - |
|
|
23
|
+
| A11Y-02@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;可见键盘焦点判定所需的聚焦态截图与键盘走查未采集(a11y 树无法证明视觉属性) | - | - | - | - | - |
|
|
24
|
+
| CRAFT-09@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;归档缺审计面样式源——无填充产物源文件归档,candidates/preview 为一次性原型资产而非填充面源码 | - | - | - | - | - |
|
|
25
|
+
| CRAFT-10@1 | blocked | 条目 2026-08-28 注册,晚于本 run 存档;归档缺审计面源标记与结构装置的视觉捕捉——evidence 仅交互轨迹 JSON,无填充产物源文件归档 | - | - | - | - | - |
|
|
26
|
+
| DECIDE-01@1 | applicable | - | clear | 选中方向为启用既有 status region 收纳导出任务(candidates/B.html 草图 + preview round 1/2 用户确认),非未审视的默认外观收敛 | DD-0003 理由可回溯 l1.scenes(导出中切页全局可查)与 PERF-01 比较轴;DD-0004 理由可回溯 l6.c2(跨视图状态闭环)与基线 status region 惯例声明——均引用 brief 具体事实 | 常规方向经比较矩阵沿 brief 轴证成,非未审视默认;基线声明的是 status region 惯例而非默认外观身份 | - |
|
|
27
|
+
|
|
28
|
+
注:2026-08-28 注册批(COPY-01/02/03、A11Y-02、CRAFT-09/10、DECIDE-01)晚于本 run 存档;按三态谓词补记——blocked 行在理由列点名缺失的证据面;DECIDE-01 依归档内可读的决策报告求值为 applicable。
|
package/lib/index.js
CHANGED
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
* `skills/` directory. The plugin locates the directory via `__dirname`,
|
|
9
9
|
* so no `!!js` expression and no cwd-dependent resolution is involved.
|
|
10
10
|
*
|
|
11
|
-
* 2.
|
|
12
|
-
* `run-review`, `run-status`, `ui-review`, `ux-spec` —
|
|
13
|
-
* matching `commands/<name>.md` prompt, substitute
|
|
14
|
-
* raw trailing input, and inject it as a
|
|
15
|
-
* `agent.followup()`.
|
|
11
|
+
* 2. Slash commands (ctx.commands) — `design-io`, `doctor`,
|
|
12
|
+
* `run-handoff`, `run-review`, `run-status`, `ui-review`, `ux-spec` —
|
|
13
|
+
* that load the matching `commands/<name>.md` prompt, substitute
|
|
14
|
+
* `$ARGUMENTS` with the raw trailing input, and inject it as a
|
|
15
|
+
* user-role follow-up turn via `agent.followup()`.
|
|
16
16
|
*
|
|
17
17
|
* The Cordis `!!js` evaluation scope provides no `require` (only Node globals
|
|
18
18
|
* plus ctx-provided values like dshHomePath/loader), so pointing a
|
|
@@ -81,7 +81,7 @@ exports.parseSkillFile = parseSkillFile
|
|
|
81
81
|
exports.createUserMessageFromPrompt = createUserMessageFromPrompt
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
|
-
*
|
|
84
|
+
* Slash commands this plugin registers. Each maps to a
|
|
85
85
|
* `commands/<name>.md` prompt file; the file's frontmatter `description`
|
|
86
86
|
* becomes the command's discovery metadata, and the body (with `$ARGUMENTS`
|
|
87
87
|
* substituted) is injected as a user follow-up turn.
|
|
@@ -89,6 +89,7 @@ exports.createUserMessageFromPrompt = createUserMessageFromPrompt
|
|
|
89
89
|
const COMMAND_NAMES = [
|
|
90
90
|
'design-io',
|
|
91
91
|
'doctor',
|
|
92
|
+
'run-handoff',
|
|
92
93
|
'run-review',
|
|
93
94
|
'run-status',
|
|
94
95
|
'ui-review',
|
|
@@ -662,7 +662,7 @@ def matrix_viewport(name: str) -> dict[str, Any]:
|
|
|
662
662
|
def capture_delivery_matrix(
|
|
663
663
|
*,
|
|
664
664
|
url: str,
|
|
665
|
-
out_dir: Path
|
|
665
|
+
out_dir: Path,
|
|
666
666
|
freeze: dict[str, Any] | None = None,
|
|
667
667
|
browser_adapter: BrowserAdapter | None = None,
|
|
668
668
|
) -> dict[str, dict[str, Any]]:
|
|
@@ -674,19 +674,19 @@ def capture_delivery_matrix(
|
|
|
674
674
|
can assemble the ``disclosure-review.json`` matrix and the ``/export-zip``
|
|
675
675
|
package from one pass.
|
|
676
676
|
|
|
677
|
-
``out_dir``
|
|
678
|
-
|
|
679
|
-
the
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
adapters
|
|
677
|
+
``out_dir`` is required and carries no default: handoff artifacts live
|
|
678
|
+
under the run tree (``<run_root>/evidence/static-handoff/``, ADR-0034 §5),
|
|
679
|
+
never the process CWD, so the caller must pass the run-tree destination
|
|
680
|
+
explicitly. ``browser_adapter`` uses the same ``BrowserAdapter`` seam as
|
|
681
|
+
``execute_capture_plan`` (real Playwright by default, injected fake in
|
|
682
|
+
tests). Production adapters should expose ``capture_and_probe`` so the
|
|
683
|
+
snapshot and metrics share one page; adapters without that seam remain
|
|
684
|
+
explicitly ``unmeasured``.
|
|
683
685
|
"""
|
|
684
686
|
freeze = freeze or {"enabled": True, "waitFonts": True, "networkIdle": False}
|
|
685
687
|
if browser_adapter is None:
|
|
686
688
|
browser_adapter = PlaywrightBrowserAdapter()
|
|
687
689
|
|
|
688
|
-
if out_dir is None:
|
|
689
|
-
out_dir = Path("output/playwright/static-handoff")
|
|
690
690
|
out_dir = Path(out_dir)
|
|
691
691
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
692
692
|
results: dict[str, dict[str, Any]] = {}
|
|
@@ -19,8 +19,10 @@ Two seams keep the contract builder pure and testable without a browser:
|
|
|
19
19
|
It only normalizes caller-supplied facts into the §4.2 shape.
|
|
20
20
|
|
|
21
21
|
``build_handoff_zip()`` packages the disclosure credential plus any caller-
|
|
22
|
-
supplied snapshot artifacts into a single ZIP
|
|
23
|
-
|
|
22
|
+
supplied snapshot artifacts into a single ZIP. The Evidence-side builder
|
|
23
|
+
(``handoff.py``) writes it to disk as ``static-handoff.zip`` next to the
|
|
24
|
+
delivery page under ``<run_root>/evidence/static-handoff/`` (ADR-0034); no
|
|
25
|
+
HTTP delivery endpoint exists. It never reads outside the caller-provided
|
|
24
26
|
file list.
|
|
25
27
|
"""
|
|
26
28
|
|
package/mcp/evidence/handoff.py
CHANGED
|
@@ -10,6 +10,12 @@ ADR-0034 fixed three boundaries the first Stage 9 implementation crossed:
|
|
|
10
10
|
``confirm-round-*.json`` that ``transaction.py`` persists (ADR-0013), never
|
|
11
11
|
re-derived. The ADR-0008 floor lives inside that record; re-deriving it here
|
|
12
12
|
would be a second confirmation authority, which CONTEXT.md forbids.
|
|
13
|
+
- **Verdict authority** - ``point-back.md`` owns the verdict (continuation
|
|
14
|
+
pack decision 4): a delivery Pass additionally requires the point-back's
|
|
15
|
+
own canonical Pass verdict, read through the single verdict-syntax parser
|
|
16
|
+
(ADR-0025) shared with G3 and run status. A confirmed round with resolved
|
|
17
|
+
gates can never convert a Recirculate/blocked - or missing/ambiguous -
|
|
18
|
+
owner verdict into Pass (decision 25).
|
|
13
19
|
- **Capture target** - the five-viewport matrix and the layout probe run
|
|
14
20
|
against the deliverable itself, not against any review chrome.
|
|
15
21
|
|
|
@@ -33,6 +39,8 @@ from dataclasses import dataclass
|
|
|
33
39
|
from pathlib import Path
|
|
34
40
|
from typing import Any, Callable
|
|
35
41
|
|
|
42
|
+
from design_playbook.mcp.ui_locale import resolve_ui_locale
|
|
43
|
+
from design_playbook.mcp.evidence.handoff_i18n import STRINGS
|
|
36
44
|
from design_playbook.mcp.evidence.disclosure import (
|
|
37
45
|
VIEWPORT_ORDER,
|
|
38
46
|
ViewportMetrics,
|
|
@@ -59,6 +67,7 @@ class StaticHandoffResult:
|
|
|
59
67
|
json_path: Path
|
|
60
68
|
zip_path: Path
|
|
61
69
|
index_html: Path
|
|
70
|
+
deliverable_html: Path
|
|
62
71
|
|
|
63
72
|
|
|
64
73
|
def _iso_now() -> str:
|
|
@@ -117,6 +126,25 @@ def _confirmation_from_record(
|
|
|
117
126
|
return True, ""
|
|
118
127
|
|
|
119
128
|
|
|
129
|
+
def _point_back_verdict(run_root: Path) -> str | None:
|
|
130
|
+
"""The canonical point-back verdict, or ``None`` when there is not
|
|
131
|
+
exactly one.
|
|
132
|
+
|
|
133
|
+
point-back.md is the finding/verdict authority (continuation pack
|
|
134
|
+
decision 4). Reading goes through the single verdict-syntax parser
|
|
135
|
+
(ADR-0025) that G3 and run status already share - never a second
|
|
136
|
+
parser here. ``None`` (missing, malformed, ambiguous, or repeated
|
|
137
|
+
verdict text) is never a Pass.
|
|
138
|
+
"""
|
|
139
|
+
from design_playbook.scripts.verdict_syntax import parse_verdict
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
text = (run_root / "point-back.md").read_text(encoding="utf-8")
|
|
143
|
+
except (OSError, UnicodeError):
|
|
144
|
+
return None
|
|
145
|
+
return parse_verdict(text).canonical
|
|
146
|
+
|
|
147
|
+
|
|
120
148
|
def _run_gate_validation(run_root: Path) -> dict[str, Any]:
|
|
121
149
|
"""Run the canonical validator over one run directory."""
|
|
122
150
|
spec = run_root / "spec.md"
|
|
@@ -586,7 +614,7 @@ def _decision_records_from_run(
|
|
|
586
614
|
|
|
587
615
|
|
|
588
616
|
def _render_index_html(
|
|
589
|
-
payload: dict[str, Any], matrix: Any, snap_dir: Path
|
|
617
|
+
payload: dict[str, Any], matrix: Any, snap_dir: Path, locale: str
|
|
590
618
|
) -> str:
|
|
591
619
|
"""Render the delivery page with the payload inlined.
|
|
592
620
|
|
|
@@ -604,13 +632,22 @@ def _render_index_html(
|
|
|
604
632
|
.replace('"', """)
|
|
605
633
|
)
|
|
606
634
|
|
|
635
|
+
strings = STRINGS[locale]
|
|
636
|
+
|
|
637
|
+
def _display(value: Any) -> str:
|
|
638
|
+
if value is None:
|
|
639
|
+
return "—"
|
|
640
|
+
if isinstance(value, bool):
|
|
641
|
+
return strings["yes" if value else "no"]
|
|
642
|
+
return strings.get(str(value), str(value))
|
|
643
|
+
|
|
607
644
|
statuses = payload.get("gateStatuses")
|
|
608
645
|
rows = ""
|
|
609
646
|
if isinstance(statuses, list):
|
|
610
647
|
for index, state in enumerate(statuses[:8], start=1):
|
|
611
648
|
rows += (
|
|
612
649
|
f'<tr><td>G{index}</td><td class="st st-{_esc(state)}">'
|
|
613
|
-
f"{_esc(state)}</td></tr>"
|
|
650
|
+
f"{_esc(_display(state))}</td></tr>"
|
|
614
651
|
)
|
|
615
652
|
viewports = payload.get("viewports")
|
|
616
653
|
vp_rows = ""
|
|
@@ -625,9 +662,9 @@ def _render_index_html(
|
|
|
625
662
|
f"<td>{_esc(vp.get('name'))}</td>"
|
|
626
663
|
f"<td>{_esc(metrics.get('sw'))}</td>"
|
|
627
664
|
f"<td>{_esc(metrics.get('innerH'))}</td>"
|
|
628
|
-
f"<td>{_esc(metrics.get('hOverflow'))}</td>"
|
|
629
|
-
f"<td>{_esc(disclosure.get('inFold'))}</td>"
|
|
630
|
-
f"<td>{_esc(metrics.get('measurementStatus'))}</td>"
|
|
665
|
+
f"<td>{_esc(_display(metrics.get('hOverflow')))}</td>"
|
|
666
|
+
f"<td>{_esc(_display(disclosure.get('inFold')))}</td>"
|
|
667
|
+
f"<td>{_esc(_display(metrics.get('measurementStatus')))}</td>"
|
|
631
668
|
"</tr>"
|
|
632
669
|
)
|
|
633
670
|
decisions = payload.get("decisions")
|
|
@@ -638,7 +675,7 @@ def _render_index_html(
|
|
|
638
675
|
decision_items += (
|
|
639
676
|
f"<li><code>{_esc(item.get('id'))}</code> "
|
|
640
677
|
f"{_esc(item.get('title'))} "
|
|
641
|
-
f"<small>({_esc(item.get('authority'))})</small></li>"
|
|
678
|
+
f"<small>({_esc(_display(item.get('authority')))})</small></li>"
|
|
642
679
|
)
|
|
643
680
|
payload_json = json.dumps(payload, ensure_ascii=False, indent=2)
|
|
644
681
|
# A literal "</script>" inside the payload would close the block early;
|
|
@@ -651,27 +688,44 @@ def _render_index_html(
|
|
|
651
688
|
matrix.get(vp), vp, snap_dir
|
|
652
689
|
):
|
|
653
690
|
snap_figures += (
|
|
654
|
-
f'<figure><img src="snapshots/viewport-{vp}.png"
|
|
691
|
+
f'<figure><img src="snapshots/viewport-{vp}.png" '
|
|
692
|
+
f'alt="{_esc(strings["snapshot"].format(viewport=vp))}" '
|
|
655
693
|
f'loading="lazy"/><figcaption>viewport-{vp}.png</figcaption></figure>'
|
|
656
694
|
)
|
|
657
695
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
.
|
|
674
|
-
|
|
696
|
+
note_key = "confirmed_note" if payload.get("authority") == "confirmed-user" else "pending_note"
|
|
697
|
+
note = _esc(strings[note_key])
|
|
698
|
+
if payload.get("confirmationNote"):
|
|
699
|
+
# Runtime diagnostics remain verbatim, distinct from localized UI copy.
|
|
700
|
+
note += (
|
|
701
|
+
f'<details><summary>{_esc(strings["diagnostic"])}</summary>'
|
|
702
|
+
f'<pre>{_esc(payload["confirmationNote"])}</pre></details>'
|
|
703
|
+
)
|
|
704
|
+
values = {key.upper(): _esc(text) for key, text in strings.items()}
|
|
705
|
+
values.update({
|
|
706
|
+
"LANG": locale,
|
|
707
|
+
"VERDICT_LABEL": strings["verdict"],
|
|
708
|
+
"AUTHORITY_LABEL": strings["authority"],
|
|
709
|
+
"PROFILE_LABEL": strings["profile"],
|
|
710
|
+
"GATES_PASSED_LABEL": strings["gates_passed"],
|
|
711
|
+
"VERDICT": _esc(_display(payload.get("verdict"))),
|
|
712
|
+
"VERDICT_LOWERCASE": _esc(str(payload.get("verdict")).lower()),
|
|
713
|
+
"RUN_ID": _esc(payload.get("runId")),
|
|
714
|
+
"AUTHORITY": _esc(_display(payload.get("authority"))),
|
|
715
|
+
"PROFILE": _esc(_display(payload.get("profile"))),
|
|
716
|
+
"TIMESTAMP": _esc(payload.get("timestamp")),
|
|
717
|
+
"GATES_PASSED": _esc(payload.get("gatesPassed")),
|
|
718
|
+
"CAPTURE_STATUS": _esc(_display(payload.get("captureStatus"))),
|
|
719
|
+
"CONFIRMATION_NOTE": note,
|
|
720
|
+
"GATE_ROWS": rows,
|
|
721
|
+
"VIEWPORT_ROWS": vp_rows,
|
|
722
|
+
"DECISION_ITEMS": decision_items,
|
|
723
|
+
"SNAPSHOT_FIGURES": snap_figures,
|
|
724
|
+
"PAYLOAD_JSON": payload_json,
|
|
725
|
+
"PAYLOAD_TEXT": _esc(json.dumps(payload, ensure_ascii=False, indent=2)),
|
|
726
|
+
})
|
|
727
|
+
# One pass: user-authored text resembling a template marker stays literal.
|
|
728
|
+
return re.sub(r"%([A-Z_]+)%", lambda match: values[match[1]], _page_template())
|
|
675
729
|
|
|
676
730
|
|
|
677
731
|
def build_static_handoff(
|
|
@@ -683,6 +737,7 @@ def build_static_handoff(
|
|
|
683
737
|
capture_runner: Callable[..., Any] | None = None,
|
|
684
738
|
gate_runner: Callable[..., Any] | None = None,
|
|
685
739
|
out_dir: Path | None = None,
|
|
740
|
+
locale: str | None = None,
|
|
686
741
|
) -> StaticHandoffResult:
|
|
687
742
|
"""Build the Stage 9 static handoff from durable run artifacts.
|
|
688
743
|
|
|
@@ -691,8 +746,10 @@ def build_static_handoff(
|
|
|
691
746
|
output (``filled-ui.html``) - the page the five-viewport matrix and the
|
|
692
747
|
layout probe actually target (ADR-0034 §4). Everything is written under
|
|
693
748
|
``<run_root>/evidence/static-handoff/``: snapshots, the disclosure JSON,
|
|
694
|
-
the ZIP package,
|
|
749
|
+
the ZIP package, a same-directory ``deliverable.html`` copy (the page's
|
|
750
|
+
relative link target, spec A5), and a self-contained index page.
|
|
695
751
|
"""
|
|
752
|
+
locale = resolve_ui_locale(locale)
|
|
696
753
|
run_root = Path(run_root)
|
|
697
754
|
deliverable = Path(deliverable)
|
|
698
755
|
out_dir = Path(out_dir) if out_dir is not None else run_root / "evidence" / "static-handoff"
|
|
@@ -707,6 +764,14 @@ def build_static_handoff(
|
|
|
707
764
|
if gate_runner is None:
|
|
708
765
|
gate_runner = _run_gate_validation
|
|
709
766
|
|
|
767
|
+
# Read the deliverable source up front and fail fast: its bytes are the
|
|
768
|
+
# run identity (hash), the ZIP's prototype member, and the on-disk copy
|
|
769
|
+
# the delivery page links relatively (#107). A missing or undecodable
|
|
770
|
+
# source aborts before any capture launches or artifact is written, so a
|
|
771
|
+
# delivery page can never exist without its link target.
|
|
772
|
+
deliverable_bytes = deliverable.read_bytes()
|
|
773
|
+
deliverable_text = deliverable_bytes.decode("utf-8")
|
|
774
|
+
|
|
710
775
|
# Sample conditional-gate preconditions BEFORE writing anything: this
|
|
711
776
|
# builder's own output lives under evidence/, and a precondition sampled
|
|
712
777
|
# afterwards would be one this run manufactured for itself.
|
|
@@ -762,14 +827,25 @@ def build_static_handoff(
|
|
|
762
827
|
confirmed, confirmation_reason = _confirmation_from_record(run_root, round_n)
|
|
763
828
|
|
|
764
829
|
capture_complete = capture_status == "captured"
|
|
765
|
-
|
|
830
|
+
# The point-back owner verdict gates a delivery Pass (decision 25): a
|
|
831
|
+
# confirmed round with resolved gates still cannot convert a
|
|
832
|
+
# Recirculate/blocked owner verdict - or a missing/ambiguous one - into
|
|
833
|
+
# Pass. Pending/Recirculate behaviour and gatesPassed semantics are
|
|
834
|
+
# unchanged.
|
|
835
|
+
owner_pass = _point_back_verdict(run_root) == "pass"
|
|
836
|
+
if (
|
|
837
|
+
confirmed
|
|
838
|
+
and owner_pass
|
|
839
|
+
and capture_complete
|
|
840
|
+
and gate.get("available")
|
|
841
|
+
and gates_resolved
|
|
842
|
+
):
|
|
766
843
|
verdict = "Pass"
|
|
767
844
|
elif confirmed:
|
|
768
845
|
verdict = "Recirculate"
|
|
769
846
|
else:
|
|
770
847
|
verdict = "Pending"
|
|
771
848
|
|
|
772
|
-
deliverable_bytes = deliverable.read_bytes()
|
|
773
849
|
run_id = (
|
|
774
850
|
f"static-handoff-{round_n}-{hashlib.sha256(deliverable_bytes).hexdigest()[:12]}"
|
|
775
851
|
)
|
|
@@ -808,6 +884,13 @@ def build_static_handoff(
|
|
|
808
884
|
json_path = out_dir / "disclosure-review.json"
|
|
809
885
|
json_path.write_text(disclosure_json(payload), encoding="utf-8")
|
|
810
886
|
|
|
887
|
+
# The delivery page links "deliverable.html" as a same-directory relative
|
|
888
|
+
# anchor (spec A5: disk artifacts, same-directory relative links); the copy
|
|
889
|
+
# must exist beside index.html, byte-identical to the ZIP member, or the
|
|
890
|
+
# delivery surface ships a dead link (#107).
|
|
891
|
+
deliverable_copy = out_dir / "deliverable.html"
|
|
892
|
+
deliverable_copy.write_bytes(deliverable_bytes)
|
|
893
|
+
|
|
811
894
|
artifacts: dict[str, str] = {}
|
|
812
895
|
if snap_dir.is_dir():
|
|
813
896
|
for vp in VIEWPORT_ORDER:
|
|
@@ -820,13 +903,13 @@ def build_static_handoff(
|
|
|
820
903
|
artifact_files=artifacts,
|
|
821
904
|
# spec §4.1: the handoff ships "snapshots and prototype code". PNGs
|
|
822
905
|
# alone do not let the recipient rebuild the reviewed page.
|
|
823
|
-
text_members={"deliverable.html":
|
|
906
|
+
text_members={"deliverable.html": deliverable_text},
|
|
824
907
|
zip_target=str(zip_path),
|
|
825
908
|
)
|
|
826
909
|
|
|
827
910
|
index_html = out_dir / "index.html"
|
|
828
911
|
index_html.write_text(
|
|
829
|
-
_render_index_html(payload, matrix, snap_dir), encoding="utf-8"
|
|
912
|
+
_render_index_html(payload, matrix, snap_dir, locale), encoding="utf-8"
|
|
830
913
|
)
|
|
831
914
|
|
|
832
915
|
return StaticHandoffResult(
|
|
@@ -835,4 +918,5 @@ def build_static_handoff(
|
|
|
835
918
|
json_path=json_path,
|
|
836
919
|
zip_path=zip_path,
|
|
837
920
|
index_html=index_html,
|
|
921
|
+
deliverable_html=deliverable_copy,
|
|
838
922
|
)
|