@orangepro/orangepro-mcp 0.1.0 → 0.2.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/LICENSE +1 -1
- package/README.md +195 -251
- package/dist/local/analyze/analyzer.js +46 -6
- package/dist/local/analyze/treeSitter/engine.js +172 -17
- package/dist/local/autoProve.js +402 -28
- package/dist/local/cli.js +93 -10
- package/dist/local/cliArgs.js +1 -0
- package/dist/local/generate/runHints.js +9 -4
- package/dist/local/graph/factories.js +5 -2
- package/dist/local/ledger.js +1 -1
- package/dist/local/mcp.js +26 -13
- package/dist/local/operations.js +396 -52
- package/dist/local/pack/coverageReport.js +3 -3
- package/dist/local/proofDoctor.js +312 -0
- package/dist/local/rtm.js +40 -10
- package/dist/local/viz/behaviorReportData.js +79 -7
- package/dist/local/viz/behaviorReportHtml.js +526 -615
- package/dist/local/viz/html.js +1 -1
- package/docs/agent-workflow.md +10 -38
- package/docs/agents/claude-code.md +3 -9
- package/docs/agents/codex.md +3 -20
- package/docs/agents/cursor.md +1 -1
- package/docs/agents/opencode.md +1 -1
- package/docs/agents/vscode.md +1 -1
- package/docs/local-proof-kit.md +52 -19
- package/package.json +39 -6
- package/scripts/spikes/go-dynamic-proof-spike.mjs +637 -0
- package/scripts/spikes/go-mutate.go +182 -0
- package/scripts/spikes/java-dynamic-proof-spike.mjs +571 -0
- package/scripts/spikes/java-mutate.mjs +264 -0
- package/scripts/spikes/python-dynamic-proof-spike.mjs +244 -0
- package/scripts/spikes/python-mutate.py +89 -0
|
@@ -1,651 +1,562 @@
|
|
|
1
1
|
const TEMPLATE = `<!DOCTYPE html>
|
|
2
|
-
<html lang="en"
|
|
2
|
+
<html lang="en">
|
|
3
3
|
<head>
|
|
4
|
-
<meta charset="UTF-8"
|
|
5
|
-
<meta name="viewport" content="width=device-width,
|
|
6
|
-
<title>OrangePro · Behavior Coverage
|
|
4
|
+
<meta charset="UTF-8"/>
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
|
|
6
|
+
<title>OrangePro · Behavior Coverage</title>
|
|
7
7
|
<style>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
.
|
|
223
|
-
.
|
|
224
|
-
.
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
.trigger .verb { color: var(--green); font-weight: 700; }
|
|
229
|
-
.trigger .path { color: var(--blue); }
|
|
230
|
-
|
|
231
|
-
.steps { margin: 14px 0 0; padding: 0; list-style: none; position: relative; }
|
|
232
|
-
.step { position: relative; padding: 0 0 18px 30px; }
|
|
233
|
-
.step:last-child { padding-bottom: 0; }
|
|
234
|
-
/* the connecting rail between dots, colored by the *incoming* hop tier */
|
|
235
|
-
.step::before { content: ""; position: absolute; left: 6px; top: 14px; bottom: -4px; width: 0; border-left: 2px solid var(--blue); }
|
|
236
|
-
.step:last-child::before { display: none; }
|
|
237
|
-
.step[data-edge="framework-derived"]::before { border-left-style: dashed; border-left-color: var(--amber); }
|
|
238
|
-
.step .node { position: absolute; left: 0; top: 3px; width: 13px; height: 13px; border-radius: 50%; background: var(--bg); border: 2px solid var(--blue); }
|
|
239
|
-
.step[data-tier="framework-derived"] .node { border-color: var(--amber); border-style: dashed; }
|
|
240
|
-
.step .sig { font-family: var(--mono); font-size: 13px; color: var(--ink); display: inline-flex; align-items: center; gap: 9px; flex-wrap: wrap; }
|
|
241
|
-
.step .idx { color: var(--faint); font-size: 11px; margin-right: 2px; font-variant-numeric: tabular-nums; }
|
|
242
|
-
.step .desc { font-size: 12.5px; color: var(--muted); margin-top: 3px; }
|
|
243
|
-
.hop-tag { font-size: 10px; font-weight: 600; padding: 1px 7px; border-radius: 20px; letter-spacing: .02em; }
|
|
244
|
-
.hop-tag.hard { color: var(--blue); background: var(--blue-bg); border: 1px solid var(--blue-bd); }
|
|
245
|
-
.hop-tag.fw { color: var(--amber); background: var(--amber-bg); border: 1px solid var(--amber-bd); }
|
|
246
|
-
|
|
247
|
-
.flow-foot { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 12.5px; }
|
|
248
|
-
.flow-foot .why { color: var(--muted); }
|
|
249
|
-
.flow-foot .why b { color: var(--ink-soft); font-weight: 600; }
|
|
250
|
-
|
|
251
|
-
/* ---------- AI-suggested candidate flows (worklist — never evidence) ----------
|
|
252
|
-
Deliberately NEUTRAL: no tier colors (blue/amber) and no proof colors
|
|
253
|
-
(green/red). Dashed borders + gray rails keep candidates visually apart. */
|
|
254
|
-
.ai-flows { margin-top: 28px; padding-top: 20px; border-top: 1px dashed var(--border-2); }
|
|
255
|
-
.badge.b-ai { color: var(--muted); background: var(--surface-3); border-color: var(--border-2); }
|
|
256
|
-
.cflow { background: var(--surface); border: 1px dashed var(--border-2); border-radius: var(--radius); padding: 20px 22px; margin-bottom: 16px; }
|
|
257
|
-
.cflow .flow-head h3 { color: var(--ink-soft); }
|
|
258
|
-
.step.cstep::before { border-left: 2px dotted var(--border-2); }
|
|
259
|
-
.step.cstep .node { border-color: var(--faint); border-style: dotted; }
|
|
260
|
-
.hop-tag.match { color: var(--ink-soft); background: var(--surface-3); border: 1px solid var(--border-2); }
|
|
261
|
-
.hop-tag.unv { color: var(--muted); background: var(--surface-2); border: 1px dashed var(--border-2); }
|
|
262
|
-
|
|
263
|
-
/* ---------- priority risks ---------- */
|
|
264
|
-
.risk { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 18px 20px; margin-bottom: 14px; transition: border-color .16s; }
|
|
265
|
-
.risk:hover { border-color: var(--border-2); }
|
|
266
|
-
.risk .rk { font-size: 12px; color: var(--accent); font-weight: 700; font-variant-numeric: tabular-nums; }
|
|
267
|
-
.risk .ep { font-family: var(--mono); font-size: 14px; margin: 5px 0 0; }
|
|
268
|
-
.risk .ep .verb { color: var(--green); font-weight: 700; }
|
|
269
|
-
.risk .ep .path { color: var(--ink); }
|
|
270
|
-
.risk .rdesc { font-size: 12.5px; color: var(--muted); margin: 9px 0 11px; max-width: 80ch; text-wrap: pretty; }
|
|
271
|
-
.risk .rtags { display: flex; gap: 7px; flex-wrap: wrap; margin-bottom: 12px; }
|
|
272
|
-
.todo { background: var(--green-bg); border: 1px solid var(--green-bd); border-radius: var(--radius-sm); padding: 11px 14px; }
|
|
273
|
-
.todo .th { font-size: 11px; letter-spacing: .05em; text-transform: uppercase; color: var(--green); font-weight: 700; margin-bottom: 3px; }
|
|
274
|
-
.todo p { margin: 0; font-size: 12.5px; color: var(--ink-soft); }
|
|
275
|
-
|
|
276
|
-
.empty { text-align: center; padding: 46px 20px; color: var(--muted); }
|
|
277
|
-
.empty .ic { font-size: 26px; margin-bottom: 8px; opacity: .6; }
|
|
278
|
-
.empty b { color: var(--ink-soft); }
|
|
279
|
-
|
|
280
|
-
@media (max-width: 880px) {
|
|
281
|
-
.kpis { grid-template-columns: repeat(2, 1fr); }
|
|
282
|
-
.cols, .beh-layout { grid-template-columns: 1fr; }
|
|
283
|
-
.beh-side { position: static; }
|
|
284
|
-
.meta { display: none; }
|
|
285
|
-
}
|
|
286
|
-
@media (prefers-reduced-motion: reduce) {
|
|
287
|
-
*, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
|
|
288
|
-
}
|
|
8
|
+
:root {
|
|
9
|
+
--bg:#0d1117; --s1:#161b22; --s2:#1c2128; --s3:#21262d;
|
|
10
|
+
--bd:#2b313a; --bd2:#383f49;
|
|
11
|
+
--ink:#e6edf3; --ink2:#c9d3df; --muted:#9aa4b1; --faint:#6e7681;
|
|
12
|
+
--green:#3fb950; --gbg:rgba(63,185,80,.13); --gbd:rgba(63,185,80,.38);
|
|
13
|
+
--amber:#e3b341; --abg:rgba(227,179,65,.13); --abd:rgba(227,179,65,.38);
|
|
14
|
+
--red:#f85149; --rbg:rgba(248,81,73,.12); --rbd:rgba(248,81,73,.35);
|
|
15
|
+
--blue:#58a6ff; --bbg:rgba(88,166,255,.12); --bbd:rgba(88,166,255,.35);
|
|
16
|
+
--orange:#f0883e;--obg:rgba(240,136,62,.12); --obd:rgba(240,136,62,.40);
|
|
17
|
+
--mono:ui-monospace,"SF Mono","JetBrains Mono",Menlo,Consolas,monospace;
|
|
18
|
+
--sans:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;
|
|
19
|
+
--ease:cubic-bezier(.22,1,.36,1);
|
|
20
|
+
}
|
|
21
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
22
|
+
body{background:var(--bg);color:var(--ink);font-family:var(--sans);font-size:14px;line-height:1.55;-webkit-font-smoothing:antialiased}
|
|
23
|
+
a{color:var(--blue);text-decoration:none}
|
|
24
|
+
::-webkit-scrollbar{width:7px;height:7px}
|
|
25
|
+
::-webkit-scrollbar-thumb{background:#30363d;border-radius:4px;border:2px solid var(--bg)}
|
|
26
|
+
|
|
27
|
+
.wrap{max-width:1100px;margin:0 auto;padding:24px 24px 80px}
|
|
28
|
+
|
|
29
|
+
/* HEADER */
|
|
30
|
+
.hdr{display:flex;align-items:center;justify-content:space-between;gap:16px;padding-bottom:14px;border-bottom:1px solid var(--bd)}
|
|
31
|
+
.hdr-left{display:flex;align-items:center;gap:10px}
|
|
32
|
+
.logo{width:28px;height:28px;border-radius:6px;background:linear-gradient(145deg,#f0883e,#c95c1a);display:grid;place-items:center;font-weight:800;font-size:13px;color:#1a0a02;flex-shrink:0}
|
|
33
|
+
.hdr-name{font-size:15px;font-weight:650;letter-spacing:-.01em}
|
|
34
|
+
.hdr-right{font-size:11.5px;color:var(--muted);display:flex;gap:10px;align-items:center}
|
|
35
|
+
.hdr-tag{background:var(--s2);border:1px solid var(--bd);border-radius:4px;padding:2px 7px;font-family:var(--mono);font-size:10.5px;color:var(--ink2)}
|
|
36
|
+
|
|
37
|
+
/* KPI STRIP */
|
|
38
|
+
.kpis{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin:16px 0 0}
|
|
39
|
+
.kpi{background:var(--s1);border:1px solid var(--bd);border-radius:9px;padding:13px 14px;position:relative;overflow:hidden}
|
|
40
|
+
.kpi::before{content:"";position:absolute;inset:0;pointer-events:none;opacity:.4;background:radial-gradient(90px 70px at 100% 0%,var(--kw,transparent),transparent 70%)}
|
|
41
|
+
.kpi-lbl{font-size:10px;color:var(--muted);font-weight:600;letter-spacing:.05em;text-transform:uppercase;position:relative}
|
|
42
|
+
.kpi-num{font-size:24px;font-weight:750;letter-spacing:-.02em;margin:5px 0 1px;line-height:1;font-variant-numeric:tabular-nums;position:relative}
|
|
43
|
+
.kpi-sub{font-size:10.5px;color:var(--faint);position:relative}
|
|
44
|
+
.kpi[data-t="total"]{--kw:rgba(88,166,255,.08)} .kpi[data-t="total"] .kpi-num{color:var(--ink)}
|
|
45
|
+
.kpi[data-t="proven"]{--kw:var(--gbg)} .kpi[data-t="proven"] .kpi-num{color:var(--green)}
|
|
46
|
+
.kpi[data-t="signal"]{--kw:var(--abg)} .kpi[data-t="signal"] .kpi-num{color:var(--amber)}
|
|
47
|
+
.kpi[data-t="reach"]{--kw:var(--bbg)} .kpi[data-t="reach"] .kpi-num{color:var(--blue)}
|
|
48
|
+
.kpi[data-t="nosig"]{--kw:var(--rbg)} .kpi[data-t="nosig"] .kpi-num{color:var(--red)}
|
|
49
|
+
|
|
50
|
+
/* TABS */
|
|
51
|
+
nav.tabs{display:flex;gap:2px;margin:18px 0 0;border-bottom:1px solid var(--bd)}
|
|
52
|
+
.tab{appearance:none;background:none;border:0;color:var(--muted);font:inherit;font-size:13px;font-weight:550;padding:9px 13px 10px;cursor:pointer;position:relative;display:inline-flex;align-items:center;gap:5px;border-radius:5px 5px 0 0;transition:color .12s}
|
|
53
|
+
.tab:hover{color:var(--ink2)}
|
|
54
|
+
.tab[aria-selected="true"]{color:var(--ink)}
|
|
55
|
+
.tab[aria-selected="true"]::after{content:"";position:absolute;left:5px;right:5px;bottom:-1px;height:2px;background:var(--orange);border-radius:2px 2px 0 0}
|
|
56
|
+
.tab .tc{font-size:10px;font-weight:600;color:var(--muted);background:var(--s3);padding:1px 5px;border-radius:20px;font-variant-numeric:tabular-nums}
|
|
57
|
+
|
|
58
|
+
/* PANELS */
|
|
59
|
+
.panel{display:none;padding-top:16px}
|
|
60
|
+
.panel.active{display:block;animation:fi .18s var(--ease)}
|
|
61
|
+
@keyframes fi{from{opacity:0;transform:translateY(2px)}to{opacity:1;transform:none}}
|
|
62
|
+
|
|
63
|
+
/* SECTION BRIDGE — one-liner that connects developer vocab to OrangePro concepts */
|
|
64
|
+
.bridge{font-size:12.5px;color:var(--muted);margin-bottom:14px;padding:0;line-height:1.6}
|
|
65
|
+
.bridge b{color:var(--ink2);font-weight:600}
|
|
66
|
+
.bridge code{font-family:var(--mono);font-size:11px;background:var(--s2);border:1px solid var(--bd);border-radius:3px;padding:1px 4px;color:var(--ink2)}
|
|
67
|
+
|
|
68
|
+
/* CODEBASE */
|
|
69
|
+
.cols2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
|
|
70
|
+
.card{background:var(--s1);border:1px solid var(--bd);border-radius:9px;padding:16px}
|
|
71
|
+
.card-lbl{font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--orange);font-weight:650;margin:0 0 8px}
|
|
72
|
+
.fw-pill{display:inline-block;background:var(--obg);border:1px solid var(--obd);color:#f6b079;border-radius:5px;padding:3px 9px;font-size:12px;font-family:var(--mono)}
|
|
73
|
+
.bignum{font-size:22px;font-weight:750;font-variant-numeric:tabular-nums}
|
|
74
|
+
.split2{display:flex;gap:16px;margin-top:4px}
|
|
75
|
+
.split2 .v{font-size:17px;font-weight:700;font-variant-numeric:tabular-nums}
|
|
76
|
+
.split2 .vb{color:var(--blue)} .split2 .vp{color:#bc8cff}
|
|
77
|
+
.split2 .k{font-size:10.5px;color:var(--muted)}
|
|
78
|
+
.svc-list{margin-top:6px;max-height:200px;overflow-y:auto}
|
|
79
|
+
.svc{display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd);font-size:12px}
|
|
80
|
+
.svc:last-child{border-bottom:0}
|
|
81
|
+
.svc .nm{font-family:var(--mono);color:var(--ink2)}
|
|
82
|
+
.svc .ct{color:var(--muted);font-variant-numeric:tabular-nums}
|
|
83
|
+
|
|
84
|
+
/* BEHAVIORS — card grid with tier glow */
|
|
85
|
+
.beh-layout{display:grid;grid-template-columns:160px 1fr;gap:12px}
|
|
86
|
+
.beh-side{background:var(--s1);border:1px solid var(--bd);border-radius:9px;padding:5px;height:max-content;position:sticky;top:12px}
|
|
87
|
+
.beh-side-lbl{font-size:9.5px;color:var(--faint);font-weight:600;letter-spacing:.05em;text-transform:uppercase;padding:5px 8px 3px}
|
|
88
|
+
.beh-search{width:100%;margin:2px 0 8px;padding:7px 9px;font:inherit;font-size:12px;color:var(--ink);background:var(--s);border:1px solid var(--bd);border-radius:6px;outline:none}
|
|
89
|
+
.beh-search:focus{border-color:var(--amber)}
|
|
90
|
+
.grp{width:100%;text-align:left;appearance:none;border:0;background:none;font:inherit;font-size:12px;color:var(--muted);padding:6px 8px;border-radius:5px;cursor:pointer;display:flex;justify-content:space-between;align-items:center;transition:background .1s,color .1s}
|
|
91
|
+
.grp:hover{background:var(--s2);color:var(--ink2)}
|
|
92
|
+
.grp[aria-pressed="true"]{background:var(--obg);color:var(--orange);font-weight:600}
|
|
93
|
+
.grp .gc{font-size:10px;opacity:.8}
|
|
94
|
+
.beh-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:9px}
|
|
95
|
+
.beh-card{background:var(--s1);border:1px solid var(--bd);border-radius:9px;padding:12px 14px;cursor:pointer;transition:border-color .12s,transform .12s;position:relative;overflow:hidden}
|
|
96
|
+
.beh-card::before{content:"";position:absolute;inset:0;pointer-events:none;opacity:0;transition:opacity .15s;background:radial-gradient(120px 90px at 0% 100%,var(--gw,transparent),transparent 70%)}
|
|
97
|
+
.beh-card:hover{transform:translateY(-1px);border-color:var(--bd2)}
|
|
98
|
+
.beh-card:hover::before{opacity:1}
|
|
99
|
+
.beh-card[data-t="proven"]{--gw:var(--gbg);border-color:rgba(63,185,80,.22)}
|
|
100
|
+
.beh-card[data-t="proven"]:hover{border-color:var(--gbd)}
|
|
101
|
+
.beh-card[data-t="assoc"]{--gw:var(--abg)}
|
|
102
|
+
.beh-card[data-t="assoc"]:hover{border-color:var(--abd)}
|
|
103
|
+
.beh-card[data-t="reach"]{--gw:var(--bbg)}
|
|
104
|
+
.beh-card[data-t="reach"]:hover{border-color:var(--bbd)}
|
|
105
|
+
.beh-card[data-t="nosig"]{--gw:var(--rbg)}
|
|
106
|
+
.beh-card[data-t="nosig"]:hover{border-color:var(--rbd)}
|
|
107
|
+
.bc-top{display:flex;align-items:flex-start;justify-content:space-between;gap:6px}
|
|
108
|
+
.bc-sig{font-family:var(--mono);font-size:12px;font-weight:600;color:var(--ink);word-break:break-all;flex:1}
|
|
109
|
+
.bc-badge{flex-shrink:0}
|
|
110
|
+
.bc-file{font-family:var(--mono);font-size:10px;color:var(--faint);margin-top:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
111
|
+
.bc-bar{height:3px;border-radius:0 0 9px 9px;position:absolute;bottom:0;left:0;right:0}
|
|
112
|
+
.beh-card[data-t="proven"] .bc-bar{background:var(--green)}
|
|
113
|
+
.beh-card[data-t="assoc"] .bc-bar{background:var(--amber)}
|
|
114
|
+
.beh-card[data-t="reach"] .bc-bar{background:var(--blue)}
|
|
115
|
+
.beh-card[data-t="nosig"] .bc-bar{background:var(--red)}
|
|
116
|
+
|
|
117
|
+
/* BADGES */
|
|
118
|
+
.badge{display:inline-flex;align-items:center;gap:3px;font-size:10px;font-weight:600;padding:2px 6px;border-radius:20px;border:1px solid transparent}
|
|
119
|
+
.badge .d{width:4px;height:4px;border-radius:50%}
|
|
120
|
+
.b-proven{color:var(--green);background:var(--gbg);border-color:var(--gbd)} .b-proven .d{background:var(--green)}
|
|
121
|
+
.b-signal{color:var(--amber);background:var(--abg);border-color:var(--abd)} .b-signal .d{background:var(--amber)}
|
|
122
|
+
.b-reach{color:var(--blue);background:var(--bbg);border-color:var(--bbd)} .b-reach .d{background:var(--blue)}
|
|
123
|
+
.b-nosig{color:var(--red);background:var(--rbg);border-color:var(--rbd)} .b-nosig .d{background:var(--red)}
|
|
124
|
+
.b-risk{color:var(--red);background:var(--rbg);border-color:var(--rbd)} .b-risk .d{background:var(--red)}
|
|
125
|
+
.b-info{color:var(--muted);background:var(--s3);border-color:var(--bd2)}
|
|
126
|
+
|
|
127
|
+
/* FLOWS */
|
|
128
|
+
.flow-card{background:var(--s1);border:1px solid var(--bd);border-radius:9px;margin-bottom:12px;overflow:hidden;transition:border-color .12s}
|
|
129
|
+
.flow-card:hover{border-color:var(--bd2)}
|
|
130
|
+
.flow-head{padding:12px 16px;display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;border-bottom:1px solid var(--bd)}
|
|
131
|
+
.flow-head h3{font-size:13.5px;font-weight:650;letter-spacing:-.01em;margin:0}
|
|
132
|
+
.flow-tags{display:flex;gap:5px;flex-wrap:wrap}
|
|
133
|
+
/* horizontal chain */
|
|
134
|
+
.flow-chain{padding:14px 16px;display:flex;align-items:center;gap:0;overflow-x:auto;min-height:60px}
|
|
135
|
+
.flow-chain::-webkit-scrollbar{height:3px}
|
|
136
|
+
.fn{display:flex;flex-direction:column;align-items:center;flex-shrink:0;min-width:80px;max-width:150px}
|
|
137
|
+
.fn-box{background:var(--s2);border:1px solid var(--bd2);border-radius:7px;padding:6px 10px;font-family:var(--mono);font-size:11px;color:var(--ink2);text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:145px;width:100%;transition:border-color .15s}
|
|
138
|
+
.fn-box.fnb-entry{background:var(--bbg);border-color:var(--bbd);color:var(--blue)}
|
|
139
|
+
.fn-box.fnb-proven{background:var(--gbg);border-color:var(--gbd);color:var(--green);animation:pulse 2.4s ease infinite}
|
|
140
|
+
.fn-box.fnb-assoc{background:var(--abg);border-color:var(--abd);color:var(--amber)}
|
|
141
|
+
@keyframes pulse{0%,100%{box-shadow:0 0 0 0 var(--gbg)}50%{box-shadow:0 0 0 4px transparent}}
|
|
142
|
+
.fe{flex:1;min-width:20px;max-width:40px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
|
143
|
+
.fe-line{width:100%;height:2px}
|
|
144
|
+
.fe-line.fel-hard{background:linear-gradient(90deg,var(--blue),rgba(88,166,255,.4))}
|
|
145
|
+
.fe-line.fel-fw{background:repeating-linear-gradient(90deg,var(--amber) 0,var(--amber) 3px,transparent 3px,transparent 6px)}
|
|
146
|
+
/* vertical chain for 5+ nodes */
|
|
147
|
+
.flow-chain.fc-vert{flex-direction:column;align-items:flex-start;gap:0;overflow-x:visible;padding:16px 20px}
|
|
148
|
+
.fc-vert .fn{flex-direction:row;align-items:center;min-width:0;max-width:none;gap:10px}
|
|
149
|
+
.fc-vert .fn-box{text-align:left;white-space:normal;max-width:300px;width:auto}
|
|
150
|
+
.fc-vert .fe{min-width:0;max-width:none;width:auto;margin-left:18px;padding:3px 0}
|
|
151
|
+
.fc-vert .fe-line{width:2px;height:20px;min-width:2px}
|
|
152
|
+
.fc-vert .fe-line.fel-hard{background:linear-gradient(180deg,var(--blue),rgba(88,166,255,.4))}
|
|
153
|
+
.fc-vert .fe-line.fel-fw{background:repeating-linear-gradient(180deg,var(--amber) 0,var(--amber) 3px,transparent 3px,transparent 6px)}
|
|
154
|
+
/* AI flows */
|
|
155
|
+
.ai-sec{margin-top:20px;padding-top:14px;border-top:1px dashed var(--bd2)}
|
|
156
|
+
.ai-lbl{font-size:11px;color:var(--faint);font-weight:600;margin-bottom:10px}
|
|
157
|
+
.ai-card{background:var(--s1);border:1px dashed var(--bd2);border-radius:9px;margin-bottom:10px;overflow:hidden}
|
|
158
|
+
.ai-card .fn-box{border-style:dashed}
|
|
159
|
+
.ai-card .fe-line{opacity:.45}
|
|
160
|
+
|
|
161
|
+
/* RISKS */
|
|
162
|
+
.risk-card{background:var(--s1);border:1px solid var(--bd);border-radius:9px;padding:14px 16px;margin-bottom:10px}
|
|
163
|
+
.risk-tools{display:flex;align-items:center;gap:8px;margin:0 0 12px;flex-wrap:wrap}
|
|
164
|
+
.risk-filter{appearance:none;border:1px solid var(--bd);background:var(--s1);color:var(--muted);border-radius:20px;padding:5px 10px;font:inherit;font-size:12px;font-weight:600;cursor:pointer}
|
|
165
|
+
.risk-filter:hover{border-color:var(--bd2);color:var(--ink2)}
|
|
166
|
+
.risk-filter[aria-pressed="true"]{border-color:var(--orange);background:var(--obg);color:var(--orange)}
|
|
167
|
+
.risk-rank{font-size:10px;color:var(--orange);font-weight:700;margin-bottom:3px}
|
|
168
|
+
.risk-ep{font-family:var(--mono);font-size:13px;margin:0 0 6px}
|
|
169
|
+
.risk-ep .v{color:var(--green);font-weight:700}
|
|
170
|
+
.risk-desc{font-size:12px;color:var(--muted);margin:0 0 8px;max-width:72ch}
|
|
171
|
+
.risk-tags{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:8px}
|
|
172
|
+
.todo{background:var(--gbg);border:1px solid var(--gbd);border-radius:6px;padding:8px 11px;font-size:11.5px;color:var(--ink2)}
|
|
173
|
+
|
|
174
|
+
/* DRILL MODAL */
|
|
175
|
+
.drill-overlay{display:none;position:fixed;inset:0;z-index:600;padding:24px;background:rgba(1,4,9,.7);justify-content:center;align-items:center}
|
|
176
|
+
.drill-overlay.open{display:flex;animation:fi .14s var(--ease)}
|
|
177
|
+
.drill-card{position:relative;background:var(--s1);border:1px solid var(--bd2);border-radius:11px;padding:18px 20px;max-width:480px;width:100%;max-height:75vh;overflow-y:auto;box-shadow:0 16px 50px rgba(0,0,0,.5)}
|
|
178
|
+
.drill-close{position:absolute;top:8px;right:10px;appearance:none;background:none;border:0;color:var(--muted);font-size:18px;cursor:pointer;padding:4px 7px;border-radius:4px}
|
|
179
|
+
.drill-close:hover{color:var(--ink);background:var(--s2)}
|
|
180
|
+
.drill-title{font-family:var(--mono);font-size:13px;font-weight:650;margin:0 28px 12px 0;word-break:break-all}
|
|
181
|
+
.drill-row{margin-bottom:10px}
|
|
182
|
+
.drill-row:last-child{margin-bottom:0}
|
|
183
|
+
.drill-row h4{font-size:9.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--orange);font-weight:650;margin:0 0 4px}
|
|
184
|
+
.drill-row p{margin:0;font-size:12px;color:var(--muted)}
|
|
185
|
+
.drill-row .mono{font-family:var(--mono);font-size:11px;color:var(--blue)}
|
|
186
|
+
.abox{font-size:12px;color:var(--ink2);border-radius:6px;padding:8px 10px}
|
|
187
|
+
.abox code{font-family:var(--mono);font-size:10.5px;background:rgba(0,0,0,.3);border-radius:3px;padding:1px 4px}
|
|
188
|
+
.abox.ax-proven{background:var(--gbg);border:1px solid var(--gbd)}
|
|
189
|
+
.abox.ax-signal{background:var(--abg);border:1px solid var(--abd)}
|
|
190
|
+
.abox.ax-reach{background:var(--bbg);border:1px solid var(--bbd)}
|
|
191
|
+
.abox.ax-nosig{background:var(--rbg);border:1px solid var(--rbd)}
|
|
192
|
+
|
|
193
|
+
/* GENERATED TEST SAMPLES */
|
|
194
|
+
.gen-tests{margin-top:10px;border-top:1px solid var(--bd);padding-top:10px}
|
|
195
|
+
.gen-tests-lbl{font-size:10px;color:var(--orange);font-weight:650;letter-spacing:.04em;text-transform:uppercase;margin-bottom:6px;display:flex;align-items:center;gap:5px}
|
|
196
|
+
.gen-tests-lbl::before{content:"";width:12px;height:12px;background:var(--obg);border:1px solid var(--obd);border-radius:3px;display:inline-block}
|
|
197
|
+
.gen-test{background:var(--s2);border:1px solid var(--bd);border-radius:7px;margin-bottom:6px;overflow:hidden}
|
|
198
|
+
.gen-test-head{display:flex;align-items:center;justify-content:space-between;padding:8px 11px;cursor:pointer;gap:8px}
|
|
199
|
+
.gen-test-head:hover{background:var(--s3)}
|
|
200
|
+
.gen-test-name{font-family:var(--mono);font-size:11.5px;color:var(--ink2);font-weight:600}
|
|
201
|
+
.gen-test-assert{font-size:10.5px;color:var(--muted);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
202
|
+
.gen-test-toggle{color:var(--faint);font-size:10px;flex-shrink:0;transition:transform .15s}
|
|
203
|
+
.gen-test.open .gen-test-toggle{transform:rotate(180deg)}
|
|
204
|
+
.gen-test-body{display:none;padding:0 11px 10px;font-family:var(--mono);font-size:11px;line-height:1.6;color:var(--ink2);white-space:pre-wrap;border-top:1px solid var(--bd)}
|
|
205
|
+
.gen-test.open .gen-test-body{display:block}
|
|
206
|
+
/* TESTING CATEGORIES STRIP */
|
|
207
|
+
.cat-strip{margin-top:10px;padding-top:9px;border-top:1px solid var(--bd)}
|
|
208
|
+
.cat-strip-lbl{font-size:9.5px;color:var(--muted);font-weight:600;letter-spacing:.04em;text-transform:uppercase;margin-bottom:6px}
|
|
209
|
+
.cat-pills{display:flex;flex-wrap:wrap;gap:5px}
|
|
210
|
+
.cat-pill{font-size:10px;padding:3px 8px;border-radius:4px;font-weight:600;letter-spacing:.01em;display:inline-flex;align-items:center;gap:4px}
|
|
211
|
+
.cat-pill.cp-shown{background:var(--gbg);border:1px solid var(--gbd);color:var(--green)}
|
|
212
|
+
.cat-pill.cp-locked{background:transparent;border:1px dashed var(--bd2);color:var(--faint)}
|
|
213
|
+
.cat-pill.cp-locked::after{content:"\\1F512";font-size:8px}
|
|
214
|
+
/* PAYWALL CTA */
|
|
215
|
+
.paywall{background:linear-gradient(135deg,rgba(240,136,62,.08),rgba(240,136,62,.02));border:1px dashed var(--obd);border-radius:9px;padding:14px 16px;margin-top:14px;text-align:center}
|
|
216
|
+
.paywall-num{font-size:18px;font-weight:750;color:var(--orange);font-variant-numeric:tabular-nums}
|
|
217
|
+
.paywall-txt{font-size:12px;color:var(--muted);margin:4px 0 10px}
|
|
218
|
+
.paywall-btn{display:inline-flex;align-items:center;gap:6px;background:var(--orange);color:#1a0a02;font-size:12px;font-weight:700;padding:7px 14px;border-radius:6px;text-decoration:none;transition:opacity .12s}
|
|
219
|
+
.paywall-btn:hover{opacity:.85}
|
|
220
|
+
|
|
221
|
+
@media(max-width:800px){
|
|
222
|
+
.kpis{grid-template-columns:repeat(3,1fr)}
|
|
223
|
+
.cols2,.beh-layout{grid-template-columns:1fr}
|
|
224
|
+
.beh-side{position:static}
|
|
225
|
+
}
|
|
226
|
+
@media(max-width:500px){.kpis{grid-template-columns:repeat(2,1fr)}}
|
|
227
|
+
@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:.001ms!important;transition-duration:.001ms!important}}
|
|
289
228
|
</style>
|
|
290
229
|
</head>
|
|
291
230
|
<body>
|
|
292
231
|
<div class="wrap">
|
|
293
232
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
<h1 id="repo-name">—</h1>
|
|
299
|
-
<p>Behavior Coverage Report</p>
|
|
300
|
-
</div>
|
|
301
|
-
</div>
|
|
302
|
-
<div class="meta">
|
|
303
|
-
<div>Scanned: <span id="scan-date">—</span></div>
|
|
304
|
-
<div class="repo-state" id="analysis-kind">Static analysis</div>
|
|
305
|
-
</div>
|
|
306
|
-
</header>
|
|
307
|
-
|
|
308
|
-
<section class="kpis" id="kpis" aria-label="Coverage summary"></section>
|
|
309
|
-
|
|
310
|
-
<div class="legend tier-legend" aria-label="Tier definitions">
|
|
311
|
-
<span class="li" title="Mutation killed — a test broke when OrangePro changed this behavior. Highest confidence."><span class="badge b-proven"><span class="d"></span>Dynamically Proven</span> Mutation killed — a test broke when OrangePro changed this behavior. Highest confidence.</span>
|
|
312
|
-
<span class="li" title="A test file appears to import or call this behavior. Not dynamically verified."><span class="badge b-assoc"><span class="d"></span>Statically Linked</span> A test file appears to import or call this behavior. Not dynamically verified.</span>
|
|
313
|
-
<span class="li" title="Appears in a static flow from an entry point, but no test signal was found."><span class="badge b-none"><span class="d"></span>Reachable Untested</span> Appears in a static flow from an entry point, but no test signal was found.</span>
|
|
314
|
-
<span class="li" title="No test or static-flow signal found yet."><span class="badge b-none"><span class="d"></span>No Signal</span> No test or static-flow signal found yet.</span>
|
|
233
|
+
<header class="hdr">
|
|
234
|
+
<div class="hdr-left">
|
|
235
|
+
<div class="logo">O</div>
|
|
236
|
+
<span class="hdr-name" id="repo-name">—</span>
|
|
315
237
|
</div>
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
</div>
|
|
341
|
-
<div class="card">
|
|
342
|
-
<p class="ttl">Test files</p>
|
|
343
|
-
<div class="bignum" id="test-total">—</div>
|
|
344
|
-
<div class="split2">
|
|
345
|
-
<div><div class="v b" id="test-int">—</div><div class="k">Integration</div></div>
|
|
346
|
-
<div><div class="v p" id="test-unit">—</div><div class="k">Unit / snapshot</div></div>
|
|
347
|
-
</div>
|
|
348
|
-
<p class="ttl" style="margin-top:24px">What we excluded</p>
|
|
349
|
-
<div class="excl" id="excluded"></div>
|
|
350
|
-
</div>
|
|
351
|
-
</div>
|
|
352
|
-
</section>
|
|
353
|
-
|
|
354
|
-
<!-- BEHAVIORS -->
|
|
355
|
-
<section class="panel" id="panel-behaviors" role="tabpanel">
|
|
356
|
-
<div class="note">
|
|
357
|
-
<h3><span class="ic" aria-hidden="true">◆</span> How we define a behavior</h3>
|
|
358
|
-
<p>A <b>behavior</b> is a discrete unit of functionality a user or system can trigger and observe an outcome from: the smallest thing you'd write an acceptance criterion for. A service method that creates, updates, deletes, or retrieves data with observable side effects.</p>
|
|
359
|
-
</div>
|
|
360
|
-
<div class="beh-layout">
|
|
361
|
-
<aside class="beh-side" id="beh-groups" aria-label="Filter by package"></aside>
|
|
362
|
-
<div class="beh-main" id="beh-list"></div>
|
|
363
|
-
</div>
|
|
364
|
-
</section>
|
|
365
|
-
|
|
366
|
-
<!-- FLOWS -->
|
|
367
|
-
<section class="panel" id="panel-flows" role="tabpanel">
|
|
368
|
-
<div class="note">
|
|
369
|
-
<h3><span class="ic" aria-hidden="true">◆</span> How behaviors compose into flows</h3>
|
|
370
|
-
<p>A <b>flow</b> is the implementation chain we can statically compose from a single entry point, crossing service boundaries. Each hop is a real call in your source, labelled by how exactly we resolved it. A flow is only as trustworthy as its weakest hop, and <b>reachable ≠ proven</b>: reachability is static, while proof requires a passing test that exercises the path without mocking its target.</p>
|
|
371
|
-
</div>
|
|
372
|
-
<div class="legend">
|
|
373
|
-
<span class="li"><span class="rail-demo"></span> <b>Hard reachable</b>: exact call, both ends authored</span>
|
|
374
|
-
<span class="li"><span class="rail-demo fw"></span> <b>Framework-derived</b>: generated/SDK target, deterministic but not authored</span>
|
|
375
|
-
<span class="li"><span class="badge b-proven"><span class="d"></span>Dynamic proof</span> real test, target not mocked</span>
|
|
238
|
+
<div class="hdr-right">
|
|
239
|
+
<span class="hdr-tag" id="framework">—</span>
|
|
240
|
+
<span id="scan-date">—</span>
|
|
241
|
+
</div>
|
|
242
|
+
</header>
|
|
243
|
+
|
|
244
|
+
<section class="kpis" id="kpis"></section>
|
|
245
|
+
|
|
246
|
+
<nav class="tabs" role="tablist">
|
|
247
|
+
<button class="tab" role="tab" aria-selected="true" data-tab="codebase">Your Code</button>
|
|
248
|
+
<button class="tab" role="tab" aria-selected="false" data-tab="behaviors">Behaviors <span class="tc" id="t-beh">—</span></button>
|
|
249
|
+
<button class="tab" role="tab" aria-selected="false" data-tab="flows">Flows <span class="tc" id="t-flow">—</span></button>
|
|
250
|
+
<button class="tab" role="tab" aria-selected="false" data-tab="risks">Risks <span class="tc" id="t-risk">—</span></button>
|
|
251
|
+
</nav>
|
|
252
|
+
|
|
253
|
+
<!-- TAB 1: YOUR CODE — familiar territory -->
|
|
254
|
+
<section class="panel active" id="panel-codebase" role="tabpanel">
|
|
255
|
+
<p class="bridge">We scanned your repo and found <b id="br-methods">—</b> public methods across <b id="br-services">—</b> services, with <b id="br-tests">—</b> test files. Here's what we're working with.</p>
|
|
256
|
+
<div class="cols2">
|
|
257
|
+
<div class="card">
|
|
258
|
+
<p class="card-lbl">Framework</p>
|
|
259
|
+
<span class="fw-pill" id="fw-pill">—</span>
|
|
260
|
+
<p class="card-lbl" style="margin-top:14px">Services (by exported method count)</p>
|
|
261
|
+
<div class="svc-list" id="svc-list"></div>
|
|
376
262
|
</div>
|
|
377
|
-
<div
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
<
|
|
383
|
-
<p><span id="ai-flow-headline"></span> Proposed by a model over existing graph ids only. <b>Not evidence — candidate chains to verify.</b> They are never counted in flow totals, tiers, or coverage; a hop marker only says whether the proposed hop matches an already-known call edge.</p>
|
|
263
|
+
<div class="card">
|
|
264
|
+
<p class="card-lbl">Test files found</p>
|
|
265
|
+
<div class="bignum" id="test-total">—</div>
|
|
266
|
+
<div class="split2">
|
|
267
|
+
<div><div class="v vb" id="test-int">—</div><div class="k">Integration</div></div>
|
|
268
|
+
<div><div class="v vp" id="test-unit">—</div><div class="k">Unit</div></div>
|
|
384
269
|
</div>
|
|
385
|
-
<div id="ai-flow-list"></div>
|
|
386
270
|
</div>
|
|
387
|
-
</
|
|
271
|
+
</div>
|
|
272
|
+
</section>
|
|
273
|
+
|
|
274
|
+
<!-- TAB 2: BEHAVIORS — bridge from "methods" to "behaviors" -->
|
|
275
|
+
<section class="panel" id="panel-behaviors" role="tabpanel">
|
|
276
|
+
<p class="bridge">Each card below is a <b>public method</b> in your code that has an observable outcome — we call it a <b>behavior</b>. The color tells you how well it's tested: <span style="color:var(--green)">green</span> = a test proves it breaks if you change it, <span style="color:var(--amber)">amber</span> = a test touches it but doesn't prove breakage, <span style="color:var(--red)">red</span> = nothing tests it.</p>
|
|
277
|
+
<div class="beh-layout">
|
|
278
|
+
<aside class="beh-side" id="beh-groups">
|
|
279
|
+
<div class="beh-side-lbl">Search</div>
|
|
280
|
+
<input id="beh-search" class="beh-search" type="search" placeholder="name or file…" aria-label="Search behaviors by name or file">
|
|
281
|
+
<div class="beh-side-lbl">Evidence</div>
|
|
282
|
+
<div id="tier-filter"></div>
|
|
283
|
+
<div class="beh-side-lbl">Package</div>
|
|
284
|
+
</aside>
|
|
285
|
+
<div class="beh-grid" id="beh-grid"></div>
|
|
286
|
+
</div>
|
|
287
|
+
</section>
|
|
288
|
+
|
|
289
|
+
<!-- TAB 3: FLOWS — bridge from "methods" to "user journeys" -->
|
|
290
|
+
<section class="panel" id="panel-flows" role="tabpanel">
|
|
291
|
+
<p class="bridge">A <b>flow</b> is the sequence of methods that execute when a real request hits your system. Each box is a method you already saw in the Behaviors tab — but here they're connected as a call chain. Solid lines = hard-coded calls. Dashed = framework-derived. <span style="color:var(--green)">Green boxes</span> pulse because a test proves they break if mutated.</p>
|
|
292
|
+
<div id="flow-list"></div>
|
|
293
|
+
<div class="ai-sec" id="ai-sec" hidden>
|
|
294
|
+
<div class="ai-lbl">AI-suggested flows — plausible paths, not proven. Dashed borders = unverified.</div>
|
|
295
|
+
<div id="ai-list"></div>
|
|
296
|
+
</div>
|
|
297
|
+
</section>
|
|
388
298
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
<div id="risk-list"></div>
|
|
396
|
-
</section>
|
|
299
|
+
<!-- TAB 4: RISKS — actionable -->
|
|
300
|
+
<section class="panel" id="panel-risks" role="tabpanel">
|
|
301
|
+
<p class="bridge">These are the flows with the highest blast radius and the weakest test coverage. Each one tells you exactly what to do next.</p>
|
|
302
|
+
<div class="risk-tools" id="risk-tools"></div>
|
|
303
|
+
<div id="risk-list"></div>
|
|
304
|
+
</section>
|
|
397
305
|
|
|
398
306
|
</div>
|
|
399
307
|
|
|
400
|
-
<!-- BEHAVIOR DETAIL MODAL — display-only drill into one behavior row -->
|
|
401
308
|
<div class="drill-overlay" id="drill">
|
|
402
|
-
<div class="drill-card" role="dialog" aria-modal="true"
|
|
403
|
-
<button class="drill-close" id="drill-close" type="button"
|
|
309
|
+
<div class="drill-card" role="dialog" aria-modal="true">
|
|
310
|
+
<button class="drill-close" id="drill-close" type="button">×</button>
|
|
404
311
|
<div id="drill-content"></div>
|
|
405
312
|
</div>
|
|
406
313
|
</div>
|
|
407
314
|
|
|
408
315
|
<script>
|
|
409
|
-
/* =====================================================================
|
|
410
|
-
window.DATA — the backend graph contract. Each block is populated by
|
|
411
|
-
a specific slice/PR; \`pipeline[].on\` drives the status strip so the
|
|
412
|
-
report visibly fills in as backend work lands.
|
|
413
|
-
Numbers below are honest Medusa-representative values from review runs
|
|
414
|
-
(1,173 behavior denominator · 1,744 hard CALLS · 421 injected · 44
|
|
415
|
-
framework-derived) plus one typed-NestJS flow showing the hard+proven
|
|
416
|
-
target state.
|
|
417
|
-
===================================================================== */
|
|
418
316
|
window.DATA = __ORANGEPRO_DATA__;
|
|
419
317
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
318
|
+
(function(){
|
|
319
|
+
const D=window.DATA,$=(s)=>document.querySelector(s);
|
|
320
|
+
const el=(t,c,h)=>{const e=document.createElement(t);if(c)e.className=c;if(h!=null)e.innerHTML=h;return e};
|
|
321
|
+
const esc=s=>String(s).replace(/[&<>]/g,m=>({"&":"&","<":"<",">":">"}[m]));
|
|
322
|
+
const S=D.summary;
|
|
323
|
+
|
|
324
|
+
// header
|
|
325
|
+
$("#repo-name").textContent=D.repo;
|
|
326
|
+
$("#scan-date").textContent=new Date(D.scanned+"T00:00:00").toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"});
|
|
327
|
+
$("#framework").textContent=D.framework;
|
|
328
|
+
$("#fw-pill").textContent=D.framework;
|
|
329
|
+
|
|
330
|
+
// bridge text in codebase tab
|
|
331
|
+
$("#br-methods").textContent=S.total;
|
|
332
|
+
$("#br-services").textContent=D.scan.serviceTotal;
|
|
333
|
+
$("#br-tests").textContent=D.scan.tests.total;
|
|
334
|
+
|
|
335
|
+
// tab counts
|
|
336
|
+
$("#t-beh").textContent=D.behaviors.length;
|
|
337
|
+
$("#t-flow").textContent=D.flows.length;
|
|
338
|
+
$("#t-risk").textContent=D.risks.length;
|
|
339
|
+
|
|
340
|
+
// KPIs
|
|
341
|
+
[
|
|
342
|
+
{lbl:"Methods found",num:S.total,sub:"public, with observable outcome",t:"total"},
|
|
343
|
+
{lbl:"Dynamically Proven",num:S.proven,sub:"test breaks if you change it",t:"proven"},
|
|
344
|
+
{lbl:"Test signal",num:S.associated,sub:"test touches it, no proof",t:"signal"},
|
|
345
|
+
{lbl:"Reachable",num:S.reachableUntested,sub:"called but untested",t:"reach"},
|
|
346
|
+
{lbl:"No signal",num:S.noSignal,sub:"nothing touches it",t:"nosig"},
|
|
347
|
+
].forEach(k=>{
|
|
348
|
+
const d=el("div","kpi",\`<div class="kpi-lbl">\${k.lbl}</div><div class="kpi-num">\${k.num}</div><div class="kpi-sub">\${k.sub}</div>\`);
|
|
349
|
+
d.dataset.t=k.t;
|
|
350
|
+
$("#kpis").append(d);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// codebase
|
|
354
|
+
D.scan.services.forEach(([nm,ct])=>$("#svc-list").append(el("div","svc",\`<span class="nm">\${esc(nm)}</span><span class="ct">\${ct}</span>\`)));
|
|
355
|
+
$("#test-total").textContent=D.scan.tests.total;
|
|
356
|
+
$("#test-int").textContent=D.scan.tests.integration;
|
|
357
|
+
$("#test-unit").textContent=D.scan.tests.unit;
|
|
358
|
+
|
|
359
|
+
// behaviors
|
|
360
|
+
function tierOf(b){
|
|
361
|
+
if(b.tier==="proven")return{cls:"proven",badge:"b-proven",label:"Dynamically Proven",ax:"ax-proven"};
|
|
362
|
+
if(b.tier==="assoc")return{cls:"assoc",badge:"b-signal",label:"Test signal",ax:"ax-signal"};
|
|
363
|
+
if(b.tier==="none"&&b.reachable)return{cls:"reach",badge:"b-reach",label:"Reachable",ax:"ax-reach"};
|
|
364
|
+
return{cls:"nosig",badge:"b-nosig",label:"No signal",ax:"ax-nosig"};
|
|
365
|
+
}
|
|
366
|
+
function ctaOf(b){
|
|
367
|
+
if(b.tier==="proven")return"This method is Dynamically Proven. A test breaks if you mutate it. Keep it green.";
|
|
368
|
+
if(b.tier==="assoc")return"A test calls this method, but doesn't prove breakage. Run <code>npx -y @orangepro/orangepro-mcp opro start</code> to attempt dynamic proof.";
|
|
369
|
+
if(b.tier==="none"&&b.reachable)return"This method is reachable from a tested path but has no direct test. Write one.";
|
|
370
|
+
return"Nothing in your test suite touches this method. Write a test that calls it and asserts the output.";
|
|
371
|
+
}
|
|
449
372
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
373
|
+
let activeGrp=null,activeTier=null,searchQ="";
|
|
374
|
+
const behSide=$("#beh-groups"),behGrid=$("#beh-grid");
|
|
375
|
+
function renderBeh(){
|
|
376
|
+
behGrid.innerHTML="";
|
|
377
|
+
const rows=D.behaviors.filter(b=>(!activeGrp||b.group===activeGrp)&&(!activeTier||tierOf(b).cls===activeTier)&&(!searchQ||(b.sig+" "+b.file).toLowerCase().includes(searchQ)));
|
|
378
|
+
rows.forEach(b=>{
|
|
379
|
+
const t=tierOf(b);
|
|
380
|
+
const c=el("div","beh-card",
|
|
381
|
+
\`<div class="bc-top"><span class="bc-sig">\${esc(b.sig)}</span><span class="badge \${t.badge} bc-badge"><span class="d"></span>\${t.label}</span></div><div class="bc-file">\${esc(b.file)}</div><div class="bc-bar"></div>\`);
|
|
382
|
+
c.dataset.t=t.cls;
|
|
383
|
+
c.tabIndex=0;
|
|
384
|
+
c.onclick=()=>showDrill(D.behaviors.indexOf(b));
|
|
385
|
+
behGrid.append(c);
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
// text search across behavior signature + file
|
|
389
|
+
const searchEl=$("#beh-search");
|
|
390
|
+
searchEl.addEventListener("input",()=>{searchQ=searchEl.value.trim().toLowerCase();renderBeh();});
|
|
391
|
+
// evidence-tier filter (Dynamically Proven first) so users can jump straight to a tier
|
|
392
|
+
const tierWrap=$("#tier-filter");
|
|
393
|
+
[["proven","Dynamically Proven"],["assoc","Test signal"],["reach","Reachable"],["nosig","No signal"]].forEach(([cls,label])=>{
|
|
394
|
+
const n=D.behaviors.filter(b=>tierOf(b).cls===cls).length;
|
|
395
|
+
const btn=el("button","grp",\`<span>\${label}</span><span class="gc">\${n}</span>\`);
|
|
396
|
+
btn.setAttribute("aria-pressed","false");
|
|
397
|
+
btn.onclick=()=>{
|
|
398
|
+
activeTier=activeTier===cls?null:cls;
|
|
399
|
+
[...tierWrap.querySelectorAll(".grp")].forEach(x=>x.setAttribute("aria-pressed",String(x===btn&&!!activeTier)));
|
|
400
|
+
renderBeh();
|
|
455
401
|
};
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
zpEl.innerHTML = \`<h2>\${esc(zp.title)}</h2>\${(zp.body || []).map(p => \`<p>\${esc(p)}</p>\`).join("")}\`;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// tab counts
|
|
469
|
-
$("#t-beh").textContent = D.summary.total.toLocaleString();
|
|
470
|
-
$("#t-flow").textContent = D.flows.length;
|
|
471
|
-
$("#t-risk").textContent = D.risks.length;
|
|
472
|
-
|
|
473
|
-
// ---- codebase ----
|
|
474
|
-
$("#svc-count").textContent = "(" + D.scan.serviceTotal + ")";
|
|
475
|
-
$("#svc-list").append(...D.scan.services.map(([n, c]) => el("div", "svc", \`<span class="nm">\${esc(n)}</span><span class="ct">\${c} behaviors</span>\`)));
|
|
476
|
-
$("#test-total").textContent = D.scan.tests.total.toLocaleString();
|
|
477
|
-
$("#test-int").textContent = D.scan.tests.integration;
|
|
478
|
-
$("#test-unit").textContent = D.scan.tests.unit;
|
|
479
|
-
$("#excluded").innerHTML = \`<b>\${D.scan.excluded.count}</b> \${esc(D.scan.excluded.text)}\`;
|
|
480
|
-
|
|
481
|
-
// ---- behaviors ----
|
|
482
|
-
// Four display tiers over three classifications: the untested \`none\` tier splits into
|
|
483
|
-
// "Reachable Untested" (symbol appears in a static flow) vs "No Signal" via b.reachable.
|
|
484
|
-
const tierBadge = { proven: ["b-proven","Dynamically Proven"], assoc: ["b-assoc","Statically Linked"], none: ["b-none","No Signal"] };
|
|
485
|
-
const tierTip = {
|
|
486
|
-
proven: "Mutation killed — a test broke when OrangePro changed this behavior. Highest confidence.",
|
|
487
|
-
assoc: "A test file appears to import or call this behavior. Not dynamically verified.",
|
|
488
|
-
none: "No test or static-flow signal found yet.",
|
|
489
|
-
reachable: "Appears in a static flow from an entry point, but no test signal was found."
|
|
402
|
+
tierWrap.append(btn);
|
|
403
|
+
});
|
|
404
|
+
D.behaviorGroups.forEach(g=>{
|
|
405
|
+
const btn=el("button","grp",\`<span>\${esc(g.key)}</span><span class="gc">\${g.count}</span>\`);
|
|
406
|
+
btn.setAttribute("aria-pressed","false");
|
|
407
|
+
btn.onclick=()=>{
|
|
408
|
+
activeGrp=activeGrp===g.key?null:g.key;
|
|
409
|
+
[...behSide.querySelectorAll(".grp")].forEach(x=>x.setAttribute("aria-pressed",String(x===btn&&!!activeGrp)));
|
|
410
|
+
renderBeh();
|
|
490
411
|
};
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
412
|
+
behSide.append(btn);
|
|
413
|
+
});
|
|
414
|
+
renderBeh();
|
|
415
|
+
|
|
416
|
+
// drill
|
|
417
|
+
const drill=$("#drill"),dc=$("#drill-content");
|
|
418
|
+
function showDrill(i){
|
|
419
|
+
const b=D.behaviors[i];if(!b)return;
|
|
420
|
+
const t=tierOf(b);
|
|
421
|
+
dc.innerHTML=\`<h2 class="drill-title">\${esc(b.sig)}</h2>
|
|
422
|
+
<div class="drill-row"><h4>Evidence</h4><span class="badge \${t.badge}"><span class="d"></span>\${t.label}</span></div>
|
|
423
|
+
<div class="drill-row"><h4>File</h4><p class="mono">\${esc(b.file)}</p></div>
|
|
424
|
+
<div class="drill-row"><h4>What to do</h4><div class="abox \${t.ax}">\${ctaOf(b)}</div></div>\`;
|
|
425
|
+
drill.classList.add("open");document.body.style.overflow="hidden";
|
|
426
|
+
}
|
|
427
|
+
$("#drill-close").onclick=()=>{drill.classList.remove("open");document.body.style.overflow=""};
|
|
428
|
+
drill.onclick=e=>{if(e.target===drill){drill.classList.remove("open");document.body.style.overflow=""}};
|
|
429
|
+
document.onkeydown=e=>{if(e.key==="Escape"){drill.classList.remove("open");document.body.style.overflow=""}};
|
|
430
|
+
|
|
431
|
+
// flows
|
|
432
|
+
const fl=$("#flow-list");
|
|
433
|
+
D.flows.forEach(f=>{
|
|
434
|
+
const rBadge=f.risk==="critical"?\`<span class="badge b-risk"><span class="d"></span>critical</span>\`:\`<span class="badge b-signal"><span class="d"></span>high</span>\`;
|
|
435
|
+
const pBadge=f.proof==="proven"?\`<span class="badge b-proven"><span class="d"></span>dynamically proven</span>\`:f.proof==="assoc"?\`<span class="badge b-signal"><span class="d"></span>signal</span>\`:\`<span class="badge b-nosig"><span class="d"></span>no proof</span>\`;
|
|
436
|
+
|
|
437
|
+
const totalNodes=(f.trigger?1:0)+f.steps.length;
|
|
438
|
+
const vert=totalNodes>4;
|
|
439
|
+
let chain=vert?\`<div class="flow-chain fc-vert">\`:\`<div class="flow-chain">\`;
|
|
440
|
+
|
|
441
|
+
if(f.trigger){
|
|
442
|
+
chain+=\`<div class="fn"><div class="fn-box fnb-entry"><span style="color:var(--green);font-weight:700">\${esc(f.trigger.verb)}</span> \${esc(f.trigger.path)}</div></div>\`;
|
|
516
443
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
[...behSide.children].forEach(c => c.setAttribute("aria-pressed", String(c === b && activeGroup)));
|
|
523
|
-
renderBeh();
|
|
524
|
-
};
|
|
525
|
-
}));
|
|
444
|
+
f.steps.forEach(s=>{
|
|
445
|
+
const eCls=s.edge==="framework-derived"?"fel-fw":"fel-hard";
|
|
446
|
+
chain+=\`<div class="fe"><div class="fe-line \${eCls}"></div></div>\`;
|
|
447
|
+
const nCls=s.proof==="proven"?"fnb-proven":s.proof==="assoc"?"fnb-assoc":"";
|
|
448
|
+
chain+=\`<div class="fn"><div class="fn-box \${nCls}">\${esc(s.sig)}</div></div>\`;
|
|
526
449
|
});
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
\`<h2 class="drill-title" id="drill-title">\${esc(b.sig)}</h2>
|
|
548
|
-
<div class="drill-section"><h4>Evidence tier</h4><span class="badge \${bc}"><span class="d"></span>\${bl}</span></div>
|
|
549
|
-
\${b.desc ? \`<div class="drill-section"><h4>What this behavior does</h4><p>\${esc(b.desc)}</p></div>\` : ""}
|
|
550
|
-
\${b.file ? \`<div class="drill-section"><h4>Source file</h4><p class="mono">\${esc(b.file)}</p></div>\` : ""}
|
|
551
|
-
<div class="drill-section"><h4>Why this is a behavior</h4><div class="why-box">\${why}</div></div>
|
|
552
|
-
<div class="drill-section"><h4>What to do</h4><div class="action-box">\${action}</div></div>\`;
|
|
553
|
-
drillReturnFocus = trigger || document.activeElement;
|
|
554
|
-
drill.classList.add("open");
|
|
555
|
-
document.body.style.overflow = "hidden";
|
|
556
|
-
$("#drill-close").focus();
|
|
557
|
-
}
|
|
558
|
-
function closeDrill() {
|
|
559
|
-
if (!drill.classList.contains("open")) return;
|
|
560
|
-
drill.classList.remove("open");
|
|
561
|
-
document.body.style.overflow = "";
|
|
562
|
-
if (drillReturnFocus && drillReturnFocus.focus) drillReturnFocus.focus();
|
|
563
|
-
drillReturnFocus = null;
|
|
564
|
-
}
|
|
565
|
-
$("#drill-close").addEventListener("click", closeDrill);
|
|
566
|
-
drill.addEventListener("click", e => { if (e.target === drill) closeDrill(); }); // backdrop click
|
|
567
|
-
document.addEventListener("keydown", e => { if (e.key === "Escape") closeDrill(); }); // Escape
|
|
568
|
-
|
|
569
|
-
// ---- flows ----
|
|
570
|
-
const fl = $("#flow-list");
|
|
571
|
-
D.flows.forEach(f => {
|
|
572
|
-
const riskBadge = f.risk === "critical" ? \`<span class="badge b-risk"><span class="d"></span>critical risk</span>\`
|
|
573
|
-
: f.risk === "high" ? \`<span class="badge b-assoc"><span class="d"></span>high risk</span>\`
|
|
574
|
-
: f.risk === "medium" ? \`<span class="badge b-info">medium risk</span>\` : "";
|
|
575
|
-
const proofBadge = f.proof === "proven" ? \`<span class="badge b-proven"><span class="d"></span>dynamically proven</span>\`
|
|
576
|
-
: \`<span class="badge b-info">no dynamic proof</span>\`;
|
|
577
|
-
const flowTier = f.steps.some(s => s.tier === "framework-derived")
|
|
578
|
-
? \`<span class="badge b-fw"><span class="d"></span>framework-derived reachable</span>\`
|
|
579
|
-
: \`<span class="badge b-hard"><span class="d"></span>hard reachable</span>\`;
|
|
580
|
-
const steps = f.steps.map((s, i) => {
|
|
581
|
-
const hop = s.edge === "hard" ? \`<span class="hop-tag hard">hard</span>\`
|
|
582
|
-
: s.edge === "framework-derived" ? \`<span class="hop-tag fw">framework-derived</span>\` : "";
|
|
583
|
-
return \`<li class="step" data-tier="\${s.tier}" data-edge="\${s.edge||''}">
|
|
584
|
-
<span class="node"></span>
|
|
585
|
-
<span class="sig"><span class="idx">\${i+1}</span>\${esc(s.sig)} \${hop}</span>
|
|
586
|
-
<div class="desc">\${esc(s.desc)}</div></li>\`;
|
|
587
|
-
}).join("");
|
|
588
|
-
const trig = f.trigger
|
|
589
|
-
? \`<div class="trigger"><span class="tk">Trigger</span><span><span class="verb">\${esc(f.trigger.verb)}</span> <span class="path">\${esc(f.trigger.path)}</span></span></div>\`
|
|
590
|
-
: \`<div class="trigger"><span class="tk">Entry</span><span class="path">\${esc(f.steps[0] ? f.steps[0].sig : f.title)}</span></div>\`;
|
|
591
|
-
fl.append(el("div","flow",
|
|
592
|
-
\`<div class="flow-head"><h3>\${esc(f.title)}</h3>
|
|
593
|
-
<div class="flow-tags">\${riskBadge}\${flowTier}\${proofBadge}<span class="badge b-info">\${f.services} services</span></div></div>
|
|
594
|
-
\${trig}
|
|
595
|
-
<ul class="steps">\${steps}</ul>
|
|
596
|
-
<div class="flow-foot"><span class="why">\${f.why}</span></div>\`));
|
|
450
|
+
chain+=\`</div>\`;
|
|
451
|
+
|
|
452
|
+
fl.append(el("div","flow-card",
|
|
453
|
+
\`<div class="flow-head"><h3>\${esc(f.title)}</h3><div class="flow-tags">\${rBadge}\${pBadge}</div></div>\${chain}\`));
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
// AI flows
|
|
457
|
+
const cf=D.candidateFlows;
|
|
458
|
+
if(cf&&cf.flows.length){
|
|
459
|
+
$("#ai-sec").hidden=false;
|
|
460
|
+
cf.flows.forEach(f=>{
|
|
461
|
+
const vert=f.steps.length>4;
|
|
462
|
+
let chain=vert?\`<div class="flow-chain fc-vert">\`:\`<div class="flow-chain">\`;
|
|
463
|
+
f.steps.forEach((s,i)=>{
|
|
464
|
+
if(i)chain+=\`<div class="fe"><div class="fe-line fel-hard"></div></div>\`;
|
|
465
|
+
chain+=\`<div class="fn"><div class="fn-box">\${esc(s.sig)}</div></div>\`;
|
|
466
|
+
});
|
|
467
|
+
chain+=\`</div>\`;
|
|
468
|
+
$("#ai-list").append(el("div","flow-card ai-card",
|
|
469
|
+
\`<div class="flow-head"><h3>\${esc(f.title)}</h3><div class="flow-tags"><span class="badge b-info">\${esc(cf.model)}</span></div></div>\${chain}\`));
|
|
597
470
|
});
|
|
471
|
+
}
|
|
598
472
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
473
|
+
// risks + generated test samples
|
|
474
|
+
let activeRiskFilter="all";
|
|
475
|
+
const riskList=$("#risk-list"),riskTools=$("#risk-tools");
|
|
476
|
+
function riskMatchesFilter(r){
|
|
477
|
+
const hasGenerated=Boolean(r.generatedTests&&r.generatedTests.length);
|
|
478
|
+
if(activeRiskFilter==="generated")return hasGenerated;
|
|
479
|
+
if(activeRiskFilter==="missing")return !hasGenerated;
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
function renderRiskFilters(){
|
|
483
|
+
const options=[
|
|
484
|
+
["all","All",D.risks.length],
|
|
485
|
+
["generated","Generated tests",D.risks.filter(r=>r.generatedTests&&r.generatedTests.length).length],
|
|
486
|
+
["missing","No generated tests",D.risks.filter(r=>!(r.generatedTests&&r.generatedTests.length)).length]
|
|
487
|
+
];
|
|
488
|
+
riskTools.innerHTML=options.map(([key,label,count])=>\`<button class="risk-filter" type="button" data-risk-filter="\${key}" aria-pressed="\${key===activeRiskFilter}">\${label} <span class="gc">\${count}</span></button>\`).join("");
|
|
489
|
+
}
|
|
490
|
+
function riskCardHtml(r){
|
|
491
|
+
const tags=r.tags.map(([t,k])=>\`<span class="badge b-\${k}"><span class="d"></span>\${esc(t)}</span>\`).join("");
|
|
492
|
+
// category strip: show which concern categories apply to this flow
|
|
493
|
+
let catHtml='';
|
|
494
|
+
if(r.applicableCategories&&r.applicableCategories.length){
|
|
495
|
+
const shownConcerns=new Set((r.generatedTests||[]).map(t=>t.concern).filter(Boolean));
|
|
496
|
+
const pills=r.applicableCategories.map(c=>{
|
|
497
|
+
const shown=shownConcerns.has(c);
|
|
498
|
+
const label=c.replace(/_/g,' ');
|
|
499
|
+
return \`<span class="cat-pill \${shown?'cp-shown':'cp-locked'}">\${esc(label)}</span>\`;
|
|
500
|
+
}).join('');
|
|
501
|
+
const shownN=r.applicableCategories.filter(c=>shownConcerns.has(c)).length;
|
|
502
|
+
const totalN=r.applicableCategories.length;
|
|
503
|
+
catHtml=\`<div class="cat-strip"><div class="cat-strip-lbl">Testing categories for this flow (\${shownN} of \${totalN} shown)</div><div class="cat-pills">\${pills}</div></div>\`;
|
|
504
|
+
}
|
|
505
|
+
let testsHtml='';
|
|
506
|
+
if(r.generatedTests&&r.generatedTests.length){
|
|
507
|
+
testsHtml=\`<div class="gen-tests"><div class="gen-tests-lbl">Generated tests (integration)</div>\`;
|
|
508
|
+
r.generatedTests.forEach(t=>{
|
|
509
|
+
const cBadge=t.concern?\`<span class="badge b-info" style="margin-left:6px;font-size:9px">\${esc(t.concern.replace('_',' '))}</span>\`:'';
|
|
510
|
+
testsHtml+=\`<div class="gen-test"><div class="gen-test-head"><span class="gen-test-name">\${esc(t.name)}\${cBadge}</span><span class="gen-test-assert">\${esc(t.assertion)}</span><span class="gen-test-toggle">▼</span></div><div class="gen-test-body">\${esc(t.code)}</div></div>\`;
|
|
621
511
|
});
|
|
512
|
+
testsHtml+=\`</div>\`;
|
|
622
513
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
514
|
+
return \`<div class="risk-rank">#\${r.rank}</div>
|
|
515
|
+
<div class="risk-ep"><span class="v">\${esc(r.verb)}</span> \${esc(r.path)}</div>
|
|
516
|
+
<div class="risk-desc">\${esc(r.desc)}</div>
|
|
517
|
+
<div class="risk-tags">\${tags}</div>
|
|
518
|
+
<div class="todo">\${esc(r.todo)}</div>\${testsHtml}\${catHtml}\`;
|
|
519
|
+
}
|
|
520
|
+
function renderRisks(){
|
|
521
|
+
riskList.innerHTML="";
|
|
522
|
+
D.risks.filter(riskMatchesFilter).forEach(r=>riskList.append(el("div","risk-card",riskCardHtml(r))));
|
|
523
|
+
if(activeRiskFilter==="all"&&D.generatedTotal){
|
|
524
|
+
const hiddenGenerated=Math.max(0,D.generatedTotal-D.shownCount);
|
|
525
|
+
const remainingRiskFlows=Math.max(0,D.risks.length-D.generatedTotal);
|
|
526
|
+
riskList.append(el("div","paywall",
|
|
527
|
+
hiddenGenerated
|
|
528
|
+
? \`<div class="paywall-num">\${hiddenGenerated} more tests generated</div>
|
|
529
|
+
<div class="paywall-txt">OrangePro generated tests for \${D.generatedTotal} behaviors across your highest-risk flows. You're seeing \${D.shownCount}.</div>
|
|
530
|
+
<a class="paywall-btn" href="https://app.orangepro.ai" target="_blank">View all on OrangePro Platform →</a>\`
|
|
531
|
+
: remainingRiskFlows
|
|
532
|
+
? \`<div class="paywall-num">\${remainingRiskFlows} high-risk flows left</div>
|
|
533
|
+
<div class="paywall-txt">The local MCP accepted \${D.generatedTotal} runnable generated test\${D.generatedTotal===1?"":"s"} for this report. Generate the remaining high-risk flow tests on OrangePro Platform.</div>
|
|
534
|
+
<a class="paywall-btn" href="https://app.orangepro.ai" target="_blank">Generate remaining tests on Platform →</a>\`
|
|
535
|
+
: \`<div class="paywall-num">All generated tests are shown</div>
|
|
536
|
+
<div class="paywall-txt">OrangePro generated tests for every high-risk flow in this report, and every generated test is visible here.</div>\`));
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
riskTools.addEventListener("click",e=>{
|
|
540
|
+
const btn=e.target.closest("[data-risk-filter]");
|
|
541
|
+
if(!btn)return;
|
|
542
|
+
activeRiskFilter=btn.dataset.riskFilter;
|
|
543
|
+
renderRiskFilters();
|
|
544
|
+
renderRisks();
|
|
545
|
+
});
|
|
546
|
+
renderRiskFilters();
|
|
547
|
+
renderRisks();
|
|
548
|
+
// toggle test expand
|
|
549
|
+
document.addEventListener('click',e=>{
|
|
550
|
+
const h=e.target.closest('.gen-test-head');
|
|
551
|
+
if(h)h.parentElement.classList.toggle('open');
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// tabs
|
|
555
|
+
const tabs=[...document.querySelectorAll(".tab")];
|
|
556
|
+
tabs.forEach(t=>t.onclick=()=>{
|
|
557
|
+
tabs.forEach(x=>x.setAttribute("aria-selected",String(x===t)));
|
|
558
|
+
document.querySelectorAll(".panel").forEach(p=>p.classList.toggle("active",p.id==="panel-"+t.dataset.tab));
|
|
559
|
+
});
|
|
649
560
|
})();
|
|
650
561
|
</script>
|
|
651
562
|
</body>
|