start-vibing 4.3.0 → 4.3.2
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/package.json +2 -2
- package/template/.claude/agents/sd-audit.md +121 -2
- package/template/.claude/agents/sd-fix.md +11 -0
- package/template/.claude/agents/sd-research.md +49 -2
- package/template/.claude/skills/super-design/.schema-version +1 -0
- package/template/.claude/skills/super-design/SKILL.md +94 -2
- package/template/.claude/skills/super-design/audit-state.schema.json +226 -0
- package/template/.claude/skills/super-design/references/audit-methodology.md +118 -0
- package/template/.claude/skills/super-design/references/design-intelligence-rubric.md +92 -11
- package/template/.claude/skills/super-design/references/design-skills-catalog.md +31 -0
- package/template/.claude/skills/super-design/scripts/build-import-graph.sh +208 -0
- package/template/.claude/skills/super-design/scripts/detect-apps.sh +180 -0
- package/template/.claude/skills/super-design/scripts/detect-changes.sh +177 -21
- package/template/.claude/skills/super-design/scripts/discover-routes.sh +120 -6
- package/template/.claude/skills/super-design/scripts/extract-tokens.mjs +165 -4
- package/template/.claude/skills/super-design/scripts/hash-pages.sh +209 -23
- package/template/.claude/skills/super-design/scripts/setup-git-notes.sh +21 -0
- package/template/.claude/skills/super-design/scripts/validate-state.sh +74 -11
- package/template/.claude/skills/super-design/scripts/verify-audit.sh +62 -9
- package/template/.claude/skills/super-design/scripts/visual-regression.sh +275 -0
- package/template/.claude/skills/super-design/scripts/write-state.sh +53 -0
- package/template/.claude/skills/super-design/templates/audit-state.schema.json +0 -57
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Usage: visual-regression.sh [--update-baselines] [<state_file>]
|
|
3
|
+
#
|
|
4
|
+
# Expected `.audit-state.json` `visual_regression` block (artifact §16
|
|
5
|
+
# lines 1367-1384). Kept inline here so the script self-documents the
|
|
6
|
+
# schema shape when audit-state.schema.json is not yet present.
|
|
7
|
+
#
|
|
8
|
+
# "visual_regression": {
|
|
9
|
+
# "enabled": true,
|
|
10
|
+
# "engine": "pixelmatch", // pixelmatch | odiff | sha256-fallback
|
|
11
|
+
# "threshold": 0.1, // per-pixel YIQ (pixelmatch/odiff)
|
|
12
|
+
# "max_diff_pixel_ratio": 0.01, // 1% pixels allowed
|
|
13
|
+
# "antialiasing": true,
|
|
14
|
+
# "viewports": [
|
|
15
|
+
# { "label": "mobile_375", "width": 375, "height": 667 },
|
|
16
|
+
# { "label": "tablet_768", "width": 768, "height": 1024 },
|
|
17
|
+
# { "label": "desktop_1280","width": 1280, "height": 800 }
|
|
18
|
+
# ],
|
|
19
|
+
# "mask_selectors": [
|
|
20
|
+
# "[data-timestamp]", ".relative-time",
|
|
21
|
+
# "[data-react-hydration]", "video", "canvas"
|
|
22
|
+
# ],
|
|
23
|
+
# "baseline_dir": ".super-design/baselines",
|
|
24
|
+
# "current_dir": "docs/super-design/.cache/hashes/screenshots",
|
|
25
|
+
# "diff_dir": "docs/super-design/.cache/hashes/diffs"
|
|
26
|
+
# }
|
|
27
|
+
#
|
|
28
|
+
# Runner behaviour:
|
|
29
|
+
# - Reads the block from .audit-state.json (default path overridable via
|
|
30
|
+
# positional arg).
|
|
31
|
+
# - For every (page, viewport) it finds a current screenshot for, locates
|
|
32
|
+
# the matching baseline PNG, runs the configured diff engine, emits
|
|
33
|
+
# {page, viewport, diff_ratio, threshold, pass, diff_image_path} into
|
|
34
|
+
# <diff_dir>/results.json.
|
|
35
|
+
# - Engine fallback chain: pixelmatch (npx) → odiff (npx) → sha256
|
|
36
|
+
# equality (logs warning on stderr).
|
|
37
|
+
# - --update-baselines replaces every baseline with the current capture
|
|
38
|
+
# and exits without diffing.
|
|
39
|
+
#
|
|
40
|
+
# POSIX sh + node; runs under Windows git-bash.
|
|
41
|
+
set -euo pipefail
|
|
42
|
+
|
|
43
|
+
UPDATE_BASELINES=0
|
|
44
|
+
STATE="docs/super-design/.audit-state.json"
|
|
45
|
+
while (( $# )); do
|
|
46
|
+
case "$1" in
|
|
47
|
+
--update-baselines) UPDATE_BASELINES=1 ;;
|
|
48
|
+
-h|--help)
|
|
49
|
+
grep '^#' "$0" | sed 's/^# \{0,1\}//'
|
|
50
|
+
exit 0
|
|
51
|
+
;;
|
|
52
|
+
*) STATE="$1" ;;
|
|
53
|
+
esac
|
|
54
|
+
shift
|
|
55
|
+
done
|
|
56
|
+
|
|
57
|
+
if [[ ! -f "$STATE" ]]; then
|
|
58
|
+
echo '{"status":"missing-state","hint":"run audit first"}' >&2
|
|
59
|
+
exit 2
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# Defaults match artifact §16. jq pulls the override from state, if any.
|
|
63
|
+
CFG="$(jq -c '.visual_regression // {}' "$STATE")"
|
|
64
|
+
ENABLED="$(jq -r '.enabled // false' <<<"$CFG")"
|
|
65
|
+
if [[ "$ENABLED" != "true" && "$UPDATE_BASELINES" -ne 1 ]]; then
|
|
66
|
+
echo '{"status":"disabled","hint":"set visual_regression.enabled=true"}'
|
|
67
|
+
exit 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
BASELINE_DIR="$(jq -r '.baseline_dir // ".super-design/baselines"' <<<"$CFG")"
|
|
71
|
+
CURRENT_DIR="$(jq -r '.current_dir // "docs/super-design/.cache/hashes/screenshots"' <<<"$CFG")"
|
|
72
|
+
DIFF_DIR="$(jq -r '.diff_dir // "docs/super-design/.cache/hashes/diffs"' <<<"$CFG")"
|
|
73
|
+
ENGINE="$(jq -r '.engine // "pixelmatch"' <<<"$CFG")"
|
|
74
|
+
THRESHOLD="$(jq -r '.threshold // 0.1' <<<"$CFG")"
|
|
75
|
+
MAX_RATIO="$(jq -r '.max_diff_pixel_ratio // 0.01' <<<"$CFG")"
|
|
76
|
+
ANTIALIAS="$(jq -r '.antialiasing // true' <<<"$CFG")"
|
|
77
|
+
|
|
78
|
+
mkdir -p "$BASELINE_DIR" "$DIFF_DIR"
|
|
79
|
+
|
|
80
|
+
# Enumerate current screenshots the hash-pages.sh pass wrote under
|
|
81
|
+
# <current_dir>/<url-encoded-url>/<viewport>.png.
|
|
82
|
+
if [[ ! -d "$CURRENT_DIR" ]]; then
|
|
83
|
+
echo "{\"status\":\"no-screenshots\",\"hint\":\"run hash-pages.sh first\",\"looked_in\":\"$CURRENT_DIR\"}" >&2
|
|
84
|
+
exit 2
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# --update-baselines: mirror current -> baseline and exit.
|
|
88
|
+
if [[ "$UPDATE_BASELINES" -eq 1 ]]; then
|
|
89
|
+
copied=0
|
|
90
|
+
while IFS= read -r -d '' png; do
|
|
91
|
+
rel="${png#$CURRENT_DIR/}"
|
|
92
|
+
dst="$BASELINE_DIR/$rel"
|
|
93
|
+
mkdir -p "$(dirname "$dst")"
|
|
94
|
+
cp -f "$png" "$dst"
|
|
95
|
+
copied=$((copied + 1))
|
|
96
|
+
done < <(find "$CURRENT_DIR" -type f -name '*.png' -print0)
|
|
97
|
+
echo "{\"status\":\"baselines-updated\",\"count\":$copied,\"baseline_dir\":\"$BASELINE_DIR\"}"
|
|
98
|
+
exit 0
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# Resolve engine availability up-front so we warn once rather than per page.
|
|
102
|
+
resolve_engine() {
|
|
103
|
+
local want="$1"
|
|
104
|
+
case "$want" in
|
|
105
|
+
pixelmatch)
|
|
106
|
+
if command -v npx >/dev/null 2>&1; then
|
|
107
|
+
if npx --yes pixelmatch --help >/dev/null 2>&1; then echo pixelmatch; return; fi
|
|
108
|
+
fi
|
|
109
|
+
;;
|
|
110
|
+
odiff)
|
|
111
|
+
if command -v npx >/dev/null 2>&1; then
|
|
112
|
+
if npx --yes odiff-bin --help >/dev/null 2>&1; then echo odiff; return; fi
|
|
113
|
+
fi
|
|
114
|
+
;;
|
|
115
|
+
esac
|
|
116
|
+
# Chain: pixelmatch > odiff > sha256.
|
|
117
|
+
if command -v npx >/dev/null 2>&1; then
|
|
118
|
+
if npx --yes pixelmatch --help >/dev/null 2>&1; then echo pixelmatch; return; fi
|
|
119
|
+
if npx --yes odiff-bin --help >/dev/null 2>&1; then echo odiff; return; fi
|
|
120
|
+
fi
|
|
121
|
+
echo sha256-fallback
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
RESOLVED_ENGINE="$(resolve_engine "$ENGINE")"
|
|
125
|
+
if [[ "$RESOLVED_ENGINE" == "sha256-fallback" ]]; then
|
|
126
|
+
echo "warning: pixelmatch/odiff unavailable; falling back to sha256 equality (binary pass/fail, diff_ratio will be 0 or 1)" >&2
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# Per-comparison worker. Runs in a node one-shot because pixelmatch /
|
|
130
|
+
# odiff are both invoked there; sha256 fallback is pure node too.
|
|
131
|
+
compare_pair() {
|
|
132
|
+
local baseline="$1"
|
|
133
|
+
local current="$2"
|
|
134
|
+
local diff_out="$3"
|
|
135
|
+
|
|
136
|
+
BASE="$baseline" CURR="$current" DIFF="$diff_out" \
|
|
137
|
+
ENGINE="$RESOLVED_ENGINE" THRESHOLD="$THRESHOLD" \
|
|
138
|
+
ANTIALIAS="$ANTIALIAS" \
|
|
139
|
+
node --experimental-vm-modules <<'JS'
|
|
140
|
+
import { createHash } from "node:crypto";
|
|
141
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync } from "node:fs";
|
|
142
|
+
import { dirname } from "node:path";
|
|
143
|
+
import { spawnSync } from "node:child_process";
|
|
144
|
+
|
|
145
|
+
const base = process.env.BASE;
|
|
146
|
+
const curr = process.env.CURR;
|
|
147
|
+
const diffPath = process.env.DIFF;
|
|
148
|
+
const engine = process.env.ENGINE;
|
|
149
|
+
const threshold = Number(process.env.THRESHOLD);
|
|
150
|
+
const antialias = process.env.ANTIALIAS === "true";
|
|
151
|
+
|
|
152
|
+
mkdirSync(dirname(diffPath), { recursive: true });
|
|
153
|
+
|
|
154
|
+
function sha(buf) { return createHash("sha256").update(buf).digest("hex"); }
|
|
155
|
+
|
|
156
|
+
if (!existsSync(base)) {
|
|
157
|
+
console.log(JSON.stringify({
|
|
158
|
+
pass: false, diff_ratio: 1, reason: "baseline-missing",
|
|
159
|
+
engine, diff_image_path: null,
|
|
160
|
+
}));
|
|
161
|
+
process.exit(0);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (engine === "sha256-fallback") {
|
|
165
|
+
const bHash = sha(readFileSync(base));
|
|
166
|
+
const cHash = sha(readFileSync(curr));
|
|
167
|
+
const ratio = bHash === cHash ? 0 : 1;
|
|
168
|
+
console.log(JSON.stringify({
|
|
169
|
+
pass: ratio === 0, diff_ratio: ratio, engine,
|
|
170
|
+
diff_image_path: null,
|
|
171
|
+
}));
|
|
172
|
+
process.exit(0);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (engine === "pixelmatch") {
|
|
176
|
+
// npx pixelmatch <baseline> <current> <diff> [threshold]
|
|
177
|
+
const r = spawnSync("npx", [
|
|
178
|
+
"--yes", "pixelmatch", base, curr, diffPath, String(threshold),
|
|
179
|
+
], { encoding: "utf8", shell: process.platform === "win32" });
|
|
180
|
+
// pixelmatch prints e.g. "error: 123 different pixels\n" to stdout.
|
|
181
|
+
// total-pixels isn't emitted, so we read PNG dimensions from the diff
|
|
182
|
+
// header (the diff PNG is always produced even on zero-diff).
|
|
183
|
+
const diffPixels = Number((r.stdout.match(/(\d+)\s+different/) || [0, 0])[1]);
|
|
184
|
+
let total = 1;
|
|
185
|
+
if (existsSync(diffPath)) {
|
|
186
|
+
const buf = readFileSync(diffPath);
|
|
187
|
+
// PNG width/height live at bytes 16..23 big-endian.
|
|
188
|
+
if (buf.length >= 24 && buf[0] === 0x89 && buf[1] === 0x50) {
|
|
189
|
+
const w = buf.readUInt32BE(16);
|
|
190
|
+
const h = buf.readUInt32BE(20);
|
|
191
|
+
total = Math.max(1, w * h);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const ratio = diffPixels / total;
|
|
195
|
+
console.log(JSON.stringify({
|
|
196
|
+
pass: null, // decided by caller against max_diff_pixel_ratio
|
|
197
|
+
diff_ratio: ratio, diff_pixels: diffPixels, total_pixels: total,
|
|
198
|
+
engine, diff_image_path: diffPath,
|
|
199
|
+
exit: r.status, stderr: (r.stderr || "").trim().slice(0, 500),
|
|
200
|
+
}));
|
|
201
|
+
process.exit(0);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (engine === "odiff") {
|
|
205
|
+
// odiff-bin <baseline> <current> <diff> --threshold=<t>
|
|
206
|
+
const args = [
|
|
207
|
+
"--yes", "odiff-bin", base, curr, diffPath,
|
|
208
|
+
`--threshold=${threshold}`,
|
|
209
|
+
];
|
|
210
|
+
if (!antialias) args.push("--antialiasing");
|
|
211
|
+
const r = spawnSync("npx", args, {
|
|
212
|
+
encoding: "utf8", shell: process.platform === "win32",
|
|
213
|
+
});
|
|
214
|
+
const matchRatio = (r.stdout + r.stderr).match(/(\d+(?:\.\d+)?)\s*%/);
|
|
215
|
+
const pctDiff = matchRatio ? Number(matchRatio[1]) / 100 : (r.status === 0 ? 0 : 1);
|
|
216
|
+
console.log(JSON.stringify({
|
|
217
|
+
pass: null,
|
|
218
|
+
diff_ratio: pctDiff,
|
|
219
|
+
engine, diff_image_path: diffPath,
|
|
220
|
+
exit: r.status, stderr: (r.stderr || "").trim().slice(0, 500),
|
|
221
|
+
}));
|
|
222
|
+
process.exit(0);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Unknown engine — surface as a hard failure rather than silent pass.
|
|
226
|
+
console.log(JSON.stringify({
|
|
227
|
+
pass: false, diff_ratio: 1,
|
|
228
|
+
reason: `unknown-engine:${engine}`, engine, diff_image_path: null,
|
|
229
|
+
}));
|
|
230
|
+
JS
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
results="[]"
|
|
234
|
+
any_fail=0
|
|
235
|
+
while IFS= read -r -d '' curr_png; do
|
|
236
|
+
rel="${curr_png#$CURRENT_DIR/}" # e.g. https%3A%2F%2F.../mobile_375.png
|
|
237
|
+
page_dir="$(dirname "$rel")"
|
|
238
|
+
viewport="$(basename "$rel" .png)"
|
|
239
|
+
page_url="$(printf '%s' "$page_dir" | node -e 'process.stdin.on("data",d=>process.stdout.write(decodeURIComponent(d.toString())))')"
|
|
240
|
+
base_png="$BASELINE_DIR/$rel"
|
|
241
|
+
diff_png="$DIFF_DIR/$rel"
|
|
242
|
+
|
|
243
|
+
raw="$(compare_pair "$base_png" "$curr_png" "$diff_png" || echo '{"pass":false,"diff_ratio":1,"reason":"compare-error"}')"
|
|
244
|
+
|
|
245
|
+
# Decide pass/fail against max_diff_pixel_ratio when engine left it null.
|
|
246
|
+
merged="$(jq -c \
|
|
247
|
+
--arg page "$page_url" \
|
|
248
|
+
--arg viewport "$viewport" \
|
|
249
|
+
--argjson threshold "$THRESHOLD" \
|
|
250
|
+
--argjson max_ratio "$MAX_RATIO" \
|
|
251
|
+
'. as $r
|
|
252
|
+
| .pass = (if .pass == null then (.diff_ratio <= $max_ratio) else .pass end)
|
|
253
|
+
| .page = $page
|
|
254
|
+
| .viewport = $viewport
|
|
255
|
+
| .threshold = $threshold
|
|
256
|
+
| .max_diff_pixel_ratio = $max_ratio' <<<"$raw")"
|
|
257
|
+
|
|
258
|
+
if [[ "$(jq -r '.pass' <<<"$merged")" != "true" ]]; then any_fail=1; fi
|
|
259
|
+
results="$(jq -c --argjson m "$merged" '. + [$m]' <<<"$results")"
|
|
260
|
+
done < <(find "$CURRENT_DIR" -type f -name '*.png' -print0)
|
|
261
|
+
|
|
262
|
+
out_file="$DIFF_DIR/results.json"
|
|
263
|
+
mkdir -p "$DIFF_DIR"
|
|
264
|
+
jq -n --argjson r "$results" \
|
|
265
|
+
--arg engine "$RESOLVED_ENGINE" \
|
|
266
|
+
--argjson threshold "$THRESHOLD" \
|
|
267
|
+
--argjson max_ratio "$MAX_RATIO" \
|
|
268
|
+
'{engine:$engine, threshold:$threshold, max_diff_pixel_ratio:$max_ratio,
|
|
269
|
+
results:$r, count: ($r|length),
|
|
270
|
+
passed: ([$r[] | select(.pass==true)] | length),
|
|
271
|
+
failed: ([$r[] | select(.pass!=true)] | length)}' \
|
|
272
|
+
>"$out_file"
|
|
273
|
+
|
|
274
|
+
cat "$out_file"
|
|
275
|
+
exit $any_fail
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Usage: write-state.sh [<app_path>] [<explicit_target>]
|
|
3
|
+
# (reads JSON body from stdin)
|
|
4
|
+
#
|
|
5
|
+
# Atomic state writer: accepts JSON on stdin, writes to <target>.tmp,
|
|
6
|
+
# validates with jq, then renames in place. Referenced from
|
|
7
|
+
# docs/compass_artifact §11 ("Write-then-rename (atomic)") and SKILL.md
|
|
8
|
+
# Step 4 ("Atomic write .audit-state.json").
|
|
9
|
+
#
|
|
10
|
+
# Monorepo support (artifact §11 line 902): first positional arg is the
|
|
11
|
+
# app root (e.g. `apps/web`). Target state path is derived as
|
|
12
|
+
# `<app_path>/docs/super-design/.audit-state.json`. For single-app
|
|
13
|
+
# repos pass "." or omit (default behavior preserved).
|
|
14
|
+
# Back-compat: if the first arg already ends in `.audit-state.json`, it
|
|
15
|
+
# is treated as an explicit target path (legacy one-arg call sites).
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
APP_PATH="${1:-.}"
|
|
19
|
+
EXPLICIT="${2:-}"
|
|
20
|
+
|
|
21
|
+
if [ -n "$EXPLICIT" ]; then
|
|
22
|
+
TARGET="$EXPLICIT"
|
|
23
|
+
else
|
|
24
|
+
case "$APP_PATH" in
|
|
25
|
+
*.audit-state.json)
|
|
26
|
+
# Legacy single-arg call: treat $APP_PATH as the full target path.
|
|
27
|
+
TARGET="$APP_PATH"
|
|
28
|
+
;;
|
|
29
|
+
.|"")
|
|
30
|
+
TARGET="docs/super-design/.audit-state.json"
|
|
31
|
+
;;
|
|
32
|
+
*)
|
|
33
|
+
TARGET="${APP_PATH%/}/docs/super-design/.audit-state.json"
|
|
34
|
+
;;
|
|
35
|
+
esac
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
TMP="${TARGET}.tmp"
|
|
39
|
+
|
|
40
|
+
mkdir -p "$(dirname "$TARGET")"
|
|
41
|
+
|
|
42
|
+
# Drain stdin into the tmp file.
|
|
43
|
+
cat >"$TMP"
|
|
44
|
+
|
|
45
|
+
# Validate it is parseable JSON before swapping.
|
|
46
|
+
if ! jq -e 'type == "object"' "$TMP" >/dev/null 2>&1; then
|
|
47
|
+
rm -f "$TMP"
|
|
48
|
+
echo '{"error":"invalid-json-on-stdin"}' >&2
|
|
49
|
+
exit 2
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
mv -f "$TMP" "$TARGET"
|
|
53
|
+
echo "{\"status\":\"written\",\"path\":\"$TARGET\"}"
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "super-design/audit-state.schema.json",
|
|
4
|
-
"type": "object",
|
|
5
|
-
"required": ["schema_version","skill_version","last_audit_at","git_sha_at_audit","theory_doc_sha","tools","pages_audited","findings_counts"],
|
|
6
|
-
"properties": {
|
|
7
|
-
"schema_version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" },
|
|
8
|
-
"skill_version": { "type": "string" },
|
|
9
|
-
"last_audit_at": { "type": "string", "format": "date-time" },
|
|
10
|
-
"git_sha_at_audit": { "type": "string", "pattern": "^[0-9a-f]{7,64}$" },
|
|
11
|
-
"git_branch": { "type": "string" },
|
|
12
|
-
"is_shallow_clone": { "type": "boolean" },
|
|
13
|
-
"theory_doc_sha": { "type": "string" },
|
|
14
|
-
"market_analysis_sha": { "type": "string" },
|
|
15
|
-
"tools": { "type": "object", "additionalProperties": { "type": "string" } },
|
|
16
|
-
"framework": {
|
|
17
|
-
"type": "object",
|
|
18
|
-
"properties": {
|
|
19
|
-
"name": { "type": "string" },
|
|
20
|
-
"router": { "type": "string" },
|
|
21
|
-
"version": { "type": "string" }
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
"route_map": { "type": "array", "items": { "type": "string" } },
|
|
25
|
-
"pages_audited": {
|
|
26
|
-
"type": "array",
|
|
27
|
-
"items": {
|
|
28
|
-
"type": "object",
|
|
29
|
-
"required": ["url","last_audited"],
|
|
30
|
-
"properties": {
|
|
31
|
-
"url": { "type": "string" },
|
|
32
|
-
"route_file": { "type": "string" },
|
|
33
|
-
"html_hash": { "type": "string" },
|
|
34
|
-
"dom_structure_hash": { "type": "string" },
|
|
35
|
-
"viewport_hashes": { "type": "object" },
|
|
36
|
-
"last_audited": { "type": "string", "format": "date-time" },
|
|
37
|
-
"findings_ids": { "type": "array", "items": { "type": "string" } }
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
"components": { "type": "object", "additionalProperties": { "type": "string" } },
|
|
42
|
-
"token_hash": { "type": "string" },
|
|
43
|
-
"import_graph_sha": { "type": "string" },
|
|
44
|
-
"findings_counts": {
|
|
45
|
-
"type": "object",
|
|
46
|
-
"required": ["blockers","high","medium","nitpicks"],
|
|
47
|
-
"properties": {
|
|
48
|
-
"blockers": { "type": "integer" },
|
|
49
|
-
"high": { "type": "integer" },
|
|
50
|
-
"medium": { "type": "integer" },
|
|
51
|
-
"nitpicks": { "type": "integer" }
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
"research_at": { "type": "string", "format": "date-time" },
|
|
55
|
-
"ignored_paths": { "type": "array", "items": { "type": "string" } }
|
|
56
|
-
}
|
|
57
|
-
}
|