@seanyao/roll 2026.522.1 → 2026.523.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/CHANGELOG.md +36 -0
- package/bin/dream-test-quality-scan +110 -0
- package/bin/roll +527 -84
- package/conventions/config.yaml +1 -1
- package/conventions/global/AGENTS.md +1 -1
- package/conventions/global/GEMINI.md +8 -3
- package/conventions/templates/backend-service/GEMINI.md +3 -3
- package/conventions/templates/cli/GEMINI.md +3 -3
- package/conventions/templates/frontend-only/GEMINI.md +3 -3
- package/conventions/templates/fullstack/GEMINI.md +3 -3
- package/lib/__pycache__/model_prices.cpython-314.pyc +0 -0
- package/lib/__pycache__/roll-loop-status.cpython-314.pyc +0 -0
- package/lib/__pycache__/roll_render.cpython-314.pyc +0 -0
- package/lib/loop-fmt.py +17 -3
- package/lib/model_prices.py +16 -5
- package/lib/roll-loop-status.py +92 -21
- package/lib/roll-peer.py +1 -1
- package/lib/roll-status.py +1 -1
- package/lib/roll_render.py +16 -3
- package/lib/slides/templates/introduction-v3.html +576 -0
- package/package.json +1 -1
- package/skills/roll-.dream/SKILL.md +59 -0
- package/skills/roll-deck/SKILL.md +22 -14
- package/skills/roll-design/SKILL.md +90 -3
- package/skills/roll-doctor/SKILL.md +1 -1
- package/skills/roll-notes/SKILL.md +6 -3
- package/skills/roll-onboard/SKILL.md +1 -1
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="dark" data-lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
6
|
+
<title>{{title_en}}</title>
|
|
7
|
+
<meta name="title-zh" content="{{title_zh}}">
|
|
8
|
+
<meta name="deck-slug" content="{{slug}}">
|
|
9
|
+
<style>
|
|
10
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Noto+Sans+SC:wght@300;400;500;600;700;900&display=swap');
|
|
11
|
+
|
|
12
|
+
:root {
|
|
13
|
+
--bg: #0a0a0f;
|
|
14
|
+
--bg-card: #12121a;
|
|
15
|
+
--bg-card-hover: #1a1a28;
|
|
16
|
+
--bg-control: rgba(18,18,26,0.92);
|
|
17
|
+
--text: #e8e8ef;
|
|
18
|
+
--text-dim: #8888a0;
|
|
19
|
+
--text-bright: #ffffff;
|
|
20
|
+
--accent: #6366f1;
|
|
21
|
+
--accent-light: #818cf8;
|
|
22
|
+
--green: #22c55e;
|
|
23
|
+
--green-text: #4ade80;
|
|
24
|
+
--orange: #f59e0b;
|
|
25
|
+
--orange-text: #fbbf24;
|
|
26
|
+
--red: #ef4444;
|
|
27
|
+
--red-text: #f87171;
|
|
28
|
+
--cyan: #06b6d4;
|
|
29
|
+
--cyan-text: #22d3ee;
|
|
30
|
+
--border: rgba(255,255,255,0.06);
|
|
31
|
+
--border-strong: rgba(255,255,255,0.12);
|
|
32
|
+
--shadow: 0 4px 24px rgba(0,0,0,0.3);
|
|
33
|
+
--radius: 16px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
[data-theme="light"] {
|
|
37
|
+
--bg: #fafafc;
|
|
38
|
+
--bg-card: #ffffff;
|
|
39
|
+
--bg-card-hover: #f3f4f8;
|
|
40
|
+
--bg-control: rgba(255,255,255,0.95);
|
|
41
|
+
--text: #1f2937;
|
|
42
|
+
--text-dim: #6b7280;
|
|
43
|
+
--text-bright: #0a0a0f;
|
|
44
|
+
--accent: #6366f1;
|
|
45
|
+
--accent-light: #4f46e5;
|
|
46
|
+
--green: #16a34a;
|
|
47
|
+
--green-text: #15803d;
|
|
48
|
+
--orange: #d97706;
|
|
49
|
+
--orange-text: #b45309;
|
|
50
|
+
--red: #dc2626;
|
|
51
|
+
--red-text: #b91c1c;
|
|
52
|
+
--cyan: #0891b2;
|
|
53
|
+
--cyan-text: #0e7490;
|
|
54
|
+
--border: rgba(15,23,42,0.08);
|
|
55
|
+
--border-strong: rgba(15,23,42,0.14);
|
|
56
|
+
--shadow: 0 4px 24px rgba(15,23,42,0.06);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
60
|
+
|
|
61
|
+
html, body {
|
|
62
|
+
font-family: 'Inter', 'Noto Sans SC', system-ui, sans-serif;
|
|
63
|
+
background: var(--bg);
|
|
64
|
+
color: var(--text);
|
|
65
|
+
height: 100%;
|
|
66
|
+
width: 100%;
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
transition: background 0.3s ease, color 0.3s ease;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Language switching: only show the active language */
|
|
72
|
+
[data-lang="en"] .lang-zh { display: none !important; }
|
|
73
|
+
[data-lang="zh"] .lang-en { display: none !important; }
|
|
74
|
+
|
|
75
|
+
#progress-bar {
|
|
76
|
+
position: fixed; top: 0; left: 0; height: 3px;
|
|
77
|
+
background: linear-gradient(90deg, var(--accent), var(--cyan));
|
|
78
|
+
z-index: 1000; transition: width 0.4s ease;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.slides-container { position: relative; width: 100vw; height: 100vh; }
|
|
82
|
+
|
|
83
|
+
.slide {
|
|
84
|
+
position: absolute; inset: 0;
|
|
85
|
+
display: flex; flex-direction: column;
|
|
86
|
+
justify-content: center; align-items: center;
|
|
87
|
+
padding: 64px 80px 96px;
|
|
88
|
+
opacity: 0; transform: translateY(30px);
|
|
89
|
+
transition: opacity 0.5s ease, transform 0.5s ease;
|
|
90
|
+
pointer-events: none; overflow-y: auto;
|
|
91
|
+
}
|
|
92
|
+
.slide.active { opacity: 1; transform: translateY(0); pointer-events: auto; }
|
|
93
|
+
.slide.exit-up { opacity: 0; transform: translateY(-30px); }
|
|
94
|
+
|
|
95
|
+
.slide-number {
|
|
96
|
+
position: absolute; top: 28px; right: 24px;
|
|
97
|
+
font-size: 13px; color: var(--text-dim); font-weight: 500;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.tag {
|
|
101
|
+
display: inline-block; padding: 5px 14px;
|
|
102
|
+
background: rgba(99,102,241,0.12); color: var(--accent-light);
|
|
103
|
+
border-radius: 20px; font-size: 12px; font-weight: 600;
|
|
104
|
+
letter-spacing: 0.06em; text-transform: uppercase; margin-bottom: 16px;
|
|
105
|
+
}
|
|
106
|
+
[data-theme="light"] .tag { background: rgba(99,102,241,0.1); }
|
|
107
|
+
|
|
108
|
+
h1 {
|
|
109
|
+
font-size: 52px; font-weight: 800; line-height: 1.15;
|
|
110
|
+
color: var(--text-bright); text-align: center; margin-bottom: 12px;
|
|
111
|
+
}
|
|
112
|
+
h1 .gradient {
|
|
113
|
+
background: linear-gradient(135deg, var(--accent-light), var(--cyan));
|
|
114
|
+
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
|
115
|
+
}
|
|
116
|
+
h2 {
|
|
117
|
+
font-size: 34px; font-weight: 700; line-height: 1.25;
|
|
118
|
+
color: var(--text-bright); margin-bottom: 12px; text-align: center;
|
|
119
|
+
}
|
|
120
|
+
h3 { font-size: 18px; font-weight: 600; color: var(--text-bright); margin-bottom: 6px; }
|
|
121
|
+
strong { color: var(--text-bright); }
|
|
122
|
+
|
|
123
|
+
.subtitle {
|
|
124
|
+
font-size: 18px; color: var(--text-dim); line-height: 1.6;
|
|
125
|
+
text-align: center; max-width: 720px;
|
|
126
|
+
}
|
|
127
|
+
.section-desc {
|
|
128
|
+
font-size: 15px; color: var(--text-dim); line-height: 1.7;
|
|
129
|
+
max-width: 820px; margin-bottom: 24px; text-align: center;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.cards { display: grid; gap: 14px; width: 100%; max-width: 1100px; }
|
|
133
|
+
.cards-2 { grid-template-columns: 1fr 1fr; }
|
|
134
|
+
.cards-3 { grid-template-columns: 1fr 1fr 1fr; }
|
|
135
|
+
.cards-4 { grid-template-columns: repeat(4, 1fr); }
|
|
136
|
+
|
|
137
|
+
.card {
|
|
138
|
+
background: var(--bg-card); border: 1px solid var(--border);
|
|
139
|
+
border-radius: var(--radius); padding: 20px 20px;
|
|
140
|
+
transition: all 0.3s ease;
|
|
141
|
+
box-shadow: var(--shadow);
|
|
142
|
+
}
|
|
143
|
+
.card:hover { background: var(--bg-card-hover); transform: translateY(-2px); }
|
|
144
|
+
.card p { font-size: 13px; color: var(--text-dim); line-height: 1.6; }
|
|
145
|
+
.card .small { font-size: 11px; }
|
|
146
|
+
|
|
147
|
+
.metric-row {
|
|
148
|
+
display: flex; gap: 14px; width: 100%; max-width: 1000px;
|
|
149
|
+
justify-content: center; margin-top: 8px;
|
|
150
|
+
}
|
|
151
|
+
.metric {
|
|
152
|
+
text-align: center; padding: 20px 14px;
|
|
153
|
+
background: var(--bg-card); border: 1px solid var(--border);
|
|
154
|
+
border-radius: var(--radius); flex: 1;
|
|
155
|
+
box-shadow: var(--shadow);
|
|
156
|
+
}
|
|
157
|
+
.metric .number { font-size: 32px; font-weight: 800; line-height: 1; margin-bottom: 6px; }
|
|
158
|
+
.metric .label { font-size: 12px; color: var(--text-dim); font-weight: 500; }
|
|
159
|
+
|
|
160
|
+
.compare {
|
|
161
|
+
display: grid; grid-template-columns: 1fr 50px 1fr;
|
|
162
|
+
gap: 0; width: 100%; max-width: 1000px; align-items: start;
|
|
163
|
+
}
|
|
164
|
+
.compare-col { padding: 22px 20px; border-radius: var(--radius); }
|
|
165
|
+
.compare-before { background: rgba(239,68,68,0.06); border: 1px solid rgba(239,68,68,0.18); }
|
|
166
|
+
.compare-after { background: rgba(34,197,94,0.06); border: 1px solid rgba(34,197,94,0.18); }
|
|
167
|
+
[data-theme="light"] .compare-before { background: rgba(239,68,68,0.04); }
|
|
168
|
+
[data-theme="light"] .compare-after { background: rgba(34,197,94,0.04); }
|
|
169
|
+
.compare-arrow {
|
|
170
|
+
display: flex; align-items: center; justify-content: center;
|
|
171
|
+
font-size: 24px; color: var(--text-dim); padding-top: 36px;
|
|
172
|
+
}
|
|
173
|
+
.compare-col h3 { margin-bottom: 14px; font-size: 15px; }
|
|
174
|
+
.compare-item {
|
|
175
|
+
display: flex; align-items: flex-start; gap: 8px;
|
|
176
|
+
margin-bottom: 10px; font-size: 13px; line-height: 1.5; color: var(--text);
|
|
177
|
+
}
|
|
178
|
+
.compare-item .icon { flex-shrink: 0; font-size: 13px; margin-top: 2px; }
|
|
179
|
+
|
|
180
|
+
.pipeline-bar {
|
|
181
|
+
display: flex; gap: 4px; width: 100%; max-width: 1000px; margin-top: 8px;
|
|
182
|
+
}
|
|
183
|
+
.pipe-stage {
|
|
184
|
+
flex: 1; text-align: center; padding: 14px 8px; border-radius: 12px;
|
|
185
|
+
}
|
|
186
|
+
.pipe-stage h4 { font-size: 16px; font-weight: 700; }
|
|
187
|
+
.pipe-stage p { font-size: 11px; color: var(--text-dim); margin-top: 4px; line-height: 1.4; }
|
|
188
|
+
.pipe-idea, .pipe-backlog { background: rgba(99,102,241,0.08); border: 1.5px solid rgba(99,102,241,0.3); }
|
|
189
|
+
.pipe-idea h4, .pipe-backlog h4 { color: var(--accent-light); }
|
|
190
|
+
.pipe-build { background: rgba(34,197,94,0.08); border: 1.5px solid rgba(34,197,94,0.3); }
|
|
191
|
+
.pipe-build h4 { color: var(--green-text); }
|
|
192
|
+
.pipe-verify { background: rgba(245,158,11,0.08); border: 1.5px solid rgba(245,158,11,0.3); }
|
|
193
|
+
.pipe-verify h4 { color: var(--orange-text); }
|
|
194
|
+
.pipe-release { background: rgba(6,182,212,0.08); border: 1.5px solid rgba(6,182,212,0.3); }
|
|
195
|
+
.pipe-release h4 { color: var(--cyan-text); }
|
|
196
|
+
.pipe-arrow { display: flex; align-items: center; justify-content: center; font-size: 16px; color: var(--text-dim); padding: 0 2px; }
|
|
197
|
+
|
|
198
|
+
/* Human-AI bar */
|
|
199
|
+
.hai-bar { display: flex; gap: 4px; width: 100%; max-width: 1000px; margin-top: 8px; }
|
|
200
|
+
.hai-zone { text-align: center; padding: 14px 10px; border-radius: 12px; }
|
|
201
|
+
.hai-zone .emoji { font-size: 20px; }
|
|
202
|
+
.hai-zone h4 { font-size: 14px; font-weight: 700; margin-top: 4px; }
|
|
203
|
+
.hai-zone p { font-size: 11px; color: var(--text-dim); margin-top: 4px; line-height: 1.5; }
|
|
204
|
+
.hai-human { flex: 2; background: rgba(99,102,241,0.06); border: 1px solid rgba(99,102,241,0.18); }
|
|
205
|
+
.hai-human h4 { color: var(--accent-light); }
|
|
206
|
+
.hai-ai { flex: 2; background: rgba(34,197,94,0.06); border: 1px solid rgba(34,197,94,0.18); }
|
|
207
|
+
.hai-ai h4 { color: var(--green-text); }
|
|
208
|
+
.hai-push { flex: 1; background: rgba(245,158,11,0.06); border: 1px solid rgba(245,158,11,0.18); }
|
|
209
|
+
.hai-push h4 { color: var(--orange-text); }
|
|
210
|
+
|
|
211
|
+
.quote-block {
|
|
212
|
+
border-left: 3px solid var(--accent);
|
|
213
|
+
padding: 14px 22px; margin: 14px 0;
|
|
214
|
+
background: rgba(99,102,241,0.06);
|
|
215
|
+
border-radius: 0 10px 10px 0; max-width: 720px;
|
|
216
|
+
}
|
|
217
|
+
.quote-block p { font-size: 15px; font-style: italic; color: var(--text); line-height: 1.6; }
|
|
218
|
+
|
|
219
|
+
.highlight-box {
|
|
220
|
+
background: linear-gradient(135deg, rgba(99,102,241,0.1), rgba(6,182,212,0.1));
|
|
221
|
+
border: 1px solid rgba(99,102,241,0.2);
|
|
222
|
+
border-radius: var(--radius); padding: 20px 24px; max-width: 820px; text-align: center;
|
|
223
|
+
}
|
|
224
|
+
.highlight-box p { font-size: 14px; color: var(--text); line-height: 1.6; }
|
|
225
|
+
|
|
226
|
+
.analogy {
|
|
227
|
+
background: rgba(245,158,11,0.06); border: 1px solid rgba(245,158,11,0.18);
|
|
228
|
+
border-radius: var(--radius); padding: 14px 20px; max-width: 820px; margin-top: 14px;
|
|
229
|
+
}
|
|
230
|
+
.analogy-label {
|
|
231
|
+
font-size: 11px; font-weight: 700; color: var(--orange-text);
|
|
232
|
+
text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 5px;
|
|
233
|
+
}
|
|
234
|
+
.analogy p { font-size: 13px; line-height: 1.6; color: var(--text); }
|
|
235
|
+
|
|
236
|
+
.flow-row {
|
|
237
|
+
display: flex; align-items: center; gap: 8px;
|
|
238
|
+
flex-wrap: wrap; justify-content: center;
|
|
239
|
+
}
|
|
240
|
+
.flow-step {
|
|
241
|
+
padding: 10px 16px; border-radius: 10px;
|
|
242
|
+
font-size: 13px; font-weight: 600;
|
|
243
|
+
}
|
|
244
|
+
.flow-arrow { font-size: 16px; color: var(--text-dim); }
|
|
245
|
+
.flow-branch { display: flex; flex-direction: column; gap: 6px; align-items: center; }
|
|
246
|
+
|
|
247
|
+
.timeline {
|
|
248
|
+
width: 100%; max-width: 880px; position: relative; padding-left: 36px;
|
|
249
|
+
}
|
|
250
|
+
.timeline::before {
|
|
251
|
+
content: ''; position: absolute; left: 12px; top: 0; bottom: 0;
|
|
252
|
+
width: 2px; background: var(--border-strong);
|
|
253
|
+
}
|
|
254
|
+
.timeline-item { position: relative; margin-bottom: 14px; padding-left: 24px; }
|
|
255
|
+
.timeline-item::before {
|
|
256
|
+
content: ''; position: absolute; left: -30px; top: 5px;
|
|
257
|
+
width: 10px; height: 10px; border-radius: 50%;
|
|
258
|
+
background: var(--accent); box-shadow: 0 0 10px rgba(99,102,241,0.3);
|
|
259
|
+
}
|
|
260
|
+
.timeline-item h4 { font-size: 14px; font-weight: 700; color: var(--text-bright); margin-bottom: 3px; }
|
|
261
|
+
.timeline-item p { font-size: 12.5px; color: var(--text-dim); line-height: 1.5; }
|
|
262
|
+
|
|
263
|
+
/* Skill table */
|
|
264
|
+
.skill-table {
|
|
265
|
+
width: 100%; max-width: 1060px; border-collapse: collapse; margin-top: 8px;
|
|
266
|
+
}
|
|
267
|
+
.skill-table th {
|
|
268
|
+
text-align: left; font-size: 11px; font-weight: 700; color: var(--text-dim);
|
|
269
|
+
text-transform: uppercase; letter-spacing: 0.06em;
|
|
270
|
+
padding: 6px 10px; border-bottom: 1px solid var(--border-strong);
|
|
271
|
+
}
|
|
272
|
+
.skill-table td {
|
|
273
|
+
padding: 7px 10px; font-size: 12.5px; color: var(--text);
|
|
274
|
+
border-bottom: 1px solid var(--border); vertical-align: top;
|
|
275
|
+
}
|
|
276
|
+
[data-theme="dark"] .skill-table tr:hover td { background: rgba(255,255,255,0.02); }
|
|
277
|
+
[data-theme="light"] .skill-table tr:hover td { background: rgba(15,23,42,0.02); }
|
|
278
|
+
.skill-name {
|
|
279
|
+
font-family: 'SF Mono', 'Fira Code', monospace; font-weight: 600;
|
|
280
|
+
font-size: 12px; white-space: nowrap;
|
|
281
|
+
}
|
|
282
|
+
.phase-tag {
|
|
283
|
+
font-size: 10px; padding: 2px 7px; border-radius: 4px; font-weight: 600;
|
|
284
|
+
display: inline-block;
|
|
285
|
+
}
|
|
286
|
+
.phase-design { background: rgba(99,102,241,0.15); color: var(--accent-light); }
|
|
287
|
+
.phase-build { background: rgba(34,197,94,0.15); color: var(--green-text); }
|
|
288
|
+
.phase-check { background: rgba(245,158,11,0.15); color: var(--orange-text); }
|
|
289
|
+
.phase-auto { background: rgba(6,182,212,0.15); color: var(--cyan-text); }
|
|
290
|
+
.phase-support { background: rgba(255,255,255,0.06); color: var(--text-dim); }
|
|
291
|
+
[data-theme="light"] .phase-support { background: rgba(15,23,42,0.06); }
|
|
292
|
+
|
|
293
|
+
/* Autonomous layers */
|
|
294
|
+
.auto-layers {
|
|
295
|
+
display: flex; gap: 12px; width: 100%; max-width: 1000px; margin-top: 8px;
|
|
296
|
+
}
|
|
297
|
+
.auto-layer {
|
|
298
|
+
flex: 1; text-align: center; padding: 18px 14px; border-radius: var(--radius);
|
|
299
|
+
background: var(--bg-card); border: 1px solid var(--border);
|
|
300
|
+
box-shadow: var(--shadow);
|
|
301
|
+
}
|
|
302
|
+
.auto-layer .al-icon { font-size: 28px; }
|
|
303
|
+
.auto-layer h4 { font-size: 14px; font-weight: 700; color: var(--text-bright); margin-top: 6px; }
|
|
304
|
+
.auto-layer p { font-size: 12px; color: var(--text-dim); margin-top: 6px; line-height: 1.5; }
|
|
305
|
+
.auto-layer .al-skills {
|
|
306
|
+
font-size: 10px; color: var(--text-dim); margin-top: 8px;
|
|
307
|
+
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.tree {
|
|
311
|
+
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
312
|
+
font-size: 13px; line-height: 1.7; color: var(--text);
|
|
313
|
+
background: var(--bg-card); border: 1px solid var(--border);
|
|
314
|
+
border-radius: var(--radius); padding: 20px 24px;
|
|
315
|
+
max-width: 720px; width: 100%;
|
|
316
|
+
box-shadow: var(--shadow);
|
|
317
|
+
}
|
|
318
|
+
.tree .comment { color: var(--text-dim); }
|
|
319
|
+
.tree .file { color: var(--cyan-text); }
|
|
320
|
+
.tree .dir { color: var(--accent-light); }
|
|
321
|
+
|
|
322
|
+
.skill-tag {
|
|
323
|
+
font-size: 11px; padding: 4px 8px; border-radius: 6px;
|
|
324
|
+
font-weight: 600; font-family: 'SF Mono', 'Fira Code', monospace;
|
|
325
|
+
background: rgba(99,102,241,0.15); color: var(--accent-light);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/* Loop overview pills */
|
|
329
|
+
.loops-row {
|
|
330
|
+
display: flex; gap: 4px; width: 100%; max-width: 1000px; margin-top: 12px;
|
|
331
|
+
}
|
|
332
|
+
.loop-pill {
|
|
333
|
+
flex: 1; text-align: center; padding: 8px 6px; border-radius: 8px;
|
|
334
|
+
border: 1px dashed;
|
|
335
|
+
}
|
|
336
|
+
.loop-pill.la { border-color: rgba(99,102,241,0.4); background: rgba(99,102,241,0.04); }
|
|
337
|
+
.loop-pill.lb { border-color: rgba(34,197,94,0.4); background: rgba(34,197,94,0.04); }
|
|
338
|
+
.loop-pill.lc { border-color: rgba(245,158,11,0.4); background: rgba(245,158,11,0.04); }
|
|
339
|
+
.loop-pill h5 { font-size: 12px; font-weight: 700; }
|
|
340
|
+
.loop-pill.la h5 { color: var(--accent-light); }
|
|
341
|
+
.loop-pill.lb h5 { color: var(--green-text); }
|
|
342
|
+
.loop-pill.lc h5 { color: var(--orange-text); }
|
|
343
|
+
.loop-pill p { font-size: 10px; color: var(--text-dim); margin-top: 2px; }
|
|
344
|
+
|
|
345
|
+
/* Controls (theme + lang) */
|
|
346
|
+
.controls {
|
|
347
|
+
position: fixed; top: 18px; left: 18px;
|
|
348
|
+
display: flex; gap: 8px; z-index: 200;
|
|
349
|
+
}
|
|
350
|
+
.ctrl-btn {
|
|
351
|
+
background: var(--bg-control); backdrop-filter: blur(20px);
|
|
352
|
+
border: 1px solid var(--border-strong);
|
|
353
|
+
color: var(--text); font-size: 12px; font-weight: 600;
|
|
354
|
+
padding: 8px 14px; border-radius: 24px; cursor: pointer;
|
|
355
|
+
display: flex; align-items: center; gap: 6px;
|
|
356
|
+
transition: all 0.2s;
|
|
357
|
+
min-height: 36px;
|
|
358
|
+
}
|
|
359
|
+
.ctrl-btn:hover { transform: translateY(-1px); border-color: var(--accent-light); }
|
|
360
|
+
.ctrl-btn svg { width: 14px; height: 14px; }
|
|
361
|
+
.ctrl-btn .label { font-size: 12px; }
|
|
362
|
+
|
|
363
|
+
/* Nav */
|
|
364
|
+
.nav {
|
|
365
|
+
position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
|
|
366
|
+
display: flex; align-items: center; gap: 14px; z-index: 100;
|
|
367
|
+
background: var(--bg-control); backdrop-filter: blur(20px);
|
|
368
|
+
padding: 8px 18px; border-radius: 40px; border: 1px solid var(--border-strong);
|
|
369
|
+
}
|
|
370
|
+
.nav-btn {
|
|
371
|
+
width: 36px; height: 36px; border-radius: 50%;
|
|
372
|
+
background: rgba(127,127,127,0.08); border: 1px solid var(--border-strong);
|
|
373
|
+
color: var(--text); font-size: 18px; cursor: pointer;
|
|
374
|
+
display: flex; align-items: center; justify-content: center; transition: all 0.2s;
|
|
375
|
+
}
|
|
376
|
+
.nav-btn:hover { background: rgba(127,127,127,0.16); }
|
|
377
|
+
.nav-btn:disabled { opacity: 0.3; cursor: default; }
|
|
378
|
+
.nav-info { font-size: 12px; color: var(--text-dim); font-weight: 500; min-width: 50px; text-align: center; }
|
|
379
|
+
|
|
380
|
+
.key-hint {
|
|
381
|
+
position: fixed; bottom: 32px; right: 24px;
|
|
382
|
+
font-size: 11px; color: var(--text-dim); display: flex; gap: 10px; z-index: 100;
|
|
383
|
+
}
|
|
384
|
+
.key-hint kbd {
|
|
385
|
+
background: rgba(127,127,127,0.1); border: 1px solid var(--border-strong);
|
|
386
|
+
padding: 1px 6px; border-radius: 3px; font-family: inherit;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.fade-in { opacity: 0; transform: translateY(16px); }
|
|
390
|
+
.slide.active .fade-in { animation: fadeUp 0.5s ease forwards; }
|
|
391
|
+
.slide.active .fade-in:nth-child(2) { animation-delay: 0.06s; }
|
|
392
|
+
.slide.active .fade-in:nth-child(3) { animation-delay: 0.12s; }
|
|
393
|
+
.slide.active .fade-in:nth-child(4) { animation-delay: 0.18s; }
|
|
394
|
+
.slide.active .fade-in:nth-child(5) { animation-delay: 0.24s; }
|
|
395
|
+
.slide.active .fade-in:nth-child(6) { animation-delay: 0.30s; }
|
|
396
|
+
@keyframes fadeUp { to { opacity: 1; transform: translateY(0); } }
|
|
397
|
+
|
|
398
|
+
.slide::-webkit-scrollbar { width: 4px; }
|
|
399
|
+
.slide::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 2px; }
|
|
400
|
+
|
|
401
|
+
/* Mobile adaptation */
|
|
402
|
+
@media (max-width: 900px) {
|
|
403
|
+
.slide { padding: 64px 18px 110px; justify-content: flex-start; }
|
|
404
|
+
h1 { font-size: 30px; }
|
|
405
|
+
h2 { font-size: 22px; line-height: 1.3; }
|
|
406
|
+
h3 { font-size: 15px; }
|
|
407
|
+
.subtitle { font-size: 15px; }
|
|
408
|
+
.section-desc { font-size: 13px; margin-bottom: 16px; }
|
|
409
|
+
.tag { font-size: 11px; padding: 4px 11px; margin-bottom: 12px; }
|
|
410
|
+
.cards-2, .cards-3, .cards-4 { grid-template-columns: 1fr; }
|
|
411
|
+
.compare { grid-template-columns: 1fr; gap: 12px; }
|
|
412
|
+
.compare-arrow { transform: rotate(90deg); padding: 0; }
|
|
413
|
+
.pipeline-bar { flex-direction: column; }
|
|
414
|
+
.pipe-arrow { transform: rotate(90deg); padding: 4px 0; }
|
|
415
|
+
.hai-bar { flex-direction: column; }
|
|
416
|
+
.auto-layers { flex-direction: column; }
|
|
417
|
+
.loops-row { flex-direction: column; }
|
|
418
|
+
.metric-row { flex-direction: column; }
|
|
419
|
+
.timeline { padding-left: 28px; }
|
|
420
|
+
.timeline-item { padding-left: 16px; }
|
|
421
|
+
.skill-table { font-size: 11px; }
|
|
422
|
+
.skill-table th:nth-child(2), .skill-table td:nth-child(2) { display: none; }
|
|
423
|
+
.slide-number { top: 16px; right: 16px; font-size: 11px; }
|
|
424
|
+
.controls { top: 12px; left: 12px; gap: 6px; }
|
|
425
|
+
.ctrl-btn { padding: 6px 10px; font-size: 11px; min-height: 32px; }
|
|
426
|
+
.ctrl-btn .label { display: none; }
|
|
427
|
+
.key-hint { display: none; }
|
|
428
|
+
.nav { bottom: 16px; padding: 6px 14px; }
|
|
429
|
+
.nav-btn { width: 32px; height: 32px; font-size: 16px; }
|
|
430
|
+
.tree { font-size: 11px; padding: 14px 16px; overflow-x: auto; white-space: pre; }
|
|
431
|
+
.quote-block { padding: 12px 16px; }
|
|
432
|
+
.quote-block p { font-size: 13px; }
|
|
433
|
+
.highlight-box { padding: 14px 16px; }
|
|
434
|
+
.highlight-box p { font-size: 13px; }
|
|
435
|
+
.flow-step { font-size: 12px; padding: 8px 12px; }
|
|
436
|
+
.auto-layer { padding: 14px 12px; }
|
|
437
|
+
.auto-layer .al-icon { font-size: 24px; }
|
|
438
|
+
.card { padding: 16px; }
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
@media (max-width: 480px) {
|
|
442
|
+
h1 { font-size: 26px; }
|
|
443
|
+
h2 { font-size: 19px; }
|
|
444
|
+
.slide { padding: 60px 14px 100px; }
|
|
445
|
+
}
|
|
446
|
+
</style>
|
|
447
|
+
</head>
|
|
448
|
+
<body>
|
|
449
|
+
|
|
450
|
+
<div id="progress-bar"></div>
|
|
451
|
+
|
|
452
|
+
<!-- Controls -->
|
|
453
|
+
<div class="controls">
|
|
454
|
+
<button class="ctrl-btn" id="btn-theme" onclick="toggleTheme()" aria-label="Toggle theme">
|
|
455
|
+
<span id="theme-icon">🌙</span>
|
|
456
|
+
<span class="label" id="theme-label">Dark</span>
|
|
457
|
+
</button>
|
|
458
|
+
<button class="ctrl-btn" id="btn-lang" onclick="toggleLang()" aria-label="Toggle language">
|
|
459
|
+
<span>🌐</span>
|
|
460
|
+
<span class="label" id="lang-label">EN</span>
|
|
461
|
+
</button>
|
|
462
|
+
</div>
|
|
463
|
+
|
|
464
|
+
<div class="slides-container" id="slides">
|
|
465
|
+
{{#slides}}
|
|
466
|
+
<!-- {{number}}: {{title_en}} -->
|
|
467
|
+
<div class="slide{{#is_cover}} active{{/is_cover}}" data-slide="{{number}}">
|
|
468
|
+
<div class="lang-en">
|
|
469
|
+
{{{body_en}}}
|
|
470
|
+
</div>
|
|
471
|
+
<div class="lang-zh">
|
|
472
|
+
{{{body_zh}}}
|
|
473
|
+
</div>
|
|
474
|
+
{{^is_cover}}<span class="slide-number">{{number_padded}} / {{total_slides}}</span>{{/is_cover}}
|
|
475
|
+
</div>
|
|
476
|
+
{{/slides}}
|
|
477
|
+
</div>
|
|
478
|
+
|
|
479
|
+
<!-- Nav -->
|
|
480
|
+
<div class="nav">
|
|
481
|
+
<button class="nav-btn" id="btn-prev" onclick="navigate(-1)" aria-label="Previous slide">‹</button>
|
|
482
|
+
<span class="nav-info" id="nav-info">1 / {{total_slides}}</span>
|
|
483
|
+
<button class="nav-btn" id="btn-next" onclick="navigate(1)" aria-label="Next slide">›</button>
|
|
484
|
+
</div>
|
|
485
|
+
<div class="key-hint">
|
|
486
|
+
<span><kbd>←</kbd> <kbd>→</kbd> <span class="lang-en">Navigate</span><span class="lang-zh">翻页</span></span>
|
|
487
|
+
<span><kbd>F</kbd> <span class="lang-en">Fullscreen</span><span class="lang-zh">全屏</span></span>
|
|
488
|
+
<span><kbd>T</kbd> <span class="lang-en">Theme</span><span class="lang-zh">主题</span></span>
|
|
489
|
+
<span><kbd>L</kbd> <span class="lang-en">Lang</span><span class="lang-zh">语言</span></span>
|
|
490
|
+
</div>
|
|
491
|
+
|
|
492
|
+
<script>
|
|
493
|
+
const slides = document.querySelectorAll('.slide');
|
|
494
|
+
const total = slides.length;
|
|
495
|
+
let current = 0;
|
|
496
|
+
|
|
497
|
+
function showSlide(i) {
|
|
498
|
+
slides.forEach((s, idx) => {
|
|
499
|
+
s.classList.remove('active', 'exit-up');
|
|
500
|
+
if (idx < i) s.classList.add('exit-up');
|
|
501
|
+
});
|
|
502
|
+
slides[i].classList.add('active');
|
|
503
|
+
document.getElementById('nav-info').textContent = `${i + 1} / ${total}`;
|
|
504
|
+
document.getElementById('btn-prev').disabled = i === 0;
|
|
505
|
+
document.getElementById('btn-next').disabled = i === total - 1;
|
|
506
|
+
document.getElementById('progress-bar').style.width = `${((i + 1) / total) * 100}%`;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function navigate(dir) {
|
|
510
|
+
const next = current + dir;
|
|
511
|
+
if (next >= 0 && next < total) { current = next; showSlide(current); }
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Theme toggle
|
|
515
|
+
function applyTheme(theme) {
|
|
516
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
517
|
+
document.getElementById('theme-icon').textContent = theme === 'dark' ? '🌙' : '☀️';
|
|
518
|
+
document.getElementById('theme-label').textContent = theme === 'dark' ? 'Dark' : 'Light';
|
|
519
|
+
try { localStorage.setItem('roll-slides-theme', theme); } catch (e) {}
|
|
520
|
+
}
|
|
521
|
+
function toggleTheme() {
|
|
522
|
+
const cur = document.documentElement.getAttribute('data-theme') || 'dark';
|
|
523
|
+
applyTheme(cur === 'dark' ? 'light' : 'dark');
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Language toggle
|
|
527
|
+
function applyLang(lang) {
|
|
528
|
+
document.documentElement.setAttribute('data-lang', lang);
|
|
529
|
+
document.documentElement.setAttribute('lang', lang === 'zh' ? 'zh-CN' : 'en');
|
|
530
|
+
document.getElementById('lang-label').textContent = lang === 'en' ? 'EN' : '中文';
|
|
531
|
+
try { localStorage.setItem('roll-slides-lang', lang); } catch (e) {}
|
|
532
|
+
}
|
|
533
|
+
function toggleLang() {
|
|
534
|
+
const cur = document.documentElement.getAttribute('data-lang') || 'en';
|
|
535
|
+
applyLang(cur === 'en' ? 'zh' : 'en');
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Restore preferences
|
|
539
|
+
try {
|
|
540
|
+
const savedTheme = localStorage.getItem('roll-slides-theme');
|
|
541
|
+
if (savedTheme === 'light' || savedTheme === 'dark') applyTheme(savedTheme);
|
|
542
|
+
const savedLang = localStorage.getItem('roll-slides-lang');
|
|
543
|
+
if (savedLang === 'en' || savedLang === 'zh') applyLang(savedLang);
|
|
544
|
+
else {
|
|
545
|
+
// Auto-detect: prefer ZH if browser is Chinese
|
|
546
|
+
const nav = (navigator.language || '').toLowerCase();
|
|
547
|
+
if (nav.startsWith('zh')) applyLang('zh');
|
|
548
|
+
}
|
|
549
|
+
} catch (e) {}
|
|
550
|
+
|
|
551
|
+
document.addEventListener('keydown', (e) => {
|
|
552
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowDown' || e.key === ' ') { e.preventDefault(); navigate(1); }
|
|
553
|
+
else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { e.preventDefault(); navigate(-1); }
|
|
554
|
+
else if (e.key === 'f' || e.key === 'F') {
|
|
555
|
+
document.fullscreenElement ? document.exitFullscreen() : document.documentElement.requestFullscreen();
|
|
556
|
+
}
|
|
557
|
+
else if (e.key === 't' || e.key === 'T') { toggleTheme(); }
|
|
558
|
+
else if (e.key === 'l' || e.key === 'L') { toggleLang(); }
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// Touch swipe
|
|
562
|
+
let tx = 0, ty = 0;
|
|
563
|
+
document.addEventListener('touchstart', e => {
|
|
564
|
+
tx = e.touches[0].clientX; ty = e.touches[0].clientY;
|
|
565
|
+
}, { passive: true });
|
|
566
|
+
document.addEventListener('touchend', e => {
|
|
567
|
+
const dx = tx - e.changedTouches[0].clientX;
|
|
568
|
+
const dy = ty - e.changedTouches[0].clientY;
|
|
569
|
+
// Only treat as swipe when horizontal movement dominates and is significant
|
|
570
|
+
if (Math.abs(dx) > 60 && Math.abs(dx) > Math.abs(dy) * 1.5) navigate(dx > 0 ? 1 : -1);
|
|
571
|
+
}, { passive: true });
|
|
572
|
+
|
|
573
|
+
showSlide(0);
|
|
574
|
+
</script>
|
|
575
|
+
</body>
|
|
576
|
+
</html>
|
package/package.json
CHANGED
|
@@ -224,6 +224,65 @@ Add after `## 文档覆盖度` section:
|
|
|
224
224
|
{发现内容列表 或 "文档新鲜度良好,无滞后或缺失项。"}
|
|
225
225
|
```
|
|
226
226
|
|
|
227
|
+
### Scan 7 — Test Quality (rubric-driven)
|
|
228
|
+
|
|
229
|
+
Apply the test-quality rubric at [guide/en/testing/quality-rubric.md](../../guide/en/testing/quality-rubric.md)
|
|
230
|
+
(Chinese: [quality-rubric.zh.md](../../guide/zh/testing/quality-rubric.md)) against every file under
|
|
231
|
+
`tests/`. The rubric publishes six anti-pattern categories (❶..❻); each has a
|
|
232
|
+
**Signals** subsection that lists the matching heuristics. Scan 7 is purely a
|
|
233
|
+
mechanical apply-the-rubric step — no new logic.
|
|
234
|
+
|
|
235
|
+
**Per-category signals** — read from the rubric, summarized here:
|
|
236
|
+
|
|
237
|
+
| Marker | Anti-pattern | Cheapest signal |
|
|
238
|
+
|--------|--------------|-----------------|
|
|
239
|
+
| ❶ | Hardcoded business data | Bare numeric / version / pricing literal inside `[[ "$output" == *"..."*` that matches a value also defined in `lib/` |
|
|
240
|
+
| ❷ | Over-mocking real boundaries | `function git() {` / `function gh() {` overrides at the top of a unit test |
|
|
241
|
+
| ❸ | Asserting implementation details | `grep '_internal_helper'` against output; assertions on `.roll/internal/*` paths |
|
|
242
|
+
| ❹ | Fixture order coupling | `setup_file` writes shared mutable state without per-test reset |
|
|
243
|
+
| ❺ | Testing private functions | Test sources a `lib/` file and calls a `_underscore_prefixed` helper directly |
|
|
244
|
+
| ❻ | Asserting framework behavior | References to `$BATS_TEST_NUMBER`, `$BATS_SUITE_NAME` in assertions |
|
|
245
|
+
|
|
246
|
+
**Rate cap — 每轮 ≤ 5 条 test-quality REFACTOR entries**. Same dream cycle may
|
|
247
|
+
emit more than 5 findings; the dream scan must rank by severity (❶ > ❷ > ❸ > ❹ > ❺ > ❻
|
|
248
|
+
and within a class, by occurrence count) and only persist the top 5 to BACKLOG.
|
|
249
|
+
Remaining findings go into the dream log under `## 测试质量` but are not made
|
|
250
|
+
into REFACTOR rows — this prevents the backlog from being drowned in test-debt
|
|
251
|
+
on the first scan after rubric publication.
|
|
252
|
+
|
|
253
|
+
**REFACTOR entry format** — same as other scans, but tagged with category:
|
|
254
|
+
|
|
255
|
+
```markdown
|
|
256
|
+
| REFACTOR-XXX | docs: <one-line description> [test-quality:❶] — flagged by dream YYYY-MM-DD | 📋 Todo |
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The `[test-quality:❶]` (through `❻`) tag is **required** so downstream filtering
|
|
260
|
+
(e.g. "show me all ❶ items still open") is mechanical. The marker character must
|
|
261
|
+
match the rubric exactly.
|
|
262
|
+
|
|
263
|
+
**Optional helper** — `bin/dream-test-quality-scan` is a thin shell script
|
|
264
|
+
maintainers can invoke ad-hoc to dry-run the ❶ detector against a single file
|
|
265
|
+
or directory (see `bin/dream-test-quality-scan --help`). The dream skill itself
|
|
266
|
+
does **not** depend on the helper — Scan 7 is the AI agent applying the rubric.
|
|
267
|
+
The helper just exists so a maintainer (or this skill's smoke test) can confirm
|
|
268
|
+
the ❶ heuristic still finds known instances.
|
|
269
|
+
|
|
270
|
+
#### Dream Log Section (Scan 7)
|
|
271
|
+
|
|
272
|
+
Add after `## 文档新鲜度` section:
|
|
273
|
+
|
|
274
|
+
```markdown
|
|
275
|
+
## 测试质量
|
|
276
|
+
- 本轮发现 {N} 项(写入 BACKLOG 的前 5 项见下;剩余 {M} 项仅记录于本日志)
|
|
277
|
+
- ❶ 硬编码业务数据:{count}
|
|
278
|
+
- ❷ 过度 mock:{count}
|
|
279
|
+
- ❸ 断言实现细节:{count}
|
|
280
|
+
- ❹ Fixture 顺序耦合:{count}
|
|
281
|
+
- ❺ 测私有函数:{count}
|
|
282
|
+
- ❻ 断言框架行为:{count}
|
|
283
|
+
{命中文件列表 或 "未发现可治理的测试反模式。"}
|
|
284
|
+
```
|
|
285
|
+
|
|
227
286
|
## Output
|
|
228
287
|
|
|
229
288
|
### REFACTOR Entry (.roll/backlog.md)
|