qualia-framework 4.4.0 → 5.1.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/AGENTS.md +24 -0
- package/CLAUDE.md +12 -63
- package/README.md +24 -18
- package/agents/builder.md +13 -33
- package/agents/plan-checker.md +18 -0
- package/agents/planner.md +17 -0
- package/agents/verifier.md +70 -0
- package/agents/visual-evaluator.md +132 -0
- package/bin/cli.js +64 -23
- package/bin/install.js +375 -29
- package/bin/qualia-ui.js +208 -1
- package/bin/slop-detect.mjs +362 -0
- package/bin/state.js +218 -2
- package/docs/erp-contract.md +5 -0
- package/docs/install-redesign-builder-prompt.md +290 -0
- package/docs/install-redesign-pilot.md +234 -0
- package/docs/playwright-loop-builder-prompt.md +185 -0
- package/docs/playwright-loop-design-notes.md +108 -0
- package/docs/playwright-loop-pilot-results.md +170 -0
- package/docs/playwright-loop-review-2026-05-03.md +65 -0
- package/docs/playwright-loop-tester-prompt.md +213 -0
- package/docs/reviews/matt-pocock-skills-analysis.md +300 -0
- package/guide.md +9 -5
- package/hooks/env-empty-guard.js +74 -0
- package/hooks/pre-compact.js +19 -9
- package/hooks/pre-deploy-gate.js +8 -2
- package/hooks/pre-push.js +26 -12
- package/hooks/supabase-destructive-guard.js +62 -0
- package/hooks/vercel-account-guard.js +91 -0
- package/package.json +2 -1
- package/rules/design-brand.md +114 -0
- package/rules/design-laws.md +148 -0
- package/rules/design-product.md +114 -0
- package/rules/design-rubric.md +157 -0
- package/rules/grounding.md +4 -0
- package/skills/qualia-build/SKILL.md +40 -46
- package/skills/qualia-discuss/SKILL.md +51 -68
- package/skills/qualia-handoff/SKILL.md +1 -0
- package/skills/qualia-issues/SKILL.md +151 -0
- package/skills/qualia-map/SKILL.md +78 -35
- package/skills/qualia-new/REFERENCE.md +139 -0
- package/skills/qualia-new/SKILL.md +85 -124
- package/skills/qualia-optimize/REFERENCE.md +202 -0
- package/skills/qualia-optimize/SKILL.md +72 -237
- package/skills/qualia-plan/SKILL.md +58 -65
- package/skills/qualia-polish/SKILL.md +180 -136
- package/skills/qualia-polish-loop/REFERENCE.md +265 -0
- package/skills/qualia-polish-loop/SKILL.md +201 -0
- package/skills/qualia-polish-loop/fixtures/broken.html +117 -0
- package/skills/qualia-polish-loop/fixtures/clean.html +196 -0
- package/skills/qualia-polish-loop/scripts/loop.mjs +302 -0
- package/skills/qualia-polish-loop/scripts/playwright-capture.mjs +197 -0
- package/skills/qualia-polish-loop/scripts/score.mjs +176 -0
- package/skills/qualia-report/SKILL.md +141 -180
- package/skills/qualia-research/SKILL.md +28 -33
- package/skills/qualia-road/SKILL.md +103 -0
- package/skills/qualia-ship/SKILL.md +1 -0
- package/skills/qualia-task/SKILL.md +1 -1
- package/skills/qualia-test/SKILL.md +50 -2
- package/skills/qualia-triage/SKILL.md +152 -0
- package/skills/qualia-verify/SKILL.md +63 -104
- package/skills/qualia-zoom/SKILL.md +51 -0
- package/skills/zoho-workflow/SKILL.md +64 -0
- package/templates/CONTEXT.md +36 -0
- package/templates/DESIGN.md +229 -435
- package/templates/PRODUCT.md +95 -0
- package/templates/decisions/ADR-template.md +30 -0
- package/tests/bin.test.sh +451 -7
- package/tests/state.test.sh +58 -0
- package/skills/qualia-design/SKILL.md +0 -169
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# PRODUCT — {Project Name}
|
|
2
|
+
|
|
3
|
+
> **Required.** The "who and why" every Qualia agent reads before designing or building anything.
|
|
4
|
+
> `/qualia-new` generates this from 5 questions. Update as the product clarifies.
|
|
5
|
+
|
|
6
|
+
## Register
|
|
7
|
+
|
|
8
|
+
**One of:** `brand` · `product`
|
|
9
|
+
|
|
10
|
+
- **brand** — design IS the product (marketing, landing, campaign, portfolio). Distinctiveness is the bar.
|
|
11
|
+
- **product** — design SERVES the product (app, admin, dashboard, tool). Earned familiarity is the bar.
|
|
12
|
+
|
|
13
|
+
A project may have BOTH (e.g., marketing site + app). When that's true, list both, and note which surfaces fall under each.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
register: product
|
|
17
|
+
secondary: brand (only for /marketing/* routes)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Users (specific, not personas)
|
|
21
|
+
|
|
22
|
+
Real humans with real jobs who will use this. Not "small business owners" — Sarah, who runs a 3-person bookkeeping practice in Limassol and pays for QuickBooks because she trusts it. Three users, named, with the workflow in mind.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
1. {Name} — {role + scenario}. {What they're trying to do when they open this}.
|
|
26
|
+
2. ...
|
|
27
|
+
3. ...
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Brand voice
|
|
31
|
+
|
|
32
|
+
How the product sounds in writing. UI strings, error messages, marketing copy. Three adjectives, then one sentence of "voice in motion."
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Voice: {three adjectives — e.g., "direct, confident, dry"}
|
|
36
|
+
|
|
37
|
+
Voice in motion: {one paragraph showing the voice in actual sentences. What an
|
|
38
|
+
error message sounds like. What a confirmation sounds like. What the empty state
|
|
39
|
+
sounds like. Concrete, not adjectives.}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Anti-references (mandatory, 3-5)
|
|
43
|
+
|
|
44
|
+
Sites the project should NOT look like. Anti-references pin down what the design is reacting against — more useful than positive references.
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
- {URL or descriptor} — {what it gets wrong for our use case}
|
|
48
|
+
- {URL or descriptor} — ...
|
|
49
|
+
- {URL or descriptor} — ...
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
For Brand register, these are usually saturated aesthetic lanes (see `rules/design-brand.md`).
|
|
53
|
+
For Product register, these are usually patterns we don't want to inherit (e.g., "Salesforce Lightning — too dense, too many panels").
|
|
54
|
+
|
|
55
|
+
## Positive references (optional, ≤3)
|
|
56
|
+
|
|
57
|
+
Sites that demonstrate qualities we want to inherit. Less important than anti-references.
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
- {URL or app name} — {specific quality, e.g., "Linear's keyboard fluency", "Stripe's empty state copy", "Vercel's color restraint"}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Strategic principles
|
|
64
|
+
|
|
65
|
+
3-5 statements that resolve future arguments. Not feature ideas — design/UX principles that compound.
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
- {Principle}. {One-line rationale.}
|
|
69
|
+
- {Principle}. {One-line rationale.}
|
|
70
|
+
- {Principle}. {One-line rationale.}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Examples:
|
|
74
|
+
- **Density before delight.** Users live in this app 6+ hours/day. Density wins over flourish.
|
|
75
|
+
- **Power users are the primary persona.** Beginners get onboarding. The app optimizes for power.
|
|
76
|
+
- **Speed of glance, not depth of click.** Information visible without interaction beats information one click away.
|
|
77
|
+
|
|
78
|
+
## Constraints
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Framework: {Next.js 16 / React 19 / Vite / Astro / etc}
|
|
82
|
+
Component library: {shadcn-ui · custom · radix · arco · ...}
|
|
83
|
+
Performance budget: {LCP < 2.5s, INP < 200ms, CLS < 0.1, etc}
|
|
84
|
+
Accessibility: WCAG 2.2 AA minimum
|
|
85
|
+
Browsers: {evergreen / IE11 / mobile-first / ...}
|
|
86
|
+
i18n: {languages supported, RTL needed?}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Differentiation (one sentence)
|
|
90
|
+
|
|
91
|
+
What someone will remember about this product 24 hours after using it for the first time. If you can't write this in one sentence, the product hasn't decided what it is yet.
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
{One sentence. What's the memory hook?}
|
|
95
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# ADR-{{NNNN}} — {{Decision title in present tense}}
|
|
2
|
+
|
|
3
|
+
- **Date:** {{YYYY-MM-DD}}
|
|
4
|
+
- **Phase:** {{N}} — {{phase name}}
|
|
5
|
+
- **Status:** Proposed | Accepted | Superseded by ADR-XXXX
|
|
6
|
+
- **Domain terms:** {{any new/affected terms — also update CONTEXT.md}}
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
What is the situation that requires a decision? Why are we deciding this now? What constraints apply (regulatory, performance, team skill, deadlines)?
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
What we are doing. State it as a single declarative sentence first, then expand.
|
|
13
|
+
|
|
14
|
+
## Consequences
|
|
15
|
+
- **What becomes easier:** ...
|
|
16
|
+
- **What becomes harder:** ...
|
|
17
|
+
- **What is now load-bearing on this decision:** ...
|
|
18
|
+
|
|
19
|
+
## Alternatives considered
|
|
20
|
+
Why each alternative was rejected — keep this honest. Future-you will read it.
|
|
21
|
+
|
|
22
|
+
- **Alt A** — {{rejected because…}}
|
|
23
|
+
- **Alt B** — {{rejected because…}}
|
|
24
|
+
|
|
25
|
+
## Notes
|
|
26
|
+
Any non-obvious context, links to discussion threads, related issues. Optional.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
**When to write an ADR (per Matt Pocock's rule):** only when the decision is hard-to-reverse, surprising-without-context, AND involves real trade-offs. If any of those three is missing, just put it in CONTEXT.md or a phase plan instead. ADRs are scarce on purpose.
|
package/tests/bin.test.sh
CHANGED
|
@@ -9,6 +9,7 @@ NODE="${NODE:-node}"
|
|
|
9
9
|
CLI_JS="$FRAMEWORK_DIR/bin/cli.js"
|
|
10
10
|
UI_JS="$FRAMEWORK_DIR/bin/qualia-ui.js"
|
|
11
11
|
INSTALL_JS="$FRAMEWORK_DIR/bin/install.js"
|
|
12
|
+
STATE_JS="$FRAMEWORK_DIR/bin/state.js"
|
|
12
13
|
PKG_VERSION=$($NODE -e 'console.log(require("'"$FRAMEWORK_DIR"'/package.json").version)')
|
|
13
14
|
|
|
14
15
|
# Track tmp dirs we create so we can clean them up on exit
|
|
@@ -476,11 +477,12 @@ else
|
|
|
476
477
|
fail_case "CLAUDE.md role substitution"
|
|
477
478
|
fi
|
|
478
479
|
|
|
479
|
-
# 31. All
|
|
480
|
-
# git-guardrails + stop-session-log added in v4.2.0
|
|
480
|
+
# 31. All 12 hooks installed (block-env-edit removed in v3.2.0;
|
|
481
|
+
# git-guardrails + stop-session-log added in v4.2.0;
|
|
482
|
+
# vercel-account-guard + env-empty-guard + supabase-destructive-guard added in v5.0.0)
|
|
481
483
|
HOOK_COUNT=$(ls "$TMP/.claude/hooks/"*.js 2>/dev/null | wc -l)
|
|
482
|
-
if [ "$HOOK_COUNT" -eq
|
|
483
|
-
pass "
|
|
484
|
+
if [ "$HOOK_COUNT" -eq 12 ]; then
|
|
485
|
+
pass "12 hooks installed in hooks/"
|
|
484
486
|
else
|
|
485
487
|
fail_case "hook count" "got $HOOK_COUNT"
|
|
486
488
|
fi
|
|
@@ -496,7 +498,7 @@ else
|
|
|
496
498
|
fail_case "settings.json contents"
|
|
497
499
|
fi
|
|
498
500
|
|
|
499
|
-
# 33. settings.json contains all
|
|
501
|
+
# 33. settings.json contains all 12 hooks wired correctly
|
|
500
502
|
if grep -q 'branch-guard.js' "$TMP/.claude/settings.json" \
|
|
501
503
|
&& grep -q 'migration-guard.js' "$TMP/.claude/settings.json" \
|
|
502
504
|
&& grep -q 'pre-push.js' "$TMP/.claude/settings.json" \
|
|
@@ -505,8 +507,11 @@ if grep -q 'branch-guard.js' "$TMP/.claude/settings.json" \
|
|
|
505
507
|
&& grep -q 'session-start.js' "$TMP/.claude/settings.json" \
|
|
506
508
|
&& grep -q 'pre-compact.js' "$TMP/.claude/settings.json" \
|
|
507
509
|
&& grep -q 'git-guardrails.js' "$TMP/.claude/settings.json" \
|
|
508
|
-
&& grep -q 'stop-session-log.js' "$TMP/.claude/settings.json"
|
|
509
|
-
|
|
510
|
+
&& grep -q 'stop-session-log.js' "$TMP/.claude/settings.json" \
|
|
511
|
+
&& grep -q 'vercel-account-guard.js' "$TMP/.claude/settings.json" \
|
|
512
|
+
&& grep -q 'env-empty-guard.js' "$TMP/.claude/settings.json" \
|
|
513
|
+
&& grep -q 'supabase-destructive-guard.js' "$TMP/.claude/settings.json"; then
|
|
514
|
+
pass "settings.json has all 12 hooks wired"
|
|
510
515
|
else
|
|
511
516
|
fail_case "settings.json missing hooks"
|
|
512
517
|
fi
|
|
@@ -992,6 +997,445 @@ else
|
|
|
992
997
|
fail_case "help missing flush" "exit=$EXIT"
|
|
993
998
|
fi
|
|
994
999
|
|
|
1000
|
+
echo ""
|
|
1001
|
+
echo "--- v5.0.0 (alignment substrate + new skills) ---"
|
|
1002
|
+
|
|
1003
|
+
# 79. CONTEXT.md template installs to qualia-templates/
|
|
1004
|
+
TMP=$(mktmp)
|
|
1005
|
+
echo "QS-FAWZI-01" | HOME="$TMP" $NODE "$INSTALL_JS" >/dev/null 2>&1
|
|
1006
|
+
if [ -f "$TMP/.claude/qualia-templates/CONTEXT.md" ]; then
|
|
1007
|
+
pass "CONTEXT.md template installs to qualia-templates/"
|
|
1008
|
+
else
|
|
1009
|
+
fail_case "CONTEXT.md template missing"
|
|
1010
|
+
fi
|
|
1011
|
+
|
|
1012
|
+
# 80. decisions/ADR-template.md installs (validates nested template dir copy)
|
|
1013
|
+
if [ -f "$TMP/.claude/qualia-templates/decisions/ADR-template.md" ]; then
|
|
1014
|
+
pass "decisions/ADR-template.md installs (nested template dir)"
|
|
1015
|
+
else
|
|
1016
|
+
fail_case "decisions/ADR-template.md missing"
|
|
1017
|
+
fi
|
|
1018
|
+
|
|
1019
|
+
# 81-84. The 4 new v5 skills install with their SKILL.md
|
|
1020
|
+
for SKILL in qualia-zoom qualia-road qualia-issues qualia-triage; do
|
|
1021
|
+
if [ -f "$TMP/.claude/skills/$SKILL/SKILL.md" ]; then
|
|
1022
|
+
pass "$SKILL skill installs"
|
|
1023
|
+
else
|
|
1024
|
+
fail_case "$SKILL skill missing after install"
|
|
1025
|
+
fi
|
|
1026
|
+
done
|
|
1027
|
+
|
|
1028
|
+
# 85. qualia-discuss SKILL.md mentions "grilling" (validates v5 fold of qualia-grill into discuss)
|
|
1029
|
+
if grep -qi "grilling" "$TMP/.claude/skills/qualia-discuss/SKILL.md"; then
|
|
1030
|
+
pass "qualia-discuss documents grilling pattern (folded from qualia-grill)"
|
|
1031
|
+
else
|
|
1032
|
+
fail_case "qualia-discuss missing grilling reference"
|
|
1033
|
+
fi
|
|
1034
|
+
|
|
1035
|
+
# 86. qualia-discuss SKILL.md references CONTEXT.md (validates v5 substrate wiring)
|
|
1036
|
+
if grep -q "CONTEXT.md" "$TMP/.claude/skills/qualia-discuss/SKILL.md"; then
|
|
1037
|
+
pass "qualia-discuss references CONTEXT.md"
|
|
1038
|
+
else
|
|
1039
|
+
fail_case "qualia-discuss missing CONTEXT.md reference"
|
|
1040
|
+
fi
|
|
1041
|
+
|
|
1042
|
+
# 87. qualia-road has disable-model-invocation: true (pure reference skill, user-only)
|
|
1043
|
+
if grep -q "disable-model-invocation: true" "$TMP/.claude/skills/qualia-road/SKILL.md"; then
|
|
1044
|
+
pass "qualia-road has disable-model-invocation: true"
|
|
1045
|
+
else
|
|
1046
|
+
fail_case "qualia-road missing disable-model-invocation"
|
|
1047
|
+
fi
|
|
1048
|
+
|
|
1049
|
+
# 88. qualia-road has NO dead skill refs (qualia-grill, qualia-deepen, qualia-tdd, qualia-onboard
|
|
1050
|
+
# were folded; if they appear as bare /qualia-* commands the road documentation is broken)
|
|
1051
|
+
if grep -qE '/qualia-(grill|deepen|tdd|onboard)\b' "$TMP/.claude/skills/qualia-road/SKILL.md"; then
|
|
1052
|
+
fail_case "qualia-road still references folded skills as commands"
|
|
1053
|
+
else
|
|
1054
|
+
pass "qualia-road has no dead-skill references"
|
|
1055
|
+
fi
|
|
1056
|
+
|
|
1057
|
+
# 89. CONTEXT.md template has NO /qualia-grill reference (was folded into /qualia-discuss)
|
|
1058
|
+
if grep -q '/qualia-grill\b' "$TMP/.claude/qualia-templates/CONTEXT.md"; then
|
|
1059
|
+
fail_case "CONTEXT.md template still references /qualia-grill"
|
|
1060
|
+
else
|
|
1061
|
+
pass "CONTEXT.md template has no /qualia-grill reference"
|
|
1062
|
+
fi
|
|
1063
|
+
|
|
1064
|
+
# 90. Builder agent has trust-boundary block (defends against CONTEXT.md prompt injection)
|
|
1065
|
+
if grep -q "Trust boundary" "$TMP/.claude/agents/builder.md"; then
|
|
1066
|
+
pass "builder.md has Trust boundary block (prompt-injection defense)"
|
|
1067
|
+
else
|
|
1068
|
+
fail_case "builder.md missing Trust boundary block"
|
|
1069
|
+
fi
|
|
1070
|
+
|
|
1071
|
+
# 91. qualia-issues uses --body-file (not vulnerable heredoc) for gh issue create
|
|
1072
|
+
if grep -q -- "--body-file" "$TMP/.claude/skills/qualia-issues/SKILL.md" \
|
|
1073
|
+
&& ! grep -q "gh issue create.*--body \"\$(cat <<" "$TMP/.claude/skills/qualia-issues/SKILL.md"; then
|
|
1074
|
+
pass "qualia-issues uses --body-file (no heredoc shell injection)"
|
|
1075
|
+
else
|
|
1076
|
+
fail_case "qualia-issues still uses heredoc for gh issue body"
|
|
1077
|
+
fi
|
|
1078
|
+
|
|
1079
|
+
# 92-94. v5.0 insights-driven hooks parse as valid Node and install
|
|
1080
|
+
for HOOK in vercel-account-guard env-empty-guard supabase-destructive-guard; do
|
|
1081
|
+
HOOK_FILE="$TMP/.claude/hooks/${HOOK}.js"
|
|
1082
|
+
if [ -f "$HOOK_FILE" ]; then
|
|
1083
|
+
EXIT=0; $NODE -c "$HOOK_FILE" 2>/dev/null || EXIT=$?
|
|
1084
|
+
if [ "$EXIT" -eq 0 ]; then
|
|
1085
|
+
pass "${HOOK}.js installs and parses as valid Node"
|
|
1086
|
+
else
|
|
1087
|
+
fail_case "${HOOK}.js parse error"
|
|
1088
|
+
fi
|
|
1089
|
+
else
|
|
1090
|
+
fail_case "${HOOK}.js missing after install"
|
|
1091
|
+
fi
|
|
1092
|
+
done
|
|
1093
|
+
|
|
1094
|
+
# 95. cmdInit guards against suspicious project names (insights friction: throwaway projects)
|
|
1095
|
+
TMP_PROJ=$(mktemp -d)
|
|
1096
|
+
EXIT=0; OUT=$(cd "$TMP_PROJ" && $NODE "$STATE_JS" init --project "test" --phases '[{"name":"X","goal":"y"}]' 2>&1) || EXIT=$?
|
|
1097
|
+
if [ "$EXIT" -ne 0 ] && echo "$OUT" | grep -q "SUSPICIOUS_NAME"; then
|
|
1098
|
+
pass "state.js init blocks suspicious project name 'test' without --force"
|
|
1099
|
+
else
|
|
1100
|
+
fail_case "state.js init allowed 'test' as project name"
|
|
1101
|
+
fi
|
|
1102
|
+
rm -rf "$TMP_PROJ"
|
|
1103
|
+
|
|
1104
|
+
# 96. cmdInit allows real client name
|
|
1105
|
+
TMP_PROJ=$(mktemp -d)
|
|
1106
|
+
EXIT=0; OUT=$(cd "$TMP_PROJ" && $NODE "$STATE_JS" init --project "RealClient" --phases '[{"name":"X","goal":"y"}]' 2>&1) || EXIT=$?
|
|
1107
|
+
if [ "$EXIT" -eq 0 ] && echo "$OUT" | grep -q '"action": "init"'; then
|
|
1108
|
+
pass "state.js init allows real project name 'RealClient'"
|
|
1109
|
+
else
|
|
1110
|
+
fail_case "state.js init rejected real name" "exit=$EXIT"
|
|
1111
|
+
fi
|
|
1112
|
+
rm -rf "$TMP_PROJ"
|
|
1113
|
+
|
|
1114
|
+
# 97. cmdInit allows suspicious name with --force (escape hatch)
|
|
1115
|
+
TMP_PROJ=$(mktemp -d)
|
|
1116
|
+
EXIT=0; OUT=$(cd "$TMP_PROJ" && $NODE "$STATE_JS" init --project "test" --phases '[{"name":"X","goal":"y"}]' --force 2>&1) || EXIT=$?
|
|
1117
|
+
if [ "$EXIT" -eq 0 ]; then
|
|
1118
|
+
pass "state.js init --force bypasses suspicious-name guard"
|
|
1119
|
+
else
|
|
1120
|
+
fail_case "state.js init --force was blocked" "exit=$EXIT"
|
|
1121
|
+
fi
|
|
1122
|
+
rm -rf "$TMP_PROJ"
|
|
1123
|
+
|
|
1124
|
+
# 98. pre-deploy-gate honors QUALIA_SKIP_LINT escape (insights friction: lint blocks ship)
|
|
1125
|
+
if grep -q "QUALIA_SKIP_LINT" "$TMP/.claude/hooks/pre-deploy-gate.js"; then
|
|
1126
|
+
pass "pre-deploy-gate.js honors QUALIA_SKIP_LINT escape"
|
|
1127
|
+
else
|
|
1128
|
+
fail_case "pre-deploy-gate missing QUALIA_SKIP_LINT escape"
|
|
1129
|
+
fi
|
|
1130
|
+
|
|
1131
|
+
echo ""
|
|
1132
|
+
echo "--- v5.0.0 (visual-polish loop addendum) ---"
|
|
1133
|
+
|
|
1134
|
+
# 99. qualia-polish-loop SKILL.md installs
|
|
1135
|
+
TMP=$(mktmp)
|
|
1136
|
+
echo "QS-FAWZI-01" | HOME="$TMP" $NODE "$INSTALL_JS" >/dev/null 2>&1
|
|
1137
|
+
if [ -f "$TMP/.claude/skills/qualia-polish-loop/SKILL.md" ]; then
|
|
1138
|
+
pass "qualia-polish-loop SKILL.md installs"
|
|
1139
|
+
else
|
|
1140
|
+
fail_case "qualia-polish-loop SKILL.md missing after install"
|
|
1141
|
+
fi
|
|
1142
|
+
|
|
1143
|
+
# 100. qualia-polish-loop REFERENCE.md installs
|
|
1144
|
+
if [ -f "$TMP/.claude/skills/qualia-polish-loop/REFERENCE.md" ]; then
|
|
1145
|
+
pass "qualia-polish-loop REFERENCE.md installs"
|
|
1146
|
+
else
|
|
1147
|
+
fail_case "qualia-polish-loop REFERENCE.md missing after install"
|
|
1148
|
+
fi
|
|
1149
|
+
|
|
1150
|
+
# 101. qualia-polish-loop SKILL.md references the design rubric
|
|
1151
|
+
if grep -q "design-rubric.md" "$TMP/.claude/skills/qualia-polish-loop/SKILL.md"; then
|
|
1152
|
+
pass "qualia-polish-loop references design-rubric.md"
|
|
1153
|
+
else
|
|
1154
|
+
fail_case "qualia-polish-loop missing rubric reference"
|
|
1155
|
+
fi
|
|
1156
|
+
|
|
1157
|
+
# 102. qualia-polish-loop REFERENCE.md has the anchored vision eval prompt
|
|
1158
|
+
if grep -q "DEFAULT TO 3" "$TMP/.claude/skills/qualia-polish-loop/REFERENCE.md"; then
|
|
1159
|
+
pass "qualia-polish-loop REFERENCE.md has anchored rubric prompt"
|
|
1160
|
+
else
|
|
1161
|
+
fail_case "qualia-polish-loop REFERENCE.md missing anchored rubric"
|
|
1162
|
+
fi
|
|
1163
|
+
|
|
1164
|
+
# 103. qualia-polish-loop SKILL.md has regression detection (LOOP_REGRESSION_DETECTED)
|
|
1165
|
+
if grep -q "LOOP_REGRESSION_DETECTED" "$TMP/.claude/skills/qualia-polish-loop/SKILL.md"; then
|
|
1166
|
+
pass "qualia-polish-loop has regression kill-switch"
|
|
1167
|
+
else
|
|
1168
|
+
fail_case "qualia-polish-loop missing regression kill-switch"
|
|
1169
|
+
fi
|
|
1170
|
+
|
|
1171
|
+
# 104. score.mjs exists in the framework skill scripts
|
|
1172
|
+
if [ -f "$FRAMEWORK_DIR/skills/qualia-polish-loop/scripts/score.mjs" ]; then
|
|
1173
|
+
pass "score.mjs exists in skill scripts"
|
|
1174
|
+
else
|
|
1175
|
+
fail_case "score.mjs missing from skill scripts"
|
|
1176
|
+
fi
|
|
1177
|
+
|
|
1178
|
+
# 105. score.mjs computes pass correctly (all dims >= 3)
|
|
1179
|
+
SCORE_OUT=$(echo '{"typography":3,"color":3,"spatial":3,"layout":3,"shadow":3,"motion":3,"microcopy":3,"container":3}' | $NODE "$FRAMEWORK_DIR/skills/qualia-polish-loop/scripts/score.mjs" 2>&1)
|
|
1180
|
+
EXIT=$?
|
|
1181
|
+
if [ "$EXIT" -eq 0 ] && echo "$SCORE_OUT" | grep -q '"pass": true'; then
|
|
1182
|
+
pass "score.mjs pass on all-3 scores"
|
|
1183
|
+
else
|
|
1184
|
+
fail_case "score.mjs pass computation" "exit=$EXIT"
|
|
1185
|
+
fi
|
|
1186
|
+
|
|
1187
|
+
# 106. score.mjs computes fail correctly (one dim < 3)
|
|
1188
|
+
SCORE_OUT=$(echo '{"typography":2,"color":3,"spatial":3,"layout":3,"shadow":3,"motion":3,"microcopy":3,"container":3}' | $NODE "$FRAMEWORK_DIR/skills/qualia-polish-loop/scripts/score.mjs" 2>&1)
|
|
1189
|
+
EXIT=$?
|
|
1190
|
+
if [ "$EXIT" -eq 1 ] && echo "$SCORE_OUT" | grep -q '"pass": false'; then
|
|
1191
|
+
pass "score.mjs fail on dim < 3"
|
|
1192
|
+
else
|
|
1193
|
+
fail_case "score.mjs fail computation" "exit=$EXIT"
|
|
1194
|
+
fi
|
|
1195
|
+
|
|
1196
|
+
# 107. qualia-road references qualia-polish-loop
|
|
1197
|
+
if grep -q "qualia-polish-loop" "$TMP/.claude/skills/qualia-road/SKILL.md"; then
|
|
1198
|
+
pass "qualia-road references qualia-polish-loop"
|
|
1199
|
+
else
|
|
1200
|
+
fail_case "qualia-road missing qualia-polish-loop reference"
|
|
1201
|
+
fi
|
|
1202
|
+
|
|
1203
|
+
# 108. package.json version is 5.1.x (multi-target install + polish-loop in v5.x line)
|
|
1204
|
+
if grep -qE '"5\.1\.' "$FRAMEWORK_DIR/package.json"; then
|
|
1205
|
+
pass "package.json version is 5.1.x"
|
|
1206
|
+
else
|
|
1207
|
+
fail_case "package.json version not 5.1.x"
|
|
1208
|
+
fi
|
|
1209
|
+
|
|
1210
|
+
# 109. loop.mjs installs (orchestrator)
|
|
1211
|
+
if [ -f "$TMP/.claude/skills/qualia-polish-loop/scripts/loop.mjs" ]; then
|
|
1212
|
+
pass "qualia-polish-loop scripts/loop.mjs installs"
|
|
1213
|
+
else
|
|
1214
|
+
fail_case "scripts/loop.mjs missing — install.js scripts/ subfolder copy broken"
|
|
1215
|
+
fi
|
|
1216
|
+
|
|
1217
|
+
# 110. playwright-capture.mjs installs (capture helper)
|
|
1218
|
+
if [ -f "$TMP/.claude/skills/qualia-polish-loop/scripts/playwright-capture.mjs" ]; then
|
|
1219
|
+
pass "qualia-polish-loop scripts/playwright-capture.mjs installs"
|
|
1220
|
+
else
|
|
1221
|
+
fail_case "scripts/playwright-capture.mjs missing"
|
|
1222
|
+
fi
|
|
1223
|
+
|
|
1224
|
+
# 111. fixtures/ subfolder installs (self-test pages)
|
|
1225
|
+
if [ -f "$TMP/.claude/skills/qualia-polish-loop/fixtures/clean.html" ] && \
|
|
1226
|
+
[ -f "$TMP/.claude/skills/qualia-polish-loop/fixtures/broken.html" ]; then
|
|
1227
|
+
pass "qualia-polish-loop fixtures/ installs (clean.html + broken.html)"
|
|
1228
|
+
else
|
|
1229
|
+
fail_case "fixtures/ subfolder not copied by install.js"
|
|
1230
|
+
fi
|
|
1231
|
+
|
|
1232
|
+
# 112. visual-evaluator agent installs
|
|
1233
|
+
if [ -f "$TMP/.claude/agents/visual-evaluator.md" ]; then
|
|
1234
|
+
pass "agents/visual-evaluator.md installs"
|
|
1235
|
+
else
|
|
1236
|
+
fail_case "visual-evaluator agent missing after install"
|
|
1237
|
+
fi
|
|
1238
|
+
|
|
1239
|
+
# 113. loop.mjs parses as valid Node ESM
|
|
1240
|
+
EXIT=0; $NODE --check "$FRAMEWORK_DIR/skills/qualia-polish-loop/scripts/loop.mjs" 2>/dev/null || EXIT=$?
|
|
1241
|
+
if [ "$EXIT" -eq 0 ]; then
|
|
1242
|
+
pass "loop.mjs parses as valid Node ESM"
|
|
1243
|
+
else
|
|
1244
|
+
fail_case "loop.mjs parse error"
|
|
1245
|
+
fi
|
|
1246
|
+
|
|
1247
|
+
# 114. playwright-capture.mjs parses as valid Node ESM
|
|
1248
|
+
EXIT=0; $NODE --check "$FRAMEWORK_DIR/skills/qualia-polish-loop/scripts/playwright-capture.mjs" 2>/dev/null || EXIT=$?
|
|
1249
|
+
if [ "$EXIT" -eq 0 ]; then
|
|
1250
|
+
pass "playwright-capture.mjs parses as valid Node ESM"
|
|
1251
|
+
else
|
|
1252
|
+
fail_case "playwright-capture.mjs parse error"
|
|
1253
|
+
fi
|
|
1254
|
+
|
|
1255
|
+
# 115. loop.mjs init creates a valid state file
|
|
1256
|
+
TMP_STATE=$(mktmp)/qpl-state.json
|
|
1257
|
+
mkdir -p "$(dirname "$TMP_STATE")"
|
|
1258
|
+
EXIT=0; $NODE "$FRAMEWORK_DIR/skills/qualia-polish-loop/scripts/loop.mjs" init \
|
|
1259
|
+
--state "$TMP_STATE" --url "http://localhost:3000" --max 4 --budget 50000 >/dev/null 2>&1 || EXIT=$?
|
|
1260
|
+
if [ "$EXIT" -eq 0 ] && [ -f "$TMP_STATE" ] && grep -q '"verdict": "pending"' "$TMP_STATE"; then
|
|
1261
|
+
pass "loop.mjs init creates valid state.json"
|
|
1262
|
+
else
|
|
1263
|
+
fail_case "loop.mjs init failed (exit=$EXIT, state present? $([ -f "$TMP_STATE" ] && echo yes || echo no))"
|
|
1264
|
+
fi
|
|
1265
|
+
|
|
1266
|
+
# 116. loop.mjs record kill-switch fires on 3 consecutive recurrences of same fingerprint
|
|
1267
|
+
TMP_STATE2=$(mktmp)/qpl-kill.json
|
|
1268
|
+
TMP_EVAL=$(mktmp)/qpl-eval.json
|
|
1269
|
+
mkdir -p "$(dirname "$TMP_STATE2")" "$(dirname "$TMP_EVAL")"
|
|
1270
|
+
$NODE "$FRAMEWORK_DIR/skills/qualia-polish-loop/scripts/loop.mjs" init \
|
|
1271
|
+
--state "$TMP_STATE2" --url "http://localhost:3000" --max 8 >/dev/null 2>&1
|
|
1272
|
+
# write 3 identical evals
|
|
1273
|
+
KILL_OK=true
|
|
1274
|
+
for ITER in 1 2 3; do
|
|
1275
|
+
cat > "$TMP_EVAL" <<EOF
|
|
1276
|
+
{
|
|
1277
|
+
"iteration": $ITER,
|
|
1278
|
+
"tokens_used": 100,
|
|
1279
|
+
"viewport_results": [],
|
|
1280
|
+
"aggregate_scores": {"typography":1,"color":3,"spatial":3,"layout":3,"shadow":3,"motion":3,"microcopy":3,"container":3},
|
|
1281
|
+
"top_issues": [{"dim":"typography","severity":"critical","description":"banned font Inter visible","likely_file":"src/styles.css","fix":"replace"}],
|
|
1282
|
+
"pass": false
|
|
1283
|
+
}
|
|
1284
|
+
EOF
|
|
1285
|
+
EXIT=0; $NODE "$FRAMEWORK_DIR/skills/qualia-polish-loop/scripts/loop.mjs" record \
|
|
1286
|
+
--state "$TMP_STATE2" --eval "$TMP_EVAL" >/dev/null 2>&1 || EXIT=$?
|
|
1287
|
+
if [ "$ITER" -lt 3 ] && [ "$EXIT" -ne 1 ]; then KILL_OK=false; fi
|
|
1288
|
+
if [ "$ITER" -eq 3 ] && [ "$EXIT" -ne 3 ]; then KILL_OK=false; fi
|
|
1289
|
+
done
|
|
1290
|
+
if [ "$KILL_OK" = true ] && grep -q '"verdict": "killed_regression"' "$TMP_STATE2"; then
|
|
1291
|
+
pass "loop.mjs kill-switch fires after 3 consecutive recurrences"
|
|
1292
|
+
else
|
|
1293
|
+
fail_case "loop.mjs kill-switch did not fire correctly"
|
|
1294
|
+
fi
|
|
1295
|
+
|
|
1296
|
+
# 117. visual-evaluator agent has trust-boundary block (security-critical)
|
|
1297
|
+
if grep -q "Trust boundary" "$TMP/.claude/agents/visual-evaluator.md"; then
|
|
1298
|
+
pass "visual-evaluator has trust-boundary block (refuses inlined-file injection)"
|
|
1299
|
+
else
|
|
1300
|
+
fail_case "visual-evaluator missing trust-boundary block"
|
|
1301
|
+
fi
|
|
1302
|
+
|
|
1303
|
+
echo ""
|
|
1304
|
+
echo "--- v5.1.0 (multi-target install: Claude / Codex / Both) ---"
|
|
1305
|
+
|
|
1306
|
+
# 118. Target=1 (Claude only) installs to ~/.claude/, not ~/.codex/
|
|
1307
|
+
TMP=$(mktmp)
|
|
1308
|
+
printf 'QS-FAWZI-01\n1\n' | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/log.txt" 2>&1
|
|
1309
|
+
EXIT=$?
|
|
1310
|
+
if [ "$EXIT" -eq 0 ] \
|
|
1311
|
+
&& [ -f "$TMP/.claude/.qualia-config.json" ] \
|
|
1312
|
+
&& [ ! -d "$TMP/.codex" ]; then
|
|
1313
|
+
pass "target=1 → ~/.claude/ populated, ~/.codex/ untouched"
|
|
1314
|
+
else
|
|
1315
|
+
fail_case "target=1 Claude-only" "exit=$EXIT codex_exists=$(test -d "$TMP/.codex" && echo yes || echo no)"
|
|
1316
|
+
fi
|
|
1317
|
+
|
|
1318
|
+
# 119. Target=2 (Codex only) writes ~/.codex/AGENTS.md, skips ~/.claude/
|
|
1319
|
+
TMP=$(mktmp)
|
|
1320
|
+
printf 'QS-FAWZI-01\n2\n' | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/log.txt" 2>&1
|
|
1321
|
+
EXIT=$?
|
|
1322
|
+
if [ "$EXIT" -eq 0 ] \
|
|
1323
|
+
&& [ -f "$TMP/.codex/AGENTS.md" ] \
|
|
1324
|
+
&& [ ! -d "$TMP/.claude" ] \
|
|
1325
|
+
&& grep -q "Role: OWNER" "$TMP/.codex/AGENTS.md" \
|
|
1326
|
+
&& ! grep -q "{{ROLE}}" "$TMP/.codex/AGENTS.md"; then
|
|
1327
|
+
pass "target=2 → ~/.codex/AGENTS.md with Role: OWNER, ~/.claude/ skipped"
|
|
1328
|
+
else
|
|
1329
|
+
fail_case "target=2 Codex-only" "exit=$EXIT claude_exists=$(test -d "$TMP/.claude" && echo yes || echo no)"
|
|
1330
|
+
fi
|
|
1331
|
+
|
|
1332
|
+
# 120. Target=3 (Both) populates both directories with the right artifacts
|
|
1333
|
+
TMP=$(mktmp)
|
|
1334
|
+
printf 'QS-FAWZI-01\n3\n' | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/log.txt" 2>&1
|
|
1335
|
+
EXIT=$?
|
|
1336
|
+
if [ "$EXIT" -eq 0 ] \
|
|
1337
|
+
&& [ -f "$TMP/.claude/.qualia-config.json" ] \
|
|
1338
|
+
&& [ -f "$TMP/.codex/AGENTS.md" ] \
|
|
1339
|
+
&& grep -q "Role: OWNER" "$TMP/.codex/AGENTS.md"; then
|
|
1340
|
+
pass "target=3 → both ~/.claude/ and ~/.codex/AGENTS.md populated"
|
|
1341
|
+
else
|
|
1342
|
+
fail_case "target=3 Both" "exit=$EXIT"
|
|
1343
|
+
fi
|
|
1344
|
+
|
|
1345
|
+
# 121. Backward compat: legacy single-line piped install (no target line)
|
|
1346
|
+
# defaults to Claude only. Same exact invocation as pre-v5.1.
|
|
1347
|
+
TMP=$(mktmp)
|
|
1348
|
+
echo "QS-FAWZI-01" | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/log.txt" 2>&1
|
|
1349
|
+
EXIT=$?
|
|
1350
|
+
if [ "$EXIT" -eq 0 ] \
|
|
1351
|
+
&& [ -f "$TMP/.claude/.qualia-config.json" ] \
|
|
1352
|
+
&& [ ! -d "$TMP/.codex" ]; then
|
|
1353
|
+
pass "legacy single-line piped install → defaults to Claude only"
|
|
1354
|
+
else
|
|
1355
|
+
fail_case "backward compat" "exit=$EXIT"
|
|
1356
|
+
fi
|
|
1357
|
+
|
|
1358
|
+
# 122. Codex AGENTS.md is backed up before overwrite (matches v5.0 CLAUDE.md
|
|
1359
|
+
# / settings.json discipline — never silently destroy user-edited files).
|
|
1360
|
+
TMP=$(mktmp)
|
|
1361
|
+
mkdir -p "$TMP/.codex"
|
|
1362
|
+
echo "OLD USER CONTENT" > "$TMP/.codex/AGENTS.md"
|
|
1363
|
+
printf 'QS-FAWZI-01\n2\n' | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/log.txt" 2>&1
|
|
1364
|
+
EXIT=$?
|
|
1365
|
+
BAK_COUNT=$(ls "$TMP/.codex/"AGENTS.md.bak.* 2>/dev/null | wc -l)
|
|
1366
|
+
if [ "$EXIT" -eq 0 ] \
|
|
1367
|
+
&& [ "$BAK_COUNT" -ge 1 ] \
|
|
1368
|
+
&& grep -q "OLD USER CONTENT" "$TMP/.codex/"AGENTS.md.bak.* \
|
|
1369
|
+
&& ! grep -q "OLD USER CONTENT" "$TMP/.codex/AGENTS.md"; then
|
|
1370
|
+
pass "Codex AGENTS.md backup-before-overwrite preserves prior content"
|
|
1371
|
+
else
|
|
1372
|
+
fail_case "Codex backup discipline" "exit=$EXIT bak_count=$BAK_COUNT"
|
|
1373
|
+
fi
|
|
1374
|
+
|
|
1375
|
+
# 123. Codex install with same content does NOT create a redundant .bak
|
|
1376
|
+
# (the v5.0 CLAUDE.md backup discipline only backs up if content differs).
|
|
1377
|
+
TMP=$(mktmp)
|
|
1378
|
+
printf 'QS-FAWZI-01\n2\n' | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/log.txt" 2>&1
|
|
1379
|
+
# Re-run with same input — content should be identical, no new backup.
|
|
1380
|
+
printf 'QS-FAWZI-01\n2\n' | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/log2.txt" 2>&1
|
|
1381
|
+
BAK_COUNT=$(ls "$TMP/.codex/"AGENTS.md.bak.* 2>/dev/null | wc -l)
|
|
1382
|
+
if [ "$BAK_COUNT" -eq 0 ]; then
|
|
1383
|
+
pass "Codex re-install with identical content → no redundant .bak"
|
|
1384
|
+
else
|
|
1385
|
+
fail_case "Codex backup over-zealous" "bak_count=$BAK_COUNT"
|
|
1386
|
+
fi
|
|
1387
|
+
|
|
1388
|
+
# 124. Non-TTY install log is free of cursor-control / line-clear escape
|
|
1389
|
+
# sequences (\r, \x1b[?25l/h hide-cursor, \x1b[2K clear-line). Spinner +
|
|
1390
|
+
# overwrite primitives must degrade cleanly when output is piped.
|
|
1391
|
+
TMP=$(mktmp)
|
|
1392
|
+
printf 'QS-FAWZI-01\n3\n' | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/log.txt" 2>&1
|
|
1393
|
+
# Look for any of: bare CR, hide-cursor (?25), clear-line (\x1b[2K).
|
|
1394
|
+
if [ -s "$TMP/log.txt" ] \
|
|
1395
|
+
&& ! grep -q $'\r' "$TMP/log.txt" \
|
|
1396
|
+
&& ! grep -q '?25' "$TMP/log.txt" \
|
|
1397
|
+
&& ! grep -q '\[2K' "$TMP/log.txt"; then
|
|
1398
|
+
pass "non-TTY install log degrades cleanly (no cursor-control escapes)"
|
|
1399
|
+
else
|
|
1400
|
+
fail_case "non-TTY log has orphan escape sequences"
|
|
1401
|
+
fi
|
|
1402
|
+
|
|
1403
|
+
# 125. Final summary card includes the new "Targets" + "Time" rows
|
|
1404
|
+
TMP=$(mktmp)
|
|
1405
|
+
printf 'QS-FAWZI-01\n3\n' | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/log.txt" 2>&1
|
|
1406
|
+
CLEAN=$(strip_ansi < "$TMP/log.txt")
|
|
1407
|
+
if echo "$CLEAN" | grep -q "Targets" \
|
|
1408
|
+
&& echo "$CLEAN" | grep -q "Claude Code · Codex" \
|
|
1409
|
+
&& echo "$CLEAN" | grep -qE "Time +[0-9]+(\.[0-9]+s|ms|s)"; then
|
|
1410
|
+
pass "summary card shows Targets + Time rows"
|
|
1411
|
+
else
|
|
1412
|
+
fail_case "summary card missing v5.1 rows"
|
|
1413
|
+
fi
|
|
1414
|
+
|
|
1415
|
+
# 126. qualia-ui.js exports the v5.1 live-progress primitives
|
|
1416
|
+
EXPORTED=$($NODE -e 'const u=require("'"$UI_JS"'"); console.log(["step","spinner","progress","box","kv","divider","section","sectionClose"].every(k=>typeof u[k]==="function") ? "ok" : "missing")')
|
|
1417
|
+
if [ "$EXPORTED" = "ok" ]; then
|
|
1418
|
+
pass "qualia-ui.js exports step/spinner/progress/box/kv/divider/section/sectionClose"
|
|
1419
|
+
else
|
|
1420
|
+
fail_case "qualia-ui exports missing" "got=$EXPORTED"
|
|
1421
|
+
fi
|
|
1422
|
+
|
|
1423
|
+
# 127. qualia-ui.js still works as CLI when invoked directly (require-vs-CLI gate)
|
|
1424
|
+
OUT=$($NODE "$UI_JS" ok "test" 2>&1)
|
|
1425
|
+
if echo "$OUT" | grep -q "test"; then
|
|
1426
|
+
pass "qualia-ui.js CLI dispatch still works after exports refactor"
|
|
1427
|
+
else
|
|
1428
|
+
fail_case "qualia-ui CLI broke"
|
|
1429
|
+
fi
|
|
1430
|
+
|
|
1431
|
+
# 128. package.json bumped to 5.1.x
|
|
1432
|
+
PKG_V=$($NODE -e 'console.log(require("'"$FRAMEWORK_DIR"'/package.json").version)')
|
|
1433
|
+
if echo "$PKG_V" | grep -qE "^5\.1\."; then
|
|
1434
|
+
pass "package.json version bumped to 5.1.x ($PKG_V)"
|
|
1435
|
+
else
|
|
1436
|
+
fail_case "package.json version not bumped to 5.1.x" "got=$PKG_V"
|
|
1437
|
+
fi
|
|
1438
|
+
|
|
995
1439
|
echo ""
|
|
996
1440
|
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
997
1441
|
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
|