@yojahny/wp-design-library 0.3.0 → 0.5.1
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/.env.example +7 -0
- package/README.md +52 -9
- package/bin/library.mjs +11 -0
- package/entries/ada/entry.md +55 -0
- package/entries/ada/strip.webp +0 -0
- package/entries/air/captures/2/measure.json +325 -0
- package/entries/air/captures/2/strip.webp +0 -0
- package/entries/air/entry.md +62 -0
- package/entries/air/strip.webp +0 -0
- package/entries/ais-community-dark-depth/strip.webp +0 -0
- package/entries/apple-home/captures/2/measure.json +91 -0
- package/entries/apple-home/captures/2/strip.webp +0 -0
- package/entries/apple-home/entry.md +64 -0
- package/entries/apple-home/strip.webp +0 -0
- package/entries/apple-macbook-neo/captures/2/measure.json +570 -0
- package/entries/apple-macbook-neo/captures/2/strip.webp +0 -0
- package/entries/apple-macbook-neo/entry.md +62 -0
- package/entries/apple-macbook-neo/strip.webp +0 -0
- package/entries/apple-watch-ultra-4/entry.md +63 -0
- package/entries/apple-watch-ultra-4/measure.json +481 -0
- package/entries/apple-watch-ultra-4/strip.webp +0 -0
- package/entries/bpco/captures/2/strip.webp +0 -0
- package/entries/bpco/captures/3/strip.webp +0 -0
- package/entries/bpco/entry.md +81 -0
- package/entries/bpco/strip.webp +0 -0
- package/entries/column/captures/2/measure.json +996 -0
- package/entries/column/captures/2/strip.webp +0 -0
- package/entries/column/entry.md +82 -0
- package/entries/column/strip.webp +0 -0
- package/entries/cthdrl/captures/2/measure.json +251 -0
- package/entries/cthdrl/captures/2/strip.webp +0 -0
- package/entries/cthdrl/entry.md +86 -0
- package/entries/cthdrl/strip.webp +0 -0
- package/entries/dala-craftedbygc/entry.md +69 -0
- package/entries/dala-craftedbygc/strip.webp +0 -0
- package/entries/dock/captures/2/measure.json +206 -0
- package/entries/dock/captures/2/strip.webp +0 -0
- package/entries/dock/entry.md +62 -0
- package/entries/dock/strip.webp +0 -0
- package/entries/dub/captures/2/measure.json +37 -0
- package/entries/dub/captures/2/strip.webp +0 -0
- package/entries/dub/entry.md +61 -0
- package/entries/dub/strip.webp +0 -0
- package/entries/duolingo/captures/2/measure.json +138 -0
- package/entries/duolingo/captures/2/strip.webp +0 -0
- package/entries/duolingo/entry.md +65 -0
- package/entries/duolingo/strip.webp +0 -0
- package/entries/elevenlabs/captures/2/measure.json +160 -0
- package/entries/elevenlabs/captures/2/strip.webp +0 -0
- package/entries/elevenlabs/entry.md +62 -0
- package/entries/elevenlabs/strip.webp +0 -0
- package/entries/flying-papers/captures/2/measure.json +291 -0
- package/entries/flying-papers/captures/2/strip.webp +0 -0
- package/entries/flying-papers/entry.md +61 -0
- package/entries/flying-papers/strip.webp +0 -0
- package/entries/gsap/captures/2/measure.json +120 -0
- package/entries/gsap/captures/2/strip.webp +0 -0
- package/entries/gsap/entry.md +62 -0
- package/entries/gsap/strip.webp +0 -0
- package/entries/letters/captures/2/measure.json +20 -0
- package/entries/letters/captures/2/strip.webp +0 -0
- package/entries/letters/entry.md +62 -0
- package/entries/letters/strip.webp +0 -0
- package/entries/micro/captures/2/measure.json +852 -0
- package/entries/micro/captures/2/strip.webp +0 -0
- package/entries/micro/entry.md +61 -0
- package/entries/micro/strip.webp +0 -0
- package/entries/monopo-saigon/captures/2/measure.json +145 -0
- package/entries/monopo-saigon/captures/2/strip.webp +0 -0
- package/entries/monopo-saigon/entry.md +64 -0
- package/entries/monopo-saigon/strip.webp +0 -0
- package/entries/notion/captures/2/measure.json +338 -0
- package/entries/notion/captures/2/strip.webp +0 -0
- package/entries/notion/entry.md +66 -0
- package/entries/notion/strip.webp +0 -0
- package/entries/origin-financial/captures/2/measure.json +169 -0
- package/entries/origin-financial/captures/2/strip.webp +0 -0
- package/entries/origin-financial/entry.md +65 -0
- package/entries/origin-financial/strip.webp +0 -0
- package/entries/oryzo-ai/captures/2/measure.json +39 -0
- package/entries/oryzo-ai/captures/2/strip.webp +0 -0
- package/entries/oryzo-ai/entry.md +76 -0
- package/entries/oryzo-ai/strip.webp +0 -0
- package/entries/silencio/captures/2/strip.webp +0 -0
- package/entries/silencio/entry.md +114 -0
- package/entries/silencio/motion/product-story/frame-0.webp +0 -0
- package/entries/silencio/motion/product-story/frame-1.webp +0 -0
- package/entries/silencio/motion/product-story/frame-2.webp +0 -0
- package/entries/silencio/motion/product-story/frame-3.webp +0 -0
- package/entries/silencio/motion/product-story/frame-4.webp +0 -0
- package/entries/silencio/motion/product-story/frame-5.webp +0 -0
- package/entries/silencio/motion/product-story/frame-6.webp +0 -0
- package/entries/silencio/motion/product-story/frame-7.webp +0 -0
- package/entries/silencio/strip.webp +0 -0
- package/entries/steep/captures/2/measure.json +20 -0
- package/entries/steep/captures/2/strip.webp +0 -0
- package/entries/steep/entry.md +63 -0
- package/entries/steep/strip.webp +0 -0
- package/entries/stripe/captures/2/measure.json +892 -0
- package/entries/stripe/captures/2/strip.webp +0 -0
- package/entries/stripe/entry.md +61 -0
- package/entries/stripe/strip.webp +0 -0
- package/entries/superpower/captures/2/measure.json +179 -0
- package/entries/superpower/captures/2/strip.webp +0 -0
- package/entries/superpower/entry.md +64 -0
- package/entries/superpower/strip.webp +0 -0
- package/entries/superr/entry.md +54 -0
- package/entries/superr/strip.webp +0 -0
- package/entries/svz-weare/captures/2/measure.json +129 -0
- package/entries/svz-weare/captures/2/strip.webp +0 -0
- package/entries/svz-weare/entry.md +147 -0
- package/entries/svz-weare/motion/hero-transition/frame-0.webp +0 -0
- package/entries/svz-weare/motion/hero-transition/frame-1.webp +0 -0
- package/entries/svz-weare/motion/hero-transition/frame-2.webp +0 -0
- package/entries/svz-weare/motion/hero-transition/frame-3.webp +0 -0
- package/entries/svz-weare/motion/hero-transition/frame-4.webp +0 -0
- package/entries/svz-weare/motion/hero-transition/frame-5.webp +0 -0
- package/entries/svz-weare/motion/hero-transition/frame-6.webp +0 -0
- package/entries/svz-weare/motion/hero-transition/frame-7.webp +0 -0
- package/entries/svz-weare/strip.webp +0 -0
- package/entries/thoughtlab/captures/2/measure.json +93 -0
- package/entries/thoughtlab/captures/2/strip.webp +0 -0
- package/entries/thoughtlab/entry.md +62 -0
- package/entries/thoughtlab/strip.webp +0 -0
- package/entries/ventriloc/entry.md +54 -0
- package/entries/ventriloc/strip.webp +0 -0
- package/entries/vivid-and-co/entry.md +58 -0
- package/entries/vivid-and-co/strip.webp +0 -0
- package/package.json +24 -5
- package/src/cli/add.mjs +100 -24
- package/src/cli/motion.mjs +168 -0
- package/src/cli/refresh.mjs +6 -0
- package/src/cli/ui.mjs +2 -1
- package/src/entry.mjs +14 -1
- package/src/index/build.mjs +9 -3
- package/src/index/embed.mjs +1 -1
- package/src/ingest/agreement.mjs +33 -0
- package/src/ingest/attach.mjs +67 -0
- package/src/ingest/draft.mjs +5 -2
- package/src/ingest/frames.mjs +20 -6
- package/src/ingest/measure.mjs +34 -1
- package/src/ingest/observed.mjs +29 -0
- package/src/mcp/http.mjs +1 -1
- package/src/mcp/instructions.mjs +2 -1
- package/src/mcp/prompts.mjs +12 -1
- package/src/mcp/resources.mjs +14 -0
- package/src/mcp/tools.mjs +48 -5
- package/src/motion.mjs +94 -0
- package/src/ui/build.mjs +83 -7
- package/src/ui/serve.mjs +29 -4
- package/src/ui/templates/entry.html +9 -1
- package/entries/ais-community-dark-depth/strip.png +0 -0
- package/tests/README.md +0 -33
- package/tests/checks/cli-check.sh +0 -13
- package/tests/checks/docker.sh +0 -10
- package/tests/checks/entry-detail.sh +0 -52
- package/tests/checks/entry.sh +0 -55
- package/tests/checks/export.sh +0 -16
- package/tests/checks/fetch-on-start.sh +0 -26
- package/tests/checks/frames-dense.sh +0 -27
- package/tests/checks/http.sh +0 -22
- package/tests/checks/hygiene.sh +0 -29
- package/tests/checks/inbox.sh +0 -27
- package/tests/checks/index-degrade.sh +0 -32
- package/tests/checks/index.sh +0 -36
- package/tests/checks/ingest-mp4.sh +0 -47
- package/tests/checks/ingest.sh +0 -39
- package/tests/checks/instructions.sh +0 -31
- package/tests/checks/licence-gate.sh +0 -24
- package/tests/checks/mcp-stdout.sh +0 -31
- package/tests/checks/measure.sh +0 -79
- package/tests/checks/minors.sh +0 -84
- package/tests/checks/optional-deps.sh +0 -55
- package/tests/checks/prompt-add-entry.sh +0 -28
- package/tests/checks/recommend.sh +0 -107
- package/tests/checks/resources.sh +0 -47
- package/tests/checks/rrf.sh +0 -120
- package/tests/checks/seed-sync.sh +0 -22
- package/tests/checks/similar.sh +0 -62
- package/tests/checks/ui-build.sh +0 -59
- package/tests/checks/vocab.sh +0 -30
- package/tests/fixtures/entry-ok/entry.md +0 -41
- package/tests/fixtures/entry-ok/strip.png +0 -0
- package/tests/fixtures/page/index.html +0 -47
- package/tests/fixtures/three-frame.webp +0 -0
- package/tests/run.sh +0 -16
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -eu
|
|
3
|
-
cd "$(dirname "$0")/../.."
|
|
4
|
-
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
|
|
5
|
-
mkdir -p "$tmp/entries"; cp -r tests/fixtures/entry-ok "$tmp/entries/entry-ok"
|
|
6
|
-
|
|
7
|
-
# A sandbox that genuinely lacks the two optional peers: copy the code, then symlink
|
|
8
|
-
# every real node_modules entry except @huggingface/transformers and playwright-core.
|
|
9
|
-
# Scoped packages are symlinked individually under @scope/, not the scope directory
|
|
10
|
-
# itself — symlinking the scope dir would drag the excluded package back in.
|
|
11
|
-
sandbox="$tmp/sandbox"
|
|
12
|
-
mkdir -p "$sandbox/node_modules"
|
|
13
|
-
cp -r bin src package.json vocab.yaml "$sandbox/"
|
|
14
|
-
for entry in node_modules/*; do
|
|
15
|
-
name=$(basename "$entry")
|
|
16
|
-
case "$name" in
|
|
17
|
-
@*|playwright-core) continue ;;
|
|
18
|
-
esac
|
|
19
|
-
ln -s "$PWD/$entry" "$sandbox/node_modules/$name"
|
|
20
|
-
done
|
|
21
|
-
for scope in node_modules/@*; do
|
|
22
|
-
sname=$(basename "$scope")
|
|
23
|
-
mkdir -p "$sandbox/node_modules/$sname"
|
|
24
|
-
for pkg in "$scope"/*; do
|
|
25
|
-
pname=$(basename "$pkg")
|
|
26
|
-
[ "$sname/$pname" = "@huggingface/transformers" ] && continue
|
|
27
|
-
ln -s "$PWD/$pkg" "$sandbox/node_modules/$sname/$pname"
|
|
28
|
-
done
|
|
29
|
-
done
|
|
30
|
-
[ ! -e "$sandbox/node_modules/playwright-core" ] || { echo "FAIL: sandbox still has playwright-core"; exit 1; }
|
|
31
|
-
[ ! -e "$sandbox/node_modules/@huggingface/transformers" ] || { echo "FAIL: sandbox still has @huggingface/transformers"; exit 1; }
|
|
32
|
-
|
|
33
|
-
# search over stdio: fts only, skip names the transformers package
|
|
34
|
-
{
|
|
35
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"0"}}}'
|
|
36
|
-
printf '%s\n' '{"jsonrpc":"2.0","method":"notifications/initialized"}'
|
|
37
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search","arguments":{"query":"parallax"}}}'
|
|
38
|
-
sleep 1
|
|
39
|
-
} | LIBRARY_DATA="$tmp" LIBRARY_SEED="$tmp/noseed" timeout 10 node "$sandbox/bin/library.mjs" serve >"$tmp/out" 2>"$tmp/err" || true
|
|
40
|
-
grep -q '"arms":\["fts"\]' "$tmp/out" || { echo "FAIL: arms not fts-only; got:"; cat "$tmp/out"; exit 1; }
|
|
41
|
-
grep -q '"skipped":\[{"arm":"vec","reason":"@huggingface/transformers is not installed"}\]' "$tmp/out" \
|
|
42
|
-
|| { echo "FAIL: skipped reason did not name the transformers package; got:"; cat "$tmp/out"; exit 1; }
|
|
43
|
-
|
|
44
|
-
# add <https url>: refused, naming playwright-core
|
|
45
|
-
out=$(LIBRARY_DATA="$tmp" node "$sandbox/bin/library.mjs" add https://example.com --slug px-url --title "Px Url" --source own --license x) || true
|
|
46
|
-
echo "$out" | grep -q '"refused"' || { echo "FAIL: https add did not refuse; got: $out"; exit 1; }
|
|
47
|
-
echo "$out" | grep -q "playwright-core is not installed" || { echo "FAIL: refusal did not name playwright-core; got: $out"; exit 1; }
|
|
48
|
-
|
|
49
|
-
# add <local recording>: still works — the regression a static playwright-core import
|
|
50
|
-
# in measure.mjs would cause, since add.mjs imports measure.mjs unconditionally
|
|
51
|
-
out=$(LIBRARY_DATA="$tmp" node "$sandbox/bin/library.mjs" add "$PWD/tests/fixtures/three-frame.webp" --slug px-local --title "Px Local" --source own --license x)
|
|
52
|
-
echo "$out" | grep -q '"blanks"' || { echo "FAIL: local-file add did not draft; got: $out"; exit 1; }
|
|
53
|
-
[ -f "$tmp/entries/px-local/strip.png" ] || { echo "FAIL: strip.png missing for local-file add"; exit 1; }
|
|
54
|
-
|
|
55
|
-
echo PASS
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# The server ships the add-entry prompt: prompts/list names it, prompts/get renders the
|
|
3
|
-
# order of operations and the tagging rubric with the vocabulary read from vocab.yaml.
|
|
4
|
-
set -eu
|
|
5
|
-
cd "$(dirname "$0")/../.."
|
|
6
|
-
tmp=$(mktemp -d); export LIBRARY_SEED="$tmp/noseed"; trap 'rm -rf "$tmp"' EXIT
|
|
7
|
-
mkdir -p "$tmp/entries"
|
|
8
|
-
{
|
|
9
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"0"}}}'
|
|
10
|
-
printf '%s\n' '{"jsonrpc":"2.0","method":"notifications/initialized"}'
|
|
11
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":2,"method":"prompts/list"}'
|
|
12
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":3,"method":"prompts/get","params":{"name":"add-entry","arguments":{"path":"/x/site.webp","source":"public-site"}}}'
|
|
13
|
-
sleep 1
|
|
14
|
-
} | LIBRARY_DATA="$tmp" timeout 10 node bin/library.mjs serve >"$tmp/out" 2>"$tmp/err" || true
|
|
15
|
-
grep -q '"name":"add-entry"' "$tmp/out" || { echo "FAIL: prompts/list lacks add-entry"; cat "$tmp/err"; exit 1; }
|
|
16
|
-
get=$(grep '"id":3' "$tmp/out")
|
|
17
|
-
need() { echo "$get" | grep -qF "$1" || { echo "FAIL: prompt text lacks: $1"; exit 1; }; }
|
|
18
|
-
need 'The session is the tagger'
|
|
19
|
-
need 'show the draft and wait for a yes'
|
|
20
|
-
need 'never remove a field to make it pass'
|
|
21
|
-
need 'Eyeballed from N sampled frames, not measured'
|
|
22
|
-
need 'added by a PR to `vocab.yaml`, never by an ingest'
|
|
23
|
-
need 'can only read files already in the server'
|
|
24
|
-
need '/x/site.webp'
|
|
25
|
-
need 'public-site'
|
|
26
|
-
# vocabulary is rendered from vocab.yaml, so every role term appears
|
|
27
|
-
for t in $(node --input-type=module -e 'import { loadVocab } from "./src/vocab.mjs"; console.log([...loadVocab().facets.role].join(" "))'); do need "$t"; done
|
|
28
|
-
echo PASS
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# recommend replaces a twelve-call client fan-out with one call: one group per
|
|
3
|
-
# vocabulary role, no prose bodies, weak hits marked, and the same named-reason
|
|
4
|
-
# degradation search already gives when the vector arm is absent.
|
|
5
|
-
set -eu
|
|
6
|
-
cd "$(dirname "$0")/../.."
|
|
7
|
-
tmp=$(mktemp -d); export LIBRARY_SEED="$tmp/noseed"; trap 'rm -rf "$tmp"' EXIT
|
|
8
|
-
mkdir -p "$tmp/entries"; cp -r tests/fixtures/entry-ok "$tmp/entries/entry-ok"
|
|
9
|
-
# entries_fts is a trigram index (schema.sql), and search() ANDs one quoted phrase
|
|
10
|
-
# per query word (query.mjs's fts5Quote): a 1-character phrase like "a" can never
|
|
11
|
-
# match under a trigram tokenizer, no matter what any entry contains. A brief built
|
|
12
|
-
# from ordinary English ("a ... and a ...") is therefore guaranteed zero hits on
|
|
13
|
-
# every role, which would let the weak-marking assertion below pass vacuously
|
|
14
|
-
# (empty hits arrays, never actually checked) even if `weak` were dropped entirely.
|
|
15
|
-
# So: drop the two 1-char words from the brief, and seed a second fixture entry
|
|
16
|
-
# whose title carries every remaining query word verbatim, so role "hero" gets a
|
|
17
|
-
# real hit and the weak-key assertion is actually exercised.
|
|
18
|
-
mkdir -p "$tmp/entries/entry-hit"; cp tests/fixtures/entry-ok/strip.png "$tmp/entries/entry-hit/strip.png"
|
|
19
|
-
cat > "$tmp/entries/entry-hit/entry.md" <<'EOF'
|
|
20
|
-
---
|
|
21
|
-
slug: entry-hit
|
|
22
|
-
title: dark premium hero and pricing table
|
|
23
|
-
tier: inspiration
|
|
24
|
-
source:
|
|
25
|
-
kind: open
|
|
26
|
-
url: https://example.com
|
|
27
|
-
license: MIT
|
|
28
|
-
captured:
|
|
29
|
-
method: frames
|
|
30
|
-
at: 2026-09-14
|
|
31
|
-
tool: wp-design-library@0.1.0
|
|
32
|
-
roles: [hero]
|
|
33
|
-
feel: [dark, premium]
|
|
34
|
-
palette:
|
|
35
|
-
canvas: "#0b0f14"
|
|
36
|
-
ink: "#f4f6f8"
|
|
37
|
-
accent: "#6ea8ff"
|
|
38
|
-
type:
|
|
39
|
-
display: geometric sans, light
|
|
40
|
-
body: same family
|
|
41
|
-
motion:
|
|
42
|
-
devices: [reveal]
|
|
43
|
-
notes: fades in gently
|
|
44
|
-
ported_from: null
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## What it does
|
|
48
|
-
|
|
49
|
-
A second fixture entry seeded so recommend has a real hit to mark weak or not.
|
|
50
|
-
|
|
51
|
-
## Section roster
|
|
52
|
-
|
|
53
|
-
hero
|
|
54
|
-
EOF
|
|
55
|
-
LIBRARY_DATA="$tmp" node bin/library.mjs index >/dev/null 2>&1
|
|
56
|
-
{
|
|
57
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"0"}}}'
|
|
58
|
-
printf '%s\n' '{"jsonrpc":"2.0","method":"notifications/initialized"}'
|
|
59
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"recommend","arguments":{"brief":"dark premium hero and pricing table"}}}'
|
|
60
|
-
sleep 1
|
|
61
|
-
} | LIBRARY_DATA="$tmp" LIBRARY_EMBED= timeout 30 node bin/library.mjs serve >"$tmp/out" 2>"$tmp/err" || true
|
|
62
|
-
|
|
63
|
-
node --input-type=module -e '
|
|
64
|
-
import fs from "node:fs";
|
|
65
|
-
import yaml from "js-yaml";
|
|
66
|
-
const fail = (m) => { console.error("FAIL: " + m); process.exit(1); };
|
|
67
|
-
const tmpDir = process.argv[2];
|
|
68
|
-
function rolesOf(slug) {
|
|
69
|
-
const raw = fs.readFileSync(`${tmpDir}/entries/${slug}/entry.md`, "utf8");
|
|
70
|
-
const m = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
71
|
-
if (!m) fail(`entry ${slug} has no frontmatter`);
|
|
72
|
-
return yaml.load(m[1]).roles ?? [];
|
|
73
|
-
}
|
|
74
|
-
let res = null;
|
|
75
|
-
for (const line of fs.readFileSync(process.argv[1], "utf8").split("\n")) {
|
|
76
|
-
if (!line.startsWith("{")) continue;
|
|
77
|
-
const d = JSON.parse(line);
|
|
78
|
-
if (d.id === 2) res = d.result?.structuredContent;
|
|
79
|
-
}
|
|
80
|
-
if (!res) fail("no recommend result");
|
|
81
|
-
const vocabRoles = yaml.load(fs.readFileSync("vocab.yaml", "utf8")).role;
|
|
82
|
-
if (!Array.isArray(res.roles)) fail("no roles array");
|
|
83
|
-
if (res.roles.length !== vocabRoles.length) fail(`${res.roles.length} role groups for ${vocabRoles.length} vocabulary roles`);
|
|
84
|
-
let totalHits = 0;
|
|
85
|
-
for (const g of res.roles) {
|
|
86
|
-
if (!vocabRoles.includes(g.role)) fail("unknown role group " + g.role);
|
|
87
|
-
for (const h of g.hits ?? []) {
|
|
88
|
-
totalHits++;
|
|
89
|
-
if ("body" in h) fail("recommend returned a prose body");
|
|
90
|
-
if (!("weak" in h)) fail("hit is not marked weak or not");
|
|
91
|
-
// The whole promise of recommend is that a group only carries hits filed under
|
|
92
|
-
// its own role — assert that against the entry'"'"'s own frontmatter, not just
|
|
93
|
-
// against the shape of the response.
|
|
94
|
-
const entryRoles = rolesOf(h.slug);
|
|
95
|
-
if (!entryRoles.includes(g.role)) fail(`hit ${h.slug} in role group ${g.role} does not carry that role (entry roles: ${JSON.stringify(entryRoles)})`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
// A pass with zero hits everywhere would let the weak-key and role-membership
|
|
99
|
-
// assertions above pass vacuously (nothing to iterate). The seeded second fixture
|
|
100
|
-
// exists so this never happens silently.
|
|
101
|
-
if (totalHits === 0) fail("no hits at all — weak-marking and role membership were never exercised");
|
|
102
|
-
if (!Array.isArray(res.arms)) fail("no arms");
|
|
103
|
-
if (!res.arms.includes("fts")) fail("fts arm did not run");
|
|
104
|
-
if (!res.skipped || !res.skipped[0]?.reason) fail("vector arm skipped without a named reason");
|
|
105
|
-
console.log(`roles ${res.roles.length}, arms ${res.arms.join(",")}, skipped ${res.skipped[0].reason}`);
|
|
106
|
-
' "$tmp/out" "$tmp" || { cat "$tmp/err"; exit 1; }
|
|
107
|
-
echo PASS
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# The server ships two resources (vocab.yaml, an entry's entry.md) and the
|
|
3
|
-
# brief-from-library prompt: resources/list, resources/templates/list and
|
|
4
|
-
# resources/read cover both; prompts/list and prompts/get cover the prompt.
|
|
5
|
-
set -eu
|
|
6
|
-
cd "$(dirname "$0")/../.."
|
|
7
|
-
tmp=$(mktemp -d); export LIBRARY_SEED="$tmp/noseed"; trap 'rm -rf "$tmp"' EXIT
|
|
8
|
-
mkdir -p "$tmp/entries"; cp -r tests/fixtures/entry-ok "$tmp/entries/entry-ok"
|
|
9
|
-
{
|
|
10
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"0"}}}'
|
|
11
|
-
printf '%s\n' '{"jsonrpc":"2.0","method":"notifications/initialized"}'
|
|
12
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":2,"method":"resources/list"}'
|
|
13
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":3,"method":"resources/templates/list"}'
|
|
14
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":4,"method":"resources/read","params":{"uri":"library://vocab"}}'
|
|
15
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":5,"method":"resources/read","params":{"uri":"library://entry/entry-ok"}}'
|
|
16
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":6,"method":"resources/read","params":{"uri":"library://entry/nope"}}'
|
|
17
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":9,"method":"resources/read","params":{"uri":"library://entry/bad.slug"}}'
|
|
18
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":7,"method":"prompts/list"}'
|
|
19
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":8,"method":"prompts/get","params":{"name":"brief-from-library","arguments":{"brief":"a hero and a pricing table, dark and premium"}}}'
|
|
20
|
-
sleep 1
|
|
21
|
-
} | LIBRARY_DATA="$tmp" timeout 10 node bin/library.mjs serve >"$tmp/out" 2>"$tmp/err" || true
|
|
22
|
-
|
|
23
|
-
grep '"id":2' "$tmp/out" | grep -q 'library://vocab' || { echo "FAIL: resources/list lacks library://vocab"; cat "$tmp/err"; exit 1; }
|
|
24
|
-
grep '"id":3' "$tmp/out" | grep -q 'library://entry/{slug}' || { echo "FAIL: resources/templates/list lacks library://entry/{slug}"; exit 1; }
|
|
25
|
-
grep '"id":4' "$tmp/out" | grep -q 'role:' || { echo "FAIL: vocab resource read lacks role:"; exit 1; }
|
|
26
|
-
grep '"id":5' "$tmp/out" | grep -q 'slug: entry-ok' || { echo "FAIL: entry resource read lacks slug: entry-ok"; exit 1; }
|
|
27
|
-
nope=$(grep '"id":6' "$tmp/out")
|
|
28
|
-
echo "$nope" | grep -q '"error"' || { echo "FAIL: unknown slug did not error"; exit 1; }
|
|
29
|
-
echo "$nope" | grep -q 'no entry nope' || { echo "FAIL: unknown slug error lacks 'no entry nope'"; exit 1; }
|
|
30
|
-
bad=$(grep '"id":9' "$tmp/out")
|
|
31
|
-
echo "$bad" | grep -q '"error"' || { echo "FAIL: bad slug did not error"; exit 1; }
|
|
32
|
-
echo "$bad" | grep -qF 'no entry bad.slug' || { echo "FAIL: bad slug error lacks 'no entry bad.slug'"; exit 1; }
|
|
33
|
-
grep '"id":7' "$tmp/out" | grep -q '"name":"brief-from-library"' || { echo "FAIL: prompts/list lacks brief-from-library"; exit 1; }
|
|
34
|
-
grep '"id":7' "$tmp/out" | grep -qF 'per-role reference set' || { echo "FAIL: prompt description does not match its recommend-first body"; exit 1; }
|
|
35
|
-
get=$(grep '"id":8' "$tmp/out")
|
|
36
|
-
echo "$get" | grep -qF 'limit: 3' || { echo "FAIL: prompt text lacks: limit: 3"; exit 1; }
|
|
37
|
-
echo "$get" | grep -qF '## References' || { echo "FAIL: prompt text lacks: ## References"; exit 1; }
|
|
38
|
-
echo "$get" | grep -qF 'ranks.fts' || { echo "FAIL: prompt text lacks the weak-vector-match rule (ranks.fts)"; exit 1; }
|
|
39
|
-
echo "$get" | grep -qF 'having no reference rather than filling it' || { echo "FAIL: prompt text lacks the weak-match-only role guidance"; exit 1; }
|
|
40
|
-
echo "$get" | grep -qF 'Call the `recommend` tool once' || { echo "FAIL: prompt does not reach for recommend"; exit 1; }
|
|
41
|
-
echo "$get" | grep -qF 'fall back to calling' || { echo "FAIL: prompt lost its search fallback"; exit 1; }
|
|
42
|
-
echo "$get" | grep -qi 'hero' || { echo "FAIL: prompt text lacks the word hero"; exit 1; }
|
|
43
|
-
# vocabulary is rendered from vocab.yaml, so every feel term appears
|
|
44
|
-
for t in $(node --input-type=module -e 'import { loadVocab } from "./src/vocab.mjs"; console.log([...loadVocab().facets.feel].join(" "))'); do
|
|
45
|
-
echo "$get" | grep -qF "$t" || { echo "FAIL: prompt text lacks feel term: $t"; exit 1; }
|
|
46
|
-
done
|
|
47
|
-
echo PASS
|
package/tests/checks/rrf.sh
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -eu
|
|
3
|
-
cd "$(dirname "$0")/../.."
|
|
4
|
-
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
|
|
5
|
-
export LIBRARY_EMBED=stub
|
|
6
|
-
mkdir -p "$tmp/entries"
|
|
7
|
-
|
|
8
|
-
# Four entries sharing role/feel/motion so tags never leak "parallax"/"bed" (both are
|
|
9
|
-
# also valid vocab terms elsewhere) into the FTS/vector text: only title+body
|
|
10
|
-
# distinguish them from the query "parallax bed". Keyword relevance (verified by
|
|
11
|
-
# querying bm25() directly): y is best (both terms in title and twice in body),
|
|
12
|
-
# z is medium (both terms once, in body only), w is weak (both terms once, in a
|
|
13
|
-
# title padded long with unrelated words so document-length normalisation costs it),
|
|
14
|
-
# x has no keyword match at all (never appears in the FTS arm's results).
|
|
15
|
-
#
|
|
16
|
-
# The stub embedder has no real semantics, so instead of hoping title/body text
|
|
17
|
-
# happens to produce a useful vector spread, the vectors are overwritten directly in
|
|
18
|
-
# vec_text after indexing so the two arms are made to actively disagree: x (worst
|
|
19
|
-
# keywords) gets the *closest* vector, y (best keywords) gets the *farthest*
|
|
20
|
-
# (negated) vector, z and w sit in between in keyword order. Only z is good on both
|
|
21
|
-
# arms, so only RRF's fusion — not either arm alone — puts it first.
|
|
22
|
-
mk() {
|
|
23
|
-
slug=$1; title=$2; body=$3
|
|
24
|
-
d="$tmp/entries/$slug"; mkdir -p "$d"
|
|
25
|
-
cp tests/fixtures/entry-ok/strip.png "$d/strip.png"
|
|
26
|
-
cat > "$d/entry.md" <<EOF
|
|
27
|
-
---
|
|
28
|
-
slug: $slug
|
|
29
|
-
title: $title
|
|
30
|
-
tier: inspiration
|
|
31
|
-
source:
|
|
32
|
-
kind: open
|
|
33
|
-
url: https://example.com
|
|
34
|
-
license: MIT
|
|
35
|
-
captured:
|
|
36
|
-
method: frames
|
|
37
|
-
at: 2026-09-14
|
|
38
|
-
tool: wp-design-library@0.1.0
|
|
39
|
-
media:
|
|
40
|
-
frames: 3
|
|
41
|
-
duration_ms: 120
|
|
42
|
-
roles: [footer]
|
|
43
|
-
feel: [cold]
|
|
44
|
-
palette:
|
|
45
|
-
canvas: "#0b0f14"
|
|
46
|
-
ink: "#f4f6f8"
|
|
47
|
-
accent: "#6ea8ff"
|
|
48
|
-
type:
|
|
49
|
-
display: geometric sans, light
|
|
50
|
-
body: same family
|
|
51
|
-
motion:
|
|
52
|
-
devices: [pin]
|
|
53
|
-
notes: static footer element
|
|
54
|
-
ported_from: null
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
$body
|
|
58
|
-
EOF
|
|
59
|
-
}
|
|
60
|
-
mk y "parallax bed hero" "the parallax bed rises here and the parallax bed falls there again"
|
|
61
|
-
mk z "quiet mountains view" "parallax bed"
|
|
62
|
-
mk w "parallax bed among many other unrelated words padding this title out considerably more" "clouds valleys rivers forests deserts glaciers coastlines"
|
|
63
|
-
mk x "quiet coastal footer element" "clouds valleys rivers forests deserts glaciers coastlines mountains"
|
|
64
|
-
|
|
65
|
-
LIBRARY_DATA="$tmp" node bin/library.mjs index >"$tmp/out" 2>&1
|
|
66
|
-
grep -q "arms: fts,vec" "$tmp/out" || { echo "FAIL: corpus did not index with the vec arm"; cat "$tmp/out"; exit 1; }
|
|
67
|
-
|
|
68
|
-
LIBRARY_DATA="$tmp" node --input-type=module -e '
|
|
69
|
-
import { openIndex, search } from "./src/index/query.mjs";
|
|
70
|
-
import { getEmbedder } from "./src/index/embed.mjs";
|
|
71
|
-
import { paths } from "./src/paths.mjs";
|
|
72
|
-
const must = (c, m) => { if (!c) { console.error("FAIL: " + m); process.exit(1); } };
|
|
73
|
-
|
|
74
|
-
const e = await getEmbedder({ models: "/x", mode: "stub" });
|
|
75
|
-
const q = await e.text("parallax bed");
|
|
76
|
-
|
|
77
|
-
function renorm(arr) {
|
|
78
|
-
let s = 0; for (const v of arr) s += v * v;
|
|
79
|
-
const n = Math.sqrt(s) || 1;
|
|
80
|
-
for (let i = 0; i < arr.length; i++) arr[i] /= n;
|
|
81
|
-
return arr;
|
|
82
|
-
}
|
|
83
|
-
const vecX = Float32Array.from(q); // rank 1: exact match
|
|
84
|
-
const vecZ = renorm(Float32Array.from(q).map((v, i) => (i === 0 ? v + 0.01 : v))); // rank 2: small nudge
|
|
85
|
-
const vecW = renorm(Float32Array.from(q).map((v, i) => (i === 0 ? v + 0.05 : v))); // rank 3: bigger nudge
|
|
86
|
-
const vecY = Float32Array.from(q).map((v) => -v); // rank 4: opposite direction
|
|
87
|
-
|
|
88
|
-
const dbw = openIndex(paths().index, { readonly: false });
|
|
89
|
-
const upd = dbw.prepare("UPDATE vec_text SET embedding = ? WHERE slug = ?");
|
|
90
|
-
upd.run(Buffer.from(vecX.buffer), "x");
|
|
91
|
-
upd.run(Buffer.from(vecZ.buffer), "z");
|
|
92
|
-
upd.run(Buffer.from(vecW.buffer), "w");
|
|
93
|
-
upd.run(Buffer.from(vecY.buffer), "y");
|
|
94
|
-
dbw.close();
|
|
95
|
-
|
|
96
|
-
const db2 = openIndex(paths().index);
|
|
97
|
-
const r = await search(db2, { query: "parallax bed", embedder: e });
|
|
98
|
-
must(r.arms.length === 2 && r.arms.includes("fts") && r.arms.includes("vec"), "arms is fts+vec");
|
|
99
|
-
must(r.hits[0].slug === "z", "z ranks first (mid on both beats top on one), got " + JSON.stringify(r.hits.map(h => h.slug)));
|
|
100
|
-
const top2 = r.hits.map(h => h.slug).slice(0, 2);
|
|
101
|
-
must(top2.includes("y"), "y appears in the top 2, got " + JSON.stringify(top2));
|
|
102
|
-
const xHit = r.hits.find(h => h.slug === "x");
|
|
103
|
-
must(!!xHit, "x is present in the results");
|
|
104
|
-
must(r.hits[0].slug !== "x", "x is not first");
|
|
105
|
-
must(xHit.ranks.fts === null, "x has no fts rank, got " + JSON.stringify(xHit.ranks));
|
|
106
|
-
must(xHit.ranks.vec === 1, "x is vec rank 1, got " + JSON.stringify(xHit.ranks));
|
|
107
|
-
|
|
108
|
-
const embOff = await getEmbedder({ models: "/x", mode: "off" });
|
|
109
|
-
must(embOff === null, "LIBRARY_EMBED=off resolves to no embedder");
|
|
110
|
-
const r2 = await search(db2, { query: "parallax bed", embedder: embOff });
|
|
111
|
-
must(r2.arms.length === 1 && r2.arms[0] === "fts", "off falls back to fts only");
|
|
112
|
-
must(Array.isArray(r2.skipped) && r2.skipped.length === 1 && r2.skipped[0].arm === "vec" && !!r2.skipped[0].reason, "skipped names the vec arm and a reason");
|
|
113
|
-
|
|
114
|
-
const badEmbedder = { arm: "vec", dims: e.dims, text: async () => { throw new Error("boom"); }, image: e.image };
|
|
115
|
-
const r3 = await search(db2, { query: "parallax bed", embedder: badEmbedder });
|
|
116
|
-
must(r3.arms.length === 1 && r3.arms[0] === "fts", "a failing embedder degrades to fts only, got " + JSON.stringify(r3.arms));
|
|
117
|
-
must(Array.isArray(r3.skipped) && r3.skipped.length === 1 && r3.skipped[0].arm === "vec" && r3.skipped[0].reason.includes("boom"), "skipped reason mentions boom, got " + JSON.stringify(r3.skipped));
|
|
118
|
-
must(Array.isArray(r3.hits) && r3.hits.length > 0, "hits are still returned despite the embedder failure");
|
|
119
|
-
'
|
|
120
|
-
echo PASS
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# On every start, entries present in the seed (the image's entries/) but missing from
|
|
3
|
-
# LIBRARY_DATA are copied in; entries already in the volume are never overwritten.
|
|
4
|
-
set -eu
|
|
5
|
-
cd "$(dirname "$0")/../.."
|
|
6
|
-
tmp=$(mktemp -d); pid=""; trap 'rm -rf "$tmp"; [ -n "$pid" ] && kill $pid 2>/dev/null || true' EXIT
|
|
7
|
-
mkdir -p "$tmp/seed" "$tmp/data/entries"
|
|
8
|
-
cp -r tests/fixtures/entry-ok "$tmp/seed/entry-ok"
|
|
9
|
-
cp -r tests/fixtures/entry-ok "$tmp/seed/entry-two"; sed -i 's/slug: entry-ok/slug: entry-two/' "$tmp/seed/entry-two/entry.md"
|
|
10
|
-
# volume already holds entry-ok with a local edit that must survive
|
|
11
|
-
cp -r tests/fixtures/entry-ok "$tmp/data/entries/entry-ok"; sed -i 's/Fixture hero with parallax bed/Volume edit kept/' "$tmp/data/entries/entry-ok/entry.md"
|
|
12
|
-
LIBRARY_DATA="$tmp/data" LIBRARY_SEED="$tmp/seed" PORT=4196 LIBRARY_TOKEN=t0k node bin/library.mjs serve --http >"$tmp/log" 2>&1 & pid=$!
|
|
13
|
-
for i in $(seq 1 30); do curl -sf localhost:4196/healthz >/dev/null 2>&1 && break; sleep 0.2; done
|
|
14
|
-
curl -sf localhost:4196/healthz | grep -q '"entries":2' || { echo "FAIL: seed entry not synced into a non-empty volume"; cat "$tmp/log"; exit 1; }
|
|
15
|
-
grep -q "Volume edit kept" "$tmp/data/entries/entry-ok/entry.md" || { echo "FAIL: volume entry was overwritten"; exit 1; }
|
|
16
|
-
grep -q "seed: copied 1 new" "$tmp/log" || { echo "FAIL: sync not reported"; cat "$tmp/log"; exit 1; }
|
|
17
|
-
kill $pid; wait $pid 2>/dev/null || true; pid=""
|
|
18
|
-
# second start copies nothing
|
|
19
|
-
LIBRARY_DATA="$tmp/data" LIBRARY_SEED="$tmp/seed" PORT=4196 LIBRARY_TOKEN=t0k node bin/library.mjs serve --http >"$tmp/log2" 2>&1 & pid=$!
|
|
20
|
-
for i in $(seq 1 30); do curl -sf localhost:4196/healthz >/dev/null 2>&1 && break; sleep 0.2; done
|
|
21
|
-
grep -q "seed: copied 0 new" "$tmp/log2" || { echo "FAIL: second start should copy nothing"; cat "$tmp/log2"; exit 1; }
|
|
22
|
-
echo PASS
|
package/tests/checks/similar.sh
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -eu
|
|
3
|
-
cd "$(dirname "$0")/../.."
|
|
4
|
-
tmp=$(mktemp -d); export LIBRARY_SEED="$tmp/noseed"; export LIBRARY_EMBED=stub; pid=""
|
|
5
|
-
trap 'rm -rf "$tmp"; [ -n "$pid" ] && kill $pid 2>/dev/null || true' EXIT
|
|
6
|
-
|
|
7
|
-
# a and b: byte-identical strip.png (both are plain copies of the fixture).
|
|
8
|
-
# c: a strip of a different size, so its stub vector differs.
|
|
9
|
-
mkdir -p "$tmp/entries"
|
|
10
|
-
cp -r tests/fixtures/entry-ok "$tmp/entries/a"; sed -i 's/slug: entry-ok/slug: a/' "$tmp/entries/a/entry.md"
|
|
11
|
-
cp -r tests/fixtures/entry-ok "$tmp/entries/b"; sed -i 's/slug: entry-ok/slug: b/' "$tmp/entries/b/entry.md"
|
|
12
|
-
cp -r tests/fixtures/entry-ok "$tmp/entries/c"; sed -i 's/slug: entry-ok/slug: c/' "$tmp/entries/c/entry.md"
|
|
13
|
-
printf x >> "$tmp/entries/c/strip.png"
|
|
14
|
-
|
|
15
|
-
# stub, over stdio: slug lookup and image lookup
|
|
16
|
-
{
|
|
17
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"0"}}}'
|
|
18
|
-
printf '%s\n' '{"jsonrpc":"2.0","method":"notifications/initialized"}'
|
|
19
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"similar","arguments":{"slug":"a"}}}'
|
|
20
|
-
printf '%s\n' "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"similar\",\"arguments\":{\"image\":\"$tmp/entries/a/strip.png\"}}}"
|
|
21
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"similar","arguments":{}}}'
|
|
22
|
-
sleep 1
|
|
23
|
-
} | LIBRARY_DATA="$tmp" timeout 10 node bin/library.mjs serve >"$tmp/out" 2>"$tmp/err" || true
|
|
24
|
-
|
|
25
|
-
node --input-type=module -e '
|
|
26
|
-
import fs from "node:fs";
|
|
27
|
-
const lines = fs.readFileSync(process.argv[2], "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
|
|
28
|
-
const must = (c, m) => { if (!c) { console.error("FAIL: " + m); process.exit(1); } };
|
|
29
|
-
const byId = (id) => lines.find((l) => l.id === id)?.result?.structuredContent;
|
|
30
|
-
|
|
31
|
-
const bySlug = byId(2);
|
|
32
|
-
must(bySlug.arms.length === 1 && bySlug.arms[0] === "vec", "similar-by-slug arms is vec");
|
|
33
|
-
must(bySlug.hits[0].slug === "b" && bySlug.hits[0].distance === 0, "similar-by-slug returns b first at distance 0; got " + JSON.stringify(bySlug.hits));
|
|
34
|
-
|
|
35
|
-
const byImage = byId(3);
|
|
36
|
-
const slugs = byImage.hits.map((h) => h.slug);
|
|
37
|
-
must(slugs.includes("a") && slugs.includes("b"), "similar-by-image returns a and b; got " + JSON.stringify(slugs));
|
|
38
|
-
|
|
39
|
-
const noArg = byId(4);
|
|
40
|
-
must(!!noArg.refused, "similar with neither slug nor image is refused");
|
|
41
|
-
' _ "$tmp/out"
|
|
42
|
-
|
|
43
|
-
# LIBRARY_EMBED=off: similar refuses, naming the arm
|
|
44
|
-
{
|
|
45
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"0"}}}'
|
|
46
|
-
printf '%s\n' '{"jsonrpc":"2.0","method":"notifications/initialized"}'
|
|
47
|
-
printf '%s\n' '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"similar","arguments":{"slug":"a"}}}'
|
|
48
|
-
sleep 1
|
|
49
|
-
} | LIBRARY_DATA="$tmp" LIBRARY_EMBED=off timeout 10 node bin/library.mjs serve >"$tmp/out-off" 2>"$tmp/err-off" || true
|
|
50
|
-
grep '"id":2' "$tmp/out-off" | grep -q 'refused.*arm' || { echo "FAIL: LIBRARY_EMBED=off did not refuse similar, naming the arm"; cat "$tmp/out-off"; exit 1; }
|
|
51
|
-
|
|
52
|
-
# over http: image is confined to <data>/inbox
|
|
53
|
-
LIBRARY_DATA="$tmp" PORT=4191 LIBRARY_TOKEN=t0k node bin/library.mjs serve --http >"$tmp/log" 2>&1 & pid=$!
|
|
54
|
-
for i in $(seq 1 30); do curl -sf localhost:4191/healthz >/dev/null 2>&1 && break; sleep 0.2; done
|
|
55
|
-
curl -sf localhost:4191/healthz | grep -q '"arms":\["fts","vec"\]' || { echo "FAIL: healthz arms with vec"; cat "$tmp/log"; exit 1; }
|
|
56
|
-
init='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"c","version":"0"}}}'
|
|
57
|
-
curl -s -X POST localhost:4191/mcp -H 'authorization: Bearer t0k' -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' -d "$init" >/dev/null
|
|
58
|
-
call='{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"similar","arguments":{"image":"/etc/hostname"}}}'
|
|
59
|
-
curl -s -X POST localhost:4191/mcp -H 'authorization: Bearer t0k' -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' -d "$call" \
|
|
60
|
-
| grep -q 'refused.*inbox' || { echo "FAIL: http similar did not refuse an image outside the inbox"; exit 1; }
|
|
61
|
-
kill $pid 2>/dev/null || true; pid=""
|
|
62
|
-
echo PASS
|
package/tests/checks/ui-build.sh
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -eu
|
|
3
|
-
cd "$(dirname "$0")/../.."
|
|
4
|
-
tmp=$(mktemp -d); export LIBRARY_SEED="$tmp/noseed"; pid=""; trap 'rm -rf "$tmp"; [ -n "$pid" ] && kill $pid 2>/dev/null || true' EXIT
|
|
5
|
-
mkdir -p "$tmp/entries"
|
|
6
|
-
cp -r tests/fixtures/entry-ok "$tmp/entries/entry-ok"
|
|
7
|
-
cp -r tests/fixtures/entry-ok "$tmp/entries/entry-draft"
|
|
8
|
-
# draft frontmatter slug carries a path-traversal + attribute-breakout payload; the
|
|
9
|
-
# directory name (entry-draft) stays safe. title is blanked so the card/entry-page title
|
|
10
|
-
# falls back to fm.slug (text, shown only through esc()) and exercises the quote-escaping
|
|
11
|
-
# fix. captured.at is newer than entry-ok's so the sort fix (not insertion order) puts it first.
|
|
12
|
-
sed -i 's/title: Fixture hero with parallax bed/title: ""/; s/roles: \[hero, closing\]/roles: []/; s#slug: entry-ok#slug: ../escape" onmouseover="x#; s/ at: 2026-09-14/ at: 2026-09-20/' "$tmp/entries/entry-draft/entry.md"
|
|
13
|
-
|
|
14
|
-
# `library ui` builds a static gallery straight from entries/, no index required first
|
|
15
|
-
LIBRARY_DATA="$tmp" node bin/library.mjs ui >"$tmp/out" 2>&1
|
|
16
|
-
ui="$tmp/ui"
|
|
17
|
-
[ -f "$ui/index.html" ] || { echo "FAIL: index.html missing"; cat "$tmp/out"; exit 1; }
|
|
18
|
-
[ -f "$ui/manifest.json" ] || { echo "FAIL: manifest.json missing"; cat "$tmp/out"; exit 1; }
|
|
19
|
-
[ "$(grep -c '"slug":' "$ui/manifest.json")" = "2" ] || { echo "FAIL: manifest does not have two rows"; cat "$ui/manifest.json"; exit 1; }
|
|
20
|
-
[ "$(grep -c '"draft": true' "$ui/manifest.json")" = "1" ] || { echo "FAIL: manifest does not mark exactly one draft"; cat "$ui/manifest.json"; exit 1; }
|
|
21
|
-
grep -q 'Fixture hero with parallax bed' "$ui/entry/entry-ok.html" || { echo "FAIL: entry page missing title"; exit 1; }
|
|
22
|
-
grep -q '<h2>What it does</h2>' "$ui/entry/entry-ok.html" || { echo "FAIL: markdown heading not converted"; exit 1; }
|
|
23
|
-
[ -f "$ui/strips/entry-ok.png" ] || { echo "FAIL: strip not copied"; exit 1; }
|
|
24
|
-
for t in hero proof feature process offer testimonial faq closing capability explainer page-head footer; do
|
|
25
|
-
grep -q "value=\"$t\"" "$ui/index.html" || { echo "FAIL: role term $t missing from index.html filters"; exit 1; }
|
|
26
|
-
done
|
|
27
|
-
|
|
28
|
-
# fm.slug must never reach a path: nothing escaped outDir or the entry/ dir via ../
|
|
29
|
-
# (the payload is `../escape" onmouseover="x`, so any file carrying "escape" anywhere under $tmp is the leak)
|
|
30
|
-
[ -z "$(find "$tmp" -name '*escape*' -print -quit)" ] || { echo "FAIL: fm.slug reached a filesystem path"; find "$tmp" -name '*escape*'; exit 1; }
|
|
31
|
-
# fm.slug must never reach an unescaped attribute: a raw " right before onmouseover=
|
|
32
|
-
# means the quote broke out; a safely-escaped " is fine and expected here
|
|
33
|
-
grep -Fq '" onmouseover="' "$ui/index.html" && { echo "FAIL: fm.slug quote broke out of an attribute"; exit 1; }
|
|
34
|
-
grep -q '"slug": "entry-draft"' "$ui/manifest.json" || { echo "FAIL: manifest does not list the draft under its directory name"; cat "$ui/manifest.json"; exit 1; }
|
|
35
|
-
|
|
36
|
-
# captured.at round-trips js-yaml as a Date; sort must land on the ISO string, newest first
|
|
37
|
-
node -e '
|
|
38
|
-
const rows = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
|
|
39
|
-
if (rows[0].slug !== "entry-draft") { console.error(JSON.stringify(rows.map((r) => [r.slug, r.captured]))); process.exit(1); }
|
|
40
|
-
' "$ui/manifest.json" || { echo "FAIL: newer captured.at entry is not first in manifest.json"; exit 1; }
|
|
41
|
-
|
|
42
|
-
# a real file one level above the ui root (LIBRARY_DATA itself), so a successful
|
|
43
|
-
# traversal would actually read something back instead of 404ing on its own
|
|
44
|
-
echo '{"leak":true}' > "$tmp/package.json"
|
|
45
|
-
|
|
46
|
-
# `serve --http` builds the same gallery on start and serves it at /, safely
|
|
47
|
-
LIBRARY_DATA="$tmp" PORT=4193 LIBRARY_TOKEN=t0k timeout 10 node bin/library.mjs serve --http >"$tmp/log" 2>&1 & pid=$!
|
|
48
|
-
for i in $(seq 1 30); do curl -sf localhost:4193/healthz >/dev/null 2>&1 && break; sleep 0.2; done
|
|
49
|
-
code=$(curl -s -o /dev/null -w '%{http_code}' localhost:4193/)
|
|
50
|
-
[ "$code" = "200" ] || { echo "FAIL: GET / -> $code"; cat "$tmp/log"; exit 1; }
|
|
51
|
-
curl -s localhost:4193/ | grep -q 'id="grid"' || { echo "FAIL: GET / missing the grid"; exit 1; }
|
|
52
|
-
code=$(curl -s -o /dev/null -w '%{http_code}' localhost:4193/entry/entry-ok.html)
|
|
53
|
-
[ "$code" = "200" ] || { echo "FAIL: GET /entry/entry-ok.html -> $code"; exit 1; }
|
|
54
|
-
code=$(curl -s -o /dev/null -w '%{http_code}' --path-as-is localhost:4193/../package.json)
|
|
55
|
-
[ "$code" = "404" ] || { echo "FAIL: GET /../package.json -> $code (traversal not blocked)"; exit 1; }
|
|
56
|
-
code=$(curl -s -o /dev/null -w '%{http_code}' --path-as-is localhost:4193/%2e%2e/package.json)
|
|
57
|
-
[ "$code" = "404" ] || { echo "FAIL: GET /%2e%2e/package.json -> $code (traversal not blocked)"; exit 1; }
|
|
58
|
-
curl -sf localhost:4193/healthz | grep -q '"ok":true' || { echo "FAIL: healthz not json"; exit 1; }
|
|
59
|
-
echo PASS
|
package/tests/checks/vocab.sh
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -eu
|
|
3
|
-
cd "$(dirname "$0")/../.."
|
|
4
|
-
tmpdir=$(mktemp -d); trap 'rm -rf "$tmpdir"' EXIT
|
|
5
|
-
tmp="$tmpdir/bad-vocab-aliases.yaml"
|
|
6
|
-
VOCAB_TMP="$tmp" node --input-type=module -e '
|
|
7
|
-
import { loadVocab, resolveTerm } from "./src/vocab.mjs";
|
|
8
|
-
import { writeFileSync } from "fs";
|
|
9
|
-
const v = loadVocab();
|
|
10
|
-
const must = (c, m) => { if (!c) { console.error("FAIL: " + m); process.exit(1); } };
|
|
11
|
-
must(v.facets.role.has("hero"), "role/hero present");
|
|
12
|
-
must(resolveTerm(v, "role", "plans") === "offer", "alias plans -> offer");
|
|
13
|
-
must(resolveTerm(v, "role", "offer") === "offer", "canonical passes through");
|
|
14
|
-
must(resolveTerm(v, "feel", "neon") === null, "unknown term is null");
|
|
15
|
-
must(resolveTerm(v, "role", "dark") === null, "term from another facet is null");
|
|
16
|
-
must(Object.keys(v.facets).length === 5, "exactly five facets");
|
|
17
|
-
const tmpFile = process.env.VOCAB_TMP;
|
|
18
|
-
writeFileSync(tmpFile, "role: [hero]\nfeel: [dark]\nmotion: [reveal]\nsource: [own]\ntier: [inspiration]\naliases: pricing\n");
|
|
19
|
-
try {
|
|
20
|
-
loadVocab(tmpFile);
|
|
21
|
-
console.error("FAIL: bad aliases did not throw");
|
|
22
|
-
process.exit(1);
|
|
23
|
-
} catch (err) {
|
|
24
|
-
if (!err.message.includes("aliases")) {
|
|
25
|
-
console.error("FAIL: error message missing aliases: " + err.message);
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
'
|
|
30
|
-
echo PASS
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
slug: entry-ok
|
|
3
|
-
title: Fixture hero with parallax bed
|
|
4
|
-
tier: inspiration
|
|
5
|
-
source:
|
|
6
|
-
kind: open
|
|
7
|
-
url: https://example.com
|
|
8
|
-
license: MIT
|
|
9
|
-
captured:
|
|
10
|
-
method: frames
|
|
11
|
-
at: 2026-09-14
|
|
12
|
-
tool: wp-design-library@0.1.0
|
|
13
|
-
media:
|
|
14
|
-
frames: 3
|
|
15
|
-
duration_ms: 120
|
|
16
|
-
roles: [hero, closing]
|
|
17
|
-
feel: [dark, depth]
|
|
18
|
-
palette:
|
|
19
|
-
canvas: "#0b0f14"
|
|
20
|
-
ink: "#f4f6f8"
|
|
21
|
-
accent: "#6ea8ff"
|
|
22
|
-
type:
|
|
23
|
-
display: geometric sans, light
|
|
24
|
-
body: same family
|
|
25
|
-
motion:
|
|
26
|
-
devices: [parallax, reveal]
|
|
27
|
-
notes: bed moves slower than the frame
|
|
28
|
-
ported_from: null
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## What it does
|
|
32
|
-
|
|
33
|
-
A dark hero whose background bed scrolls slower than the foreground frame.
|
|
34
|
-
|
|
35
|
-
## Section roster
|
|
36
|
-
|
|
37
|
-
hero, cta
|
|
38
|
-
|
|
39
|
-
## Why it works
|
|
40
|
-
|
|
41
|
-
Depth from one cheap device.
|
|
Binary file
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<title>measure fixture</title>
|
|
6
|
-
<style>
|
|
7
|
-
* { box-sizing: border-box; }
|
|
8
|
-
html, body { margin: 0; }
|
|
9
|
-
body {
|
|
10
|
-
background: #0b1218;
|
|
11
|
-
color: #f2f5f8;
|
|
12
|
-
font-family: Arial;
|
|
13
|
-
}
|
|
14
|
-
h1 { font-family: Georgia; margin: 0 0 16px; }
|
|
15
|
-
p { font-family: Arial; margin: 0 0 16px; }
|
|
16
|
-
a, button { color: #7fb6ff; }
|
|
17
|
-
button { background: transparent; border: 1px solid currentColor; font: inherit; padding: 8px 16px; }
|
|
18
|
-
section { height: 1000px; padding: 48px; }
|
|
19
|
-
|
|
20
|
-
@keyframes reveal {
|
|
21
|
-
from { opacity: 0; transform: translateY(24px); }
|
|
22
|
-
to { opacity: 1; transform: translateY(0); }
|
|
23
|
-
}
|
|
24
|
-
.reveal-target {
|
|
25
|
-
animation: reveal linear both;
|
|
26
|
-
animation-timeline: view();
|
|
27
|
-
animation-range: entry 0% entry 40%;
|
|
28
|
-
}
|
|
29
|
-
</style>
|
|
30
|
-
</head>
|
|
31
|
-
<body>
|
|
32
|
-
<section class="hero">
|
|
33
|
-
<h1>Hero heading</h1>
|
|
34
|
-
<p>Static hero copy that sets the scene.</p>
|
|
35
|
-
<a href="#learn">Learn more</a>
|
|
36
|
-
</section>
|
|
37
|
-
<section class="feature">
|
|
38
|
-
<h1>Feature heading</h1>
|
|
39
|
-
<p class="reveal-target">This paragraph reveals as it enters the viewport.</p>
|
|
40
|
-
</section>
|
|
41
|
-
<section class="closing">
|
|
42
|
-
<h1>Closing heading</h1>
|
|
43
|
-
<p>Closing copy.</p>
|
|
44
|
-
<button type="button">Get started</button>
|
|
45
|
-
</section>
|
|
46
|
-
</body>
|
|
47
|
-
</html>
|
|
Binary file
|