@1agh/maude 0.23.0 → 0.25.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 +34 -1
- package/cli/bin/maude.mjs +3 -0
- package/cli/commands/cache.mjs +181 -0
- package/cli/commands/cache.test.mjs +166 -0
- package/cli/commands/design-link.test.mjs +32 -2
- package/cli/commands/design.mjs +95 -4
- package/cli/commands/design.test.mjs +56 -0
- package/cli/commands/help.mjs +34 -0
- package/cli/commands/hub.mjs +255 -30
- package/cli/commands/hub.test.mjs +126 -2
- package/cli/commands/init.mjs +3 -0
- package/cli/commands/preflight.mjs +11 -0
- package/cli/commands/preflight.test.mjs +46 -0
- package/cli/commands/scenario-report.mjs +45 -0
- package/cli/lib/cache.mjs +407 -0
- package/cli/lib/cache.test.mjs +303 -0
- package/cli/lib/design-link.mjs +74 -10
- package/cli/lib/flow-design-integration.test.mjs +165 -0
- package/cli/lib/gitignore-block.mjs +90 -0
- package/cli/lib/gitignore-block.test.mjs +123 -0
- package/cli/lib/plugin-cli-reachability.test.mjs +56 -0
- package/cli/lib/preflight.mjs +41 -10
- package/package.json +8 -8
- package/plugins/design/dependencies.json +30 -2
- package/plugins/design/dev-server/ai-banner.tsx +2 -2
- package/plugins/design/dev-server/annotations-context-toolbar.tsx +763 -123
- package/plugins/design/dev-server/annotations-layer.tsx +1624 -208
- package/plugins/design/dev-server/api.ts +123 -4
- package/plugins/design/dev-server/bin/_enumerate-artboards-playwright.mjs +2 -2
- package/plugins/design/dev-server/bin/_html-playwright.mjs +2 -2
- package/plugins/design/dev-server/bin/_pdf-playwright.mjs +25 -8
- package/plugins/design/dev-server/bin/_png-playwright.mjs +20 -20
- package/plugins/design/dev-server/bin/_pptx-playwright.mjs +2 -2
- package/plugins/design/dev-server/bin/_pw-launch.mjs +61 -0
- package/plugins/design/dev-server/bin/_screenshot-playwright.mjs +2 -2
- package/plugins/design/dev-server/bin/_svg-playwright.mjs +125 -19
- package/plugins/design/dev-server/bin/preflight.sh +21 -10
- package/plugins/design/dev-server/bin/prep.sh +211 -0
- package/plugins/design/dev-server/bin/scenario-report.mjs +209 -0
- package/plugins/design/dev-server/bin/screenshot.sh +18 -1
- package/plugins/design/dev-server/bin/smoke.sh +208 -23
- package/plugins/design/dev-server/canvas-arrowheads.ts +220 -0
- package/plugins/design/dev-server/canvas-comment-mount.tsx +8 -24
- package/plugins/design/dev-server/canvas-cursors.ts +173 -0
- package/plugins/design/dev-server/canvas-icons.tsx +299 -0
- package/plugins/design/dev-server/canvas-lib.tsx +25 -21
- package/plugins/design/dev-server/canvas-meta.schema.json +20 -0
- package/plugins/design/dev-server/canvas-shell.tsx +404 -66
- package/plugins/design/dev-server/client/app.jsx +1231 -416
- package/plugins/design/dev-server/client/styles/3-shell.css +10 -0
- package/plugins/design/dev-server/collab/index.ts +87 -9
- package/plugins/design/dev-server/collab/persistence.ts +34 -3
- package/plugins/design/dev-server/collab/registry.ts +80 -2
- package/plugins/design/dev-server/collab/room.ts +21 -8
- package/plugins/design/dev-server/context-menu.tsx +167 -23
- package/plugins/design/dev-server/context.ts +24 -0
- package/plugins/design/dev-server/contextual-toolbar.tsx +7 -7
- package/plugins/design/dev-server/dist/client.bundle.js +364 -29
- package/plugins/design/dev-server/dist/comment-mount.js +78 -33
- package/plugins/design/dev-server/dist/styles.css +16 -0
- package/plugins/design/dev-server/equal-spacing-handles.tsx +1 -1
- package/plugins/design/dev-server/export-dialog.tsx +208 -18
- package/plugins/design/dev-server/exporters/html.ts +4 -1
- package/plugins/design/dev-server/exporters/index.ts +4 -1
- package/plugins/design/dev-server/exporters/pdf.ts +7 -1
- package/plugins/design/dev-server/exporters/png.ts +21 -2
- package/plugins/design/dev-server/exporters/pptx.ts +330 -201
- package/plugins/design/dev-server/exporters/scope.ts +107 -11
- package/plugins/design/dev-server/exporters/svg.ts +4 -1
- package/plugins/design/dev-server/fs-watch.ts +1 -0
- package/plugins/design/dev-server/http.ts +299 -28
- package/plugins/design/dev-server/input-router.tsx +35 -5
- package/plugins/design/dev-server/participants-chrome.tsx +10 -10
- package/plugins/design/dev-server/server.ts +123 -1
- package/plugins/design/dev-server/sync/agent.ts +95 -0
- package/plugins/design/dev-server/sync/codec.ts +155 -0
- package/plugins/design/dev-server/sync/connection-state.ts +203 -0
- package/plugins/design/dev-server/sync/index.ts +479 -35
- package/plugins/design/dev-server/sync/materialize.ts +62 -0
- package/plugins/design/dev-server/sync/migrate-seed.ts +163 -0
- package/plugins/design/dev-server/sync/origins.ts +57 -0
- package/plugins/design/dev-server/sync/projection.ts +368 -0
- package/plugins/design/dev-server/sync/status.ts +115 -0
- package/plugins/design/dev-server/sync/untrusted.ts +153 -0
- package/plugins/design/dev-server/test/_helpers.ts +6 -2
- package/plugins/design/dev-server/test/annotations-draw-modifiers.test.ts +206 -0
- package/plugins/design/dev-server/test/annotations-layer.test.ts +311 -0
- package/plugins/design/dev-server/test/annotations-roundtrip.test.ts +519 -0
- package/plugins/design/dev-server/test/canvas-cursors.test.ts +162 -0
- package/plugins/design/dev-server/test/canvas-origin-gate.test.ts +76 -0
- package/plugins/design/dev-server/test/canvas-route.test.ts +4 -1
- package/plugins/design/dev-server/test/collab-reseed-guard.test.ts +78 -0
- package/plugins/design/dev-server/test/collab-room.test.ts +21 -10
- package/plugins/design/dev-server/test/csp-canvas-shell.test.ts +46 -0
- package/plugins/design/dev-server/test/exporters/png.test.ts +24 -1
- package/plugins/design/dev-server/test/exporters/pptx-deck.test.ts +112 -0
- package/plugins/design/dev-server/test/exporters/pw-launch.test.ts +49 -0
- package/plugins/design/dev-server/test/exporters/scope.test.ts +0 -0
- package/plugins/design/dev-server/test/fixtures/phase-20-annotations.svg +1 -0
- package/plugins/design/dev-server/test/fixtures/phase-21-annotations.svg +1 -0
- package/plugins/design/dev-server/test/input-router.test.ts +32 -4
- package/plugins/design/dev-server/test/sanitize-annotation-svg.test.ts +132 -0
- package/plugins/design/dev-server/test/shared-doc-convergence.test.ts +265 -0
- package/plugins/design/dev-server/test/shared-doc-foundation.test.ts +63 -0
- package/plugins/design/dev-server/test/shared-doc-migrate.test.ts +160 -0
- package/plugins/design/dev-server/test/shared-doc-projection.test.ts +238 -0
- package/plugins/design/dev-server/test/sync-connection-state.test.ts +146 -0
- package/plugins/design/dev-server/test/sync-meta-codec.test.ts +123 -0
- package/plugins/design/dev-server/test/sync-runtime.test.ts +531 -4
- package/plugins/design/dev-server/test/sync-status.test.ts +80 -0
- package/plugins/design/dev-server/test/sync-untrusted.test.ts +104 -0
- package/plugins/design/dev-server/test/use-annotation-resize.test.ts +200 -0
- package/plugins/design/dev-server/test/use-tool-mode.test.tsx +39 -13
- package/plugins/design/dev-server/tool-palette.tsx +155 -24
- package/plugins/design/dev-server/undo-hud.tsx +4 -4
- package/plugins/design/dev-server/undo-stack.ts +20 -4
- package/plugins/design/dev-server/use-annotation-resize.tsx +219 -61
- package/plugins/design/dev-server/use-tool-mode.tsx +68 -19
- package/plugins/design/dev-server/ws.ts +40 -1
- package/plugins/design/templates/_shell.html +85 -10
- package/plugins/design/templates/canvas.tsx.template +13 -0
- package/plugins/design/templates/design-system-inspiration/SUB-AGENT-PROMPTS.md +14 -7
- package/plugins/flow/.claude-plugin/config.schema.json +5 -0
- package/plugins/flow/templates/ai-skeleton/README.md +1 -1
- package/plugins/flow/templates/ai-skeleton/gitignore +10 -0
- package/plugins/flow/templates/ai-skeleton/scenarios/README.md +3 -1
- package/plugins/flow/templates/ai-skeleton/workflows.config.json +2 -1
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# prep.sh — one-shot pre-flight context for the design slash commands.
|
|
3
|
+
#
|
|
4
|
+
# Collapses the 4–8 sequential bash invocations that /design:new, /design:edit
|
|
5
|
+
# and /design:setup-ds each ran in their step-0/step-1 (bootstrap-check + config
|
|
6
|
+
# jq reads + _active.json read + _preflight read + _server.json PID probe + slug
|
|
7
|
+
# compute) into a SINGLE call that emits one structured blob. Same shape family
|
|
8
|
+
# as bootstrap-check.sh / server-up.sh.
|
|
9
|
+
#
|
|
10
|
+
# Reads (all optional — missing files degrade gracefully, never error):
|
|
11
|
+
# .design/config.json → NAME, DESIGN_ROOT, ROOT_CLASS, THEME, TOKENS_REL,
|
|
12
|
+
# NEW_CANVAS_DIR, NEW_COMPONENT_DIR, TEAM_ACCENT,
|
|
13
|
+
# DEFAULT_DS, KNOWN_DS, ACCENT_STRATEGY, COLOR_SPACE
|
|
14
|
+
# <designRoot>/_active.json → ACTIVE_CANVAS, SELECTED_ELEMENT, SELECTED_FILE,
|
|
15
|
+
# SEL_VALID, OPEN_TABS, ACTIVE_SLUG
|
|
16
|
+
# <designRoot>/_preflight.json → DEPS_OK, DEPS_MISSING (Phase A cache)
|
|
17
|
+
# <designRoot>/_server.json → SERVER_UP, SERVER_PORT (PID liveness + /_health)
|
|
18
|
+
#
|
|
19
|
+
# Usage:
|
|
20
|
+
# prep.sh # JSON on stdout (default)
|
|
21
|
+
# prep.sh --json # JSON on stdout
|
|
22
|
+
# prep.sh --shell-export # `export FOO=bar` lines, eval-friendly
|
|
23
|
+
# prep.sh --shape <new|edit|setup-ds> # emit only the subset that command needs
|
|
24
|
+
# prep.sh --root <repo> # override repo root (else git root → $CLAUDE_PROJECT_DIR → pwd)
|
|
25
|
+
#
|
|
26
|
+
# Resolution (DDR-045): resolve the repo root from args/env/git — never from
|
|
27
|
+
# `dirname $0`, which maps to /$bunfs/root inside a bun --compile binary.
|
|
28
|
+
#
|
|
29
|
+
# Exit: 0 always (this is a context gatherer; callers branch on the fields).
|
|
30
|
+
|
|
31
|
+
MODE="json"
|
|
32
|
+
SHAPE="all"
|
|
33
|
+
REPO=""
|
|
34
|
+
while [ $# -gt 0 ]; do
|
|
35
|
+
case "$1" in
|
|
36
|
+
--json) MODE="json"; shift ;;
|
|
37
|
+
--shell-export) MODE="shell"; shift ;;
|
|
38
|
+
--shape) SHAPE="$2"; shift 2 ;;
|
|
39
|
+
--root) REPO="$2"; shift 2 ;;
|
|
40
|
+
--help|-h)
|
|
41
|
+
sed -n '2,33p' "$0" | sed 's/^# \?//'
|
|
42
|
+
exit 0
|
|
43
|
+
;;
|
|
44
|
+
*)
|
|
45
|
+
echo "prep.sh: unknown arg '$1' (try --help)" >&2
|
|
46
|
+
exit 2
|
|
47
|
+
;;
|
|
48
|
+
esac
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
case "$SHAPE" in
|
|
52
|
+
all|new|edit|setup-ds) ;;
|
|
53
|
+
*) echo "prep.sh: --shape must be one of new|edit|setup-ds (got '$SHAPE')" >&2; exit 2 ;;
|
|
54
|
+
esac
|
|
55
|
+
|
|
56
|
+
# ---- repo root ----------------------------------------------------------------
|
|
57
|
+
if [ -z "$REPO" ]; then
|
|
58
|
+
REPO="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
HAVE_JQ=0
|
|
62
|
+
command -v jq >/dev/null 2>&1 && HAVE_JQ=1
|
|
63
|
+
|
|
64
|
+
# ---- config -------------------------------------------------------------------
|
|
65
|
+
CFG="$REPO/.design/config.json"
|
|
66
|
+
CONFIG_PRESENT="false"; [ -f "$CFG" ] && CONFIG_PRESENT="true"
|
|
67
|
+
|
|
68
|
+
NAME="Project"
|
|
69
|
+
DESIGN_ROOT=".design"
|
|
70
|
+
ROOT_CLASS="app"
|
|
71
|
+
THEME="dark"
|
|
72
|
+
TOKENS_REL="system/colors_and_type.css"
|
|
73
|
+
NEW_CANVAS_DIR="ui/project"
|
|
74
|
+
NEW_COMPONENT_DIR="ui/project/components"
|
|
75
|
+
TEAM_ACCENT=""
|
|
76
|
+
DEFAULT_DS="project"
|
|
77
|
+
KNOWN_DS=""
|
|
78
|
+
ACCENT_STRATEGY="single"
|
|
79
|
+
COLOR_SPACE="oklch"
|
|
80
|
+
|
|
81
|
+
if [ "$CONFIG_PRESENT" = "true" ] && [ "$HAVE_JQ" = "1" ]; then
|
|
82
|
+
NAME=$(jq -r '.name // "Project"' "$CFG")
|
|
83
|
+
DESIGN_ROOT=$(jq -r '.designRoot // ".design"' "$CFG")
|
|
84
|
+
ROOT_CLASS=$(jq -r '.rootClass // "app"' "$CFG")
|
|
85
|
+
THEME=$(jq -r '.themeDefault // "dark"' "$CFG")
|
|
86
|
+
TOKENS_REL=$(jq -r '.tokensCssRel // "system/colors_and_type.css"' "$CFG")
|
|
87
|
+
NEW_CANVAS_DIR=$(jq -r '.newCanvasDir // "ui/project"' "$CFG")
|
|
88
|
+
NEW_COMPONENT_DIR=$(jq -r '.newComponentDir // "ui/project/components"' "$CFG")
|
|
89
|
+
TEAM_ACCENT=$(jq -r '.teamAccentDefault // empty' "$CFG")
|
|
90
|
+
DEFAULT_DS=$(jq -r '.defaultDesignSystem // "project"' "$CFG")
|
|
91
|
+
KNOWN_DS=$(jq -r '.designSystems[]?.name // empty' "$CFG" 2>/dev/null | tr '\n' ' ' | sed 's/ $//')
|
|
92
|
+
ACCENT_STRATEGY=$(jq -r '.accentStrategy // "single"' "$CFG")
|
|
93
|
+
COLOR_SPACE=$(jq -r '.colorSpace // "oklch"' "$CFG")
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
DR_ABS="$REPO/$DESIGN_ROOT"
|
|
97
|
+
|
|
98
|
+
# ---- active canvas / selection ------------------------------------------------
|
|
99
|
+
ACTIVE_FILE="$DR_ABS/_active.json"
|
|
100
|
+
ACTIVE_CANVAS=""
|
|
101
|
+
SELECTED_ELEMENT=""
|
|
102
|
+
SELECTED_FILE=""
|
|
103
|
+
SEL_VALID="0"
|
|
104
|
+
OPEN_TABS="0"
|
|
105
|
+
ACTIVE_SLUG=""
|
|
106
|
+
if [ -f "$ACTIVE_FILE" ] && [ "$HAVE_JQ" = "1" ]; then
|
|
107
|
+
ACTIVE_CANVAS=$(jq -r '.active // empty' "$ACTIVE_FILE" 2>/dev/null)
|
|
108
|
+
[ "$ACTIVE_CANVAS" = "null" ] && ACTIVE_CANVAS=""
|
|
109
|
+
SELECTED_ELEMENT=$(jq -rc '.selected // empty' "$ACTIVE_FILE" 2>/dev/null)
|
|
110
|
+
SELECTED_FILE=$(jq -r '.selected.file // empty' "$ACTIVE_FILE" 2>/dev/null)
|
|
111
|
+
OPEN_TABS=$(jq -r '(.open_tabs // []) | length' "$ACTIVE_FILE" 2>/dev/null || echo 0)
|
|
112
|
+
if [ -n "$SELECTED_ELEMENT" ] && [ -n "$ACTIVE_CANVAS" ] && [ "$SELECTED_FILE" = "$ACTIVE_CANVAS" ]; then
|
|
113
|
+
SEL_VALID="1"
|
|
114
|
+
fi
|
|
115
|
+
if [ -n "$ACTIVE_CANVAS" ]; then
|
|
116
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
117
|
+
ACTIVE_SLUG=$(bash "$SCRIPT_DIR/slug.sh" "${ACTIVE_CANVAS#"$DESIGN_ROOT"/}" 2>/dev/null)
|
|
118
|
+
fi
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# ---- deps preflight cache -----------------------------------------------------
|
|
122
|
+
PREFLIGHT_FILE="$DR_ABS/_preflight.json"
|
|
123
|
+
DEPS_OK="unknown"
|
|
124
|
+
DEPS_MISSING=""
|
|
125
|
+
if [ -f "$PREFLIGHT_FILE" ] && [ "$HAVE_JQ" = "1" ]; then
|
|
126
|
+
DEPS_OK=$(jq -r 'if .ok == true then "true" elif .ok == false then "false" else "unknown" end' "$PREFLIGHT_FILE" 2>/dev/null || echo unknown)
|
|
127
|
+
DEPS_MISSING=$(jq -r '(.missing // []) | join(",")' "$PREFLIGHT_FILE" 2>/dev/null || echo "")
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# ---- server liveness ----------------------------------------------------------
|
|
131
|
+
SERVER_FILE="$DR_ABS/_server.json"
|
|
132
|
+
SERVER_UP="false"
|
|
133
|
+
SERVER_PORT=""
|
|
134
|
+
if [ -f "$SERVER_FILE" ] && [ "$HAVE_JQ" = "1" ]; then
|
|
135
|
+
SERVER_PORT=$(jq -r '.port // empty' "$SERVER_FILE" 2>/dev/null)
|
|
136
|
+
SERVER_PID=$(jq -r '.pid // empty' "$SERVER_FILE" 2>/dev/null)
|
|
137
|
+
if [ -n "$SERVER_PID" ] && [ -n "$SERVER_PORT" ] \
|
|
138
|
+
&& kill -0 "$SERVER_PID" 2>/dev/null \
|
|
139
|
+
&& curl -fs "http://localhost:$SERVER_PORT/_health" >/dev/null 2>&1; then
|
|
140
|
+
SERVER_UP="true"
|
|
141
|
+
fi
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# ---- emit ---------------------------------------------------------------------
|
|
145
|
+
# Shape membership: `new`/`setup-ds` omit the active-canvas/selection block
|
|
146
|
+
# (those commands don't act on an open canvas); `edit`/`all` emit everything.
|
|
147
|
+
if [ "$MODE" = "shell" ]; then
|
|
148
|
+
printf 'export REPO_ROOT=%q\n' "$REPO"
|
|
149
|
+
printf 'export CONFIG_PRESENT=%s\n' "$CONFIG_PRESENT"
|
|
150
|
+
printf 'export NAME=%q\n' "$NAME"
|
|
151
|
+
printf 'export DESIGN_ROOT=%q\n' "$DESIGN_ROOT"
|
|
152
|
+
printf 'export ROOT_CLASS=%q\n' "$ROOT_CLASS"
|
|
153
|
+
printf 'export THEME=%q\n' "$THEME"
|
|
154
|
+
printf 'export TOKENS_REL=%q\n' "$TOKENS_REL"
|
|
155
|
+
printf 'export NEW_CANVAS_DIR=%q\n' "$NEW_CANVAS_DIR"
|
|
156
|
+
printf 'export NEW_COMPONENT_DIR=%q\n' "$NEW_COMPONENT_DIR"
|
|
157
|
+
printf 'export TEAM_ACCENT=%q\n' "$TEAM_ACCENT"
|
|
158
|
+
printf 'export DEFAULT_DS=%q\n' "$DEFAULT_DS"
|
|
159
|
+
printf 'export KNOWN_DS=%q\n' "$KNOWN_DS"
|
|
160
|
+
printf 'export ACCENT_STRATEGY=%q\n' "$ACCENT_STRATEGY"
|
|
161
|
+
printf 'export COLOR_SPACE=%q\n' "$COLOR_SPACE"
|
|
162
|
+
printf 'export DEPS_OK=%s\n' "$DEPS_OK"
|
|
163
|
+
printf 'export DEPS_MISSING=%q\n' "$DEPS_MISSING"
|
|
164
|
+
printf 'export SERVER_UP=%s\n' "$SERVER_UP"
|
|
165
|
+
printf 'export SERVER_PORT=%q\n' "$SERVER_PORT"
|
|
166
|
+
if [ "$SHAPE" = "edit" ] || [ "$SHAPE" = "all" ]; then
|
|
167
|
+
printf 'export ACTIVE_CANVAS=%q\n' "$ACTIVE_CANVAS"
|
|
168
|
+
printf 'export SELECTED_FILE=%q\n' "$SELECTED_FILE"
|
|
169
|
+
printf 'export SEL_VALID=%s\n' "$SEL_VALID"
|
|
170
|
+
printf 'export OPEN_TABS=%s\n' "$OPEN_TABS"
|
|
171
|
+
printf 'export ACTIVE_SLUG=%q\n' "$ACTIVE_SLUG"
|
|
172
|
+
fi
|
|
173
|
+
exit 0
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
# JSON mode — emit via jq when available for correct escaping; fall back to printf.
|
|
177
|
+
if [ "$HAVE_JQ" = "1" ]; then
|
|
178
|
+
jq -n \
|
|
179
|
+
--arg repo "$REPO" --arg cfg "$CONFIG_PRESENT" --arg name "$NAME" \
|
|
180
|
+
--arg dr "$DESIGN_ROOT" --arg rc "$ROOT_CLASS" --arg th "$THEME" \
|
|
181
|
+
--arg tok "$TOKENS_REL" --arg ncd "$NEW_CANVAS_DIR" --arg ncm "$NEW_COMPONENT_DIR" \
|
|
182
|
+
--arg acc "$TEAM_ACCENT" --arg dds "$DEFAULT_DS" --arg kds "$KNOWN_DS" \
|
|
183
|
+
--arg astrat "$ACCENT_STRATEGY" --arg cspace "$COLOR_SPACE" \
|
|
184
|
+
--arg depsok "$DEPS_OK" --arg depsmiss "$DEPS_MISSING" \
|
|
185
|
+
--arg sup "$SERVER_UP" --arg sport "$SERVER_PORT" \
|
|
186
|
+
--arg act "$ACTIVE_CANVAS" --arg selfile "$SELECTED_FILE" \
|
|
187
|
+
--arg selv "$SEL_VALID" --arg tabs "$OPEN_TABS" --arg slug "$ACTIVE_SLUG" \
|
|
188
|
+
--argjson sel "${SELECTED_ELEMENT:-null}" \
|
|
189
|
+
'{
|
|
190
|
+
repo_root: $repo,
|
|
191
|
+
config_present: ($cfg == "true"),
|
|
192
|
+
config: {
|
|
193
|
+
name: $name, design_root: $dr, root_class: $rc, theme: $th,
|
|
194
|
+
tokens_rel: $tok, new_canvas_dir: $ncd, new_component_dir: $ncm,
|
|
195
|
+
team_accent: $acc, default_ds: $dds,
|
|
196
|
+
known_ds: ($kds | if . == "" then [] else split(" ") end),
|
|
197
|
+
accent_strategy: $astrat, color_space: $cspace
|
|
198
|
+
},
|
|
199
|
+
active: {
|
|
200
|
+
canvas: $act, selected: $sel, selected_file: $selfile,
|
|
201
|
+
sel_valid: ($selv == "1"), open_tabs: ($tabs | tonumber? // 0), slug: $slug
|
|
202
|
+
},
|
|
203
|
+
deps: { ok: $depsok, missing: ($depsmiss | if . == "" then [] else split(",") end) },
|
|
204
|
+
server: { up: ($sup == "true"), port: ($sport | if . == "" then null else (tonumber? // .) end) }
|
|
205
|
+
}'
|
|
206
|
+
else
|
|
207
|
+
printf '{"repo_root":"%s","config_present":%s,"jq":false,"note":"jq unavailable — install jq for full prep.sh output"}\n' \
|
|
208
|
+
"$REPO" "$CONFIG_PRESENT"
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
exit 0
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scenario-report.mjs — deterministic cross-platform scenario report generator.
|
|
3
|
+
//
|
|
4
|
+
// Phase C / DDR-061. Resolves the long-standing "report generator" TODO in
|
|
5
|
+
// plugins/flow/skills/scenario/SKILL.md. Walks a scenario run directory and
|
|
6
|
+
// emits the mechanical sections of report.md (TL;DR table, counter-delta table,
|
|
7
|
+
// per-step pivot, collapsed path-listing). The LLM authors ONLY the prose
|
|
8
|
+
// sections ("What surprised us", "Recommended follow-ups") — they're left as
|
|
9
|
+
// marked placeholders for it to fill in.
|
|
10
|
+
//
|
|
11
|
+
// Invoked from flow markdown via the on-PATH `maude` binary (DDR-062):
|
|
12
|
+
// maude scenario-report <run-dir> [--out <path>]
|
|
13
|
+
// or directly:
|
|
14
|
+
// node scenario-report.mjs <run-dir> [--out <path>]
|
|
15
|
+
//
|
|
16
|
+
// Input layout (produced by the scenario runners — see scenario SKILL.md):
|
|
17
|
+
// <run-dir>/
|
|
18
|
+
// ├── <platform>/result.txt # "pass" | "fail: <reason>" | "skipped: <reason>"
|
|
19
|
+
// ├── <platform>/step-<N>-<desc>.png # per-step screenshots
|
|
20
|
+
// └── <platform>/counters.json # OPTIONAL { "mastered": "+3", "remaining": "-3" }
|
|
21
|
+
//
|
|
22
|
+
// Output: <run-dir>/report.md (or --out), zero external dependencies (node:fs only).
|
|
23
|
+
|
|
24
|
+
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
25
|
+
import { basename, join } from 'node:path';
|
|
26
|
+
|
|
27
|
+
const PLATFORM_ORDER = ['web-desktop', 'web-mobile', 'ios-phone', 'ios-tablet', 'android-phone'];
|
|
28
|
+
|
|
29
|
+
const toolingFor = (platform) => (platform.startsWith('web-') ? 'agent-browser' : 'agent-device');
|
|
30
|
+
|
|
31
|
+
// Parse "pass" / "fail: reason" / "skipped: reason" → { result, reason }.
|
|
32
|
+
function parseResult(text) {
|
|
33
|
+
const t = (text || '').trim();
|
|
34
|
+
if (!t) return { result: 'UNKNOWN', reason: 'no result.txt' };
|
|
35
|
+
const lower = t.toLowerCase();
|
|
36
|
+
if (lower.startsWith('pass')) return { result: 'PASS', reason: '' };
|
|
37
|
+
if (lower.startsWith('fail')) return { result: 'FAIL', reason: t.replace(/^fail[:\s]*/i, '') };
|
|
38
|
+
if (lower.startsWith('skip'))
|
|
39
|
+
return { result: 'SKIPPED', reason: t.replace(/^skip(ped)?[:\s]*/i, '') };
|
|
40
|
+
return { result: 'UNKNOWN', reason: t.slice(0, 80) };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const SYM = { PASS: '✅', FAIL: '❌', SKIPPED: '⏭️', UNKNOWN: '❓' };
|
|
44
|
+
|
|
45
|
+
// Extract the numeric step index from "step-7-card-1-back.png" → 7.
|
|
46
|
+
function stepIndex(file) {
|
|
47
|
+
const m = /^step-(\d+)/.exec(file);
|
|
48
|
+
return m ? Number(m[1]) : Number.POSITIVE_INFINITY;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function readPlatform(runDir, platform) {
|
|
52
|
+
const dir = join(runDir, platform);
|
|
53
|
+
if (!existsSync(dir) || !statSync(dir).isDirectory()) return null;
|
|
54
|
+
const resultPath = join(dir, 'result.txt');
|
|
55
|
+
const { result, reason } = parseResult(
|
|
56
|
+
existsSync(resultPath) ? readFileSync(resultPath, 'utf8') : ''
|
|
57
|
+
);
|
|
58
|
+
const steps = readdirSync(dir)
|
|
59
|
+
.filter((f) => /^step-\d+.*\.png$/.test(f))
|
|
60
|
+
.sort((a, b) => stepIndex(a) - stepIndex(b) || a.localeCompare(b));
|
|
61
|
+
let counters = null;
|
|
62
|
+
const countersPath = join(dir, 'counters.json');
|
|
63
|
+
if (existsSync(countersPath)) {
|
|
64
|
+
try {
|
|
65
|
+
counters = JSON.parse(readFileSync(countersPath, 'utf8'));
|
|
66
|
+
} catch {
|
|
67
|
+
/* malformed counters — treated as absent */
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { platform, result, reason, steps, counters };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function generateReport(runDir) {
|
|
74
|
+
if (!existsSync(runDir) || !statSync(runDir).isDirectory()) {
|
|
75
|
+
throw new Error(`scenario-report: run dir not found: ${runDir}`);
|
|
76
|
+
}
|
|
77
|
+
// Discover platforms: known order first, then any extra subdirs present.
|
|
78
|
+
const present = readdirSync(runDir).filter((n) => {
|
|
79
|
+
const p = join(runDir, n);
|
|
80
|
+
return statSync(p).isDirectory() && existsSync(join(p, 'result.txt'));
|
|
81
|
+
});
|
|
82
|
+
const ordered = [
|
|
83
|
+
...PLATFORM_ORDER.filter((p) => present.includes(p)),
|
|
84
|
+
...present.filter((p) => !PLATFORM_ORDER.includes(p)),
|
|
85
|
+
];
|
|
86
|
+
const platforms = ordered.map((p) => readPlatform(runDir, p)).filter(Boolean);
|
|
87
|
+
|
|
88
|
+
const scenarioName =
|
|
89
|
+
basename(join(runDir, '..', '..')) === '.' ? 'scenario' : basename(join(runDir, '..'));
|
|
90
|
+
const out = [];
|
|
91
|
+
out.push(`# Scenario report — ${scenarioName}`);
|
|
92
|
+
out.push('');
|
|
93
|
+
out.push(`_generated ${new Date().toISOString()} · run: \`${runDir}\`_`);
|
|
94
|
+
out.push('');
|
|
95
|
+
|
|
96
|
+
// ── TL;DR ──────────────────────────────────────────────────────────────
|
|
97
|
+
out.push('## TL;DR');
|
|
98
|
+
out.push('');
|
|
99
|
+
out.push('| Platform | Result | Steps reached | Tooling |');
|
|
100
|
+
out.push('| --- | --- | --- | --- |');
|
|
101
|
+
for (const p of platforms) {
|
|
102
|
+
const detail = p.reason ? ` — ${p.reason}` : '';
|
|
103
|
+
out.push(
|
|
104
|
+
`| ${p.platform} | ${SYM[p.result]} ${p.result}${detail} | ${p.steps.length} | ${toolingFor(p.platform)} |`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
out.push('');
|
|
108
|
+
|
|
109
|
+
// ── Counter-delta ──────────────────────────────────────────────────────
|
|
110
|
+
const counterKeys = [
|
|
111
|
+
...new Set(platforms.flatMap((p) => (p.counters ? Object.keys(p.counters) : []))),
|
|
112
|
+
];
|
|
113
|
+
out.push('## Counter delta (cross-platform parity)');
|
|
114
|
+
out.push('');
|
|
115
|
+
if (counterKeys.length === 0) {
|
|
116
|
+
out.push(
|
|
117
|
+
'_No `counters.json` recorded for any platform. Add `<platform>/counters.json` ' +
|
|
118
|
+
'(e.g. `{"mastered":"+3","remaining":"-3"}`) in the runners to enable the parity check._'
|
|
119
|
+
);
|
|
120
|
+
} else {
|
|
121
|
+
out.push(`| Platform | ${counterKeys.map((k) => `\`${k}\` Δ`).join(' | ')} |`);
|
|
122
|
+
out.push(`| --- | ${counterKeys.map(() => '---').join(' | ')} |`);
|
|
123
|
+
for (const p of platforms) {
|
|
124
|
+
const cells = counterKeys.map((k) =>
|
|
125
|
+
p.counters && p.counters[k] != null ? p.counters[k] : '—'
|
|
126
|
+
);
|
|
127
|
+
out.push(`| ${p.platform} | ${cells.join(' | ')} |`);
|
|
128
|
+
}
|
|
129
|
+
out.push('');
|
|
130
|
+
// Parity verdict: identical non-skipped rows = verified.
|
|
131
|
+
const nonSkipped = platforms.filter((p) => p.result !== 'SKIPPED' && p.counters);
|
|
132
|
+
const sigs = new Set(nonSkipped.map((p) => counterKeys.map((k) => p.counters[k]).join('|')));
|
|
133
|
+
out.push(
|
|
134
|
+
sigs.size <= 1
|
|
135
|
+
? '**Parity: ✅ identical counter deltas across all non-skipped platforms.**'
|
|
136
|
+
: '**Parity: ❌ counter deltas diverge across platforms — investigate (a divergence needs a DDR or a fix).**'
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
out.push('');
|
|
140
|
+
|
|
141
|
+
// ── Per-step pivot ─────────────────────────────────────────────────────
|
|
142
|
+
const allStepIdx = [
|
|
143
|
+
...new Set(platforms.flatMap((p) => p.steps.map(stepIndex)).filter(Number.isFinite)),
|
|
144
|
+
].sort((a, b) => a - b);
|
|
145
|
+
out.push('## Per-step screenshots (pivot)');
|
|
146
|
+
out.push('');
|
|
147
|
+
if (allStepIdx.length === 0) {
|
|
148
|
+
out.push('_No `step-*.png` screenshots found in any platform directory._');
|
|
149
|
+
} else {
|
|
150
|
+
out.push(`| Platform | ${allStepIdx.map((n) => `Step ${n}`).join(' | ')} |`);
|
|
151
|
+
out.push(`| --- | ${allStepIdx.map(() => '---').join(' | ')} |`);
|
|
152
|
+
for (const p of platforms) {
|
|
153
|
+
const cells = allStepIdx.map((n) => {
|
|
154
|
+
const file = p.steps.find((f) => stepIndex(f) === n);
|
|
155
|
+
return file ? `` : '';
|
|
156
|
+
});
|
|
157
|
+
out.push(`| ${p.platform} | ${cells.join(' | ')} |`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
out.push('');
|
|
161
|
+
|
|
162
|
+
// ── LLM prose placeholders ─────────────────────────────────────────────
|
|
163
|
+
out.push('## What surprised us');
|
|
164
|
+
out.push('');
|
|
165
|
+
out.push(
|
|
166
|
+
'<!-- LLM-AUTHORED: replace this line with non-obvious findings — UX divergence, broken expectations, unexpected counter math, mid-run state changes. Delete this comment. -->'
|
|
167
|
+
);
|
|
168
|
+
out.push('');
|
|
169
|
+
out.push('## Recommended follow-ups');
|
|
170
|
+
out.push('');
|
|
171
|
+
out.push(
|
|
172
|
+
'<!-- LLM-AUTHORED: replace this line with a prioritized list of codebase changes that would make this scenario more reliable (missing testIDs, fragile selectors, parity gaps). Delete this comment. -->'
|
|
173
|
+
);
|
|
174
|
+
out.push('');
|
|
175
|
+
|
|
176
|
+
// ── Collapsed path listing ─────────────────────────────────────────────
|
|
177
|
+
out.push('<details>');
|
|
178
|
+
out.push('<summary>All screenshot paths (per platform)</summary>');
|
|
179
|
+
out.push('');
|
|
180
|
+
for (const p of platforms) {
|
|
181
|
+
out.push(`**${p.platform}** (${p.result})`);
|
|
182
|
+
for (const f of p.steps) out.push(`- \`${p.platform}/${f}\``);
|
|
183
|
+
out.push('');
|
|
184
|
+
}
|
|
185
|
+
out.push('</details>');
|
|
186
|
+
out.push('');
|
|
187
|
+
|
|
188
|
+
return out.join('\n');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ── CLI entrypoint ───────────────────────────────────────────────────────
|
|
192
|
+
function main(argv) {
|
|
193
|
+
const args = argv.slice(2);
|
|
194
|
+
const runDir = args.find((a) => !a.startsWith('--'));
|
|
195
|
+
if (!runDir) {
|
|
196
|
+
process.stderr.write('usage: scenario-report <run-dir> [--out <path>]\n');
|
|
197
|
+
process.exit(2);
|
|
198
|
+
}
|
|
199
|
+
const outIdx = args.indexOf('--out');
|
|
200
|
+
const outPath = outIdx >= 0 ? args[outIdx + 1] : join(runDir, 'report.md');
|
|
201
|
+
const md = generateReport(runDir);
|
|
202
|
+
writeFileSync(outPath, md, 'utf8');
|
|
203
|
+
process.stdout.write(`${outPath}\n`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Run as a script (not when imported). `import.meta.main` is Bun; the argv[1]
|
|
207
|
+
// check covers Node.
|
|
208
|
+
const invokedDirectly = import.meta.main || process.argv[1]?.endsWith('scenario-report.mjs');
|
|
209
|
+
if (invokedDirectly) main(process.argv);
|
|
@@ -64,6 +64,19 @@ else
|
|
|
64
64
|
[ -z "$OUT" ] && { echo "screenshot.sh: --out required for single-shot modes" >&2; exit 2; }
|
|
65
65
|
fi
|
|
66
66
|
|
|
67
|
+
# agent-browser ignores RELATIVE screenshot paths (it writes to its own
|
|
68
|
+
# ~/.agent-browser/tmp instead), which strands the PNG and trips the
|
|
69
|
+
# missing-file guard below. Canonicalize OUT / OUT_DIR to absolute up front so
|
|
70
|
+
# captures land where the caller asked.
|
|
71
|
+
if [ -n "$OUT" ]; then
|
|
72
|
+
mkdir -p "$(dirname "$OUT")" 2>/dev/null || true
|
|
73
|
+
OUT="$(cd "$(dirname "$OUT")" 2>/dev/null && pwd)/$(basename "$OUT")"
|
|
74
|
+
fi
|
|
75
|
+
if [ -n "$OUT_DIR" ]; then
|
|
76
|
+
mkdir -p "$OUT_DIR" 2>/dev/null || true
|
|
77
|
+
OUT_DIR="$(cd "$OUT_DIR" 2>/dev/null && pwd)"
|
|
78
|
+
fi
|
|
79
|
+
|
|
67
80
|
# TSX specimens cannot be opened via file:// — the browser would see raw JSX.
|
|
68
81
|
# They must go through the dev-server route (http://localhost:PORT/<rel>),
|
|
69
82
|
# which transpiles via _canvas-shell.html?canvas=<rel>. Phase 19 / DDR-044.
|
|
@@ -97,7 +110,11 @@ if [ -z "$URL" ]; then
|
|
|
97
110
|
|
|
98
111
|
# URL-encode spaces (rough); leave other chars alone.
|
|
99
112
|
ACTIVE_ENC=$(printf '%s' "$ACTIVE" | sed 's/ /%20/g')
|
|
100
|
-
|
|
113
|
+
# Canvases mount through the canvas shell. The bare `/<rel>` route 404s when
|
|
114
|
+
# the canvas-origin sandbox is on (default since phase-9.1); only
|
|
115
|
+
# `/_canvas-shell.html?canvas=<rel>` renders the canvas (valid in both
|
|
116
|
+
# split-on and legacy same-origin modes).
|
|
117
|
+
URL="http://localhost:${PORT}/_canvas-shell.html?canvas=${ACTIVE_ENC}"
|
|
101
118
|
fi
|
|
102
119
|
|
|
103
120
|
# ---------- selector mapping ----------
|