@orangepro/orangepro-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +328 -0
- package/dist/local/agentWorkflow.js +81 -0
- package/dist/local/aiGraph/links.js +635 -0
- package/dist/local/analyze/analyzer.js +2129 -0
- package/dist/local/analyze/behaviorContracts.js +169 -0
- package/dist/local/analyze/boilerplate.js +42 -0
- package/dist/local/analyze/callGraph.js +458 -0
- package/dist/local/analyze/classify.js +219 -0
- package/dist/local/analyze/clustering.js +357 -0
- package/dist/local/analyze/confirm.js +2422 -0
- package/dist/local/analyze/coverage.js +518 -0
- package/dist/local/analyze/coverageArtifacts.js +607 -0
- package/dist/local/analyze/frameworks.js +115 -0
- package/dist/local/analyze/linkage/conventions.js +160 -0
- package/dist/local/analyze/parseCache.js +164 -0
- package/dist/local/analyze/selfAssert.js +53 -0
- package/dist/local/analyze/symbols.js +430 -0
- package/dist/local/analyze/testLayer.js +135 -0
- package/dist/local/analyze/treeSitter/engine.js +1253 -0
- package/dist/local/analyze/treeSitter/languages.js +101 -0
- package/dist/local/autoProve.js +620 -0
- package/dist/local/cli.js +1468 -0
- package/dist/local/cliArgs.js +112 -0
- package/dist/local/corpusScope.js +162 -0
- package/dist/local/enrich/csv.js +348 -0
- package/dist/local/enrich/index.js +43 -0
- package/dist/local/enrich/markdown.js +193 -0
- package/dist/local/explain/explain.js +91 -0
- package/dist/local/exportCli.js +26 -0
- package/dist/local/flows/flowWalker.js +215 -0
- package/dist/local/flows/llmFlowDiscovery.js +567 -0
- package/dist/local/freshness/changed.js +280 -0
- package/dist/local/freshness/manifest.js +35 -0
- package/dist/local/freshness/status.js +30 -0
- package/dist/local/gaps/gaps.js +114 -0
- package/dist/local/generate/buckets.js +73 -0
- package/dist/local/generate/compareJudge.js +124 -0
- package/dist/local/generate/compareReport.js +538 -0
- package/dist/local/generate/compareScore.js +105 -0
- package/dist/local/generate/deriveImports.js +91 -0
- package/dist/local/generate/generator.js +2586 -0
- package/dist/local/generate/prompt.js +144 -0
- package/dist/local/generate/promptV5.js +438 -0
- package/dist/local/generate/providers.js +400 -0
- package/dist/local/generate/runHints.js +304 -0
- package/dist/local/graph/citations.js +73 -0
- package/dist/local/graph/confirmable.js +72 -0
- package/dist/local/graph/factories.js +210 -0
- package/dist/local/graph/ontology.js +18 -0
- package/dist/local/interactive.js +53 -0
- package/dist/local/jobs/jobStore.js +80 -0
- package/dist/local/jobs/notify.js +29 -0
- package/dist/local/jobs/runner.js +75 -0
- package/dist/local/ledger.js +117 -0
- package/dist/local/localConfig.js +112 -0
- package/dist/local/mcp.js +548 -0
- package/dist/local/operations.js +1749 -0
- package/dist/local/pack/coverageReport.js +192 -0
- package/dist/local/pack/exporter.js +195 -0
- package/dist/local/pack/schema.js +128 -0
- package/dist/local/pack/summary.js +127 -0
- package/dist/local/pack/validate.js +25 -0
- package/dist/local/proofRunnability.js +366 -0
- package/dist/local/recipe/dbSqljs.js +255 -0
- package/dist/local/reprove/paths.js +13 -0
- package/dist/local/reprove/scoped.js +136 -0
- package/dist/local/resolve/barrelWalker.js +178 -0
- package/dist/local/resolve/exportIndex.js +270 -0
- package/dist/local/resolve/importGraph.js +347 -0
- package/dist/local/resolve/resolver.js +122 -0
- package/dist/local/resolve/resolverCache.js +117 -0
- package/dist/local/rtm.js +413 -0
- package/dist/local/score/coverage.js +99 -0
- package/dist/local/score/doctor.js +67 -0
- package/dist/local/score/risk.js +362 -0
- package/dist/local/score/score.js +182 -0
- package/dist/local/types.js +1 -0
- package/dist/local/util/hash.js +16 -0
- package/dist/local/util/ids.js +16 -0
- package/dist/local/util/progress.js +8 -0
- package/dist/local/util/redact.js +39 -0
- package/dist/local/util/time.js +1 -0
- package/dist/local/util/walk.js +174 -0
- package/dist/local/viz/behaviorReportData.js +367 -0
- package/dist/local/viz/behaviorReportHtml.js +664 -0
- package/dist/local/viz/d3.bundle.js +3 -0
- package/dist/local/viz/html.js +1152 -0
- package/dist/local/viz/payload.js +525 -0
- package/dist/local/workspace.js +99 -0
- package/docs/agent-workflow.md +167 -0
- package/docs/agents/claude-code.md +43 -0
- package/docs/agents/codex.md +52 -0
- package/docs/agents/cursor.md +39 -0
- package/docs/agents/opencode.md +43 -0
- package/docs/agents/vscode.md +34 -0
- package/docs/local-proof-kit.md +269 -0
- package/package.json +92 -0
- package/scripts/spikes/dynamic-proof-jest-reporter.cjs +66 -0
- package/scripts/spikes/dynamic-proof-mocha-reporter.cjs +105 -0
- package/scripts/spikes/dynamic-proof-spike.mjs +2335 -0
- package/scripts/spikes/dynamic-proof-vitest-reporter.mjs +81 -0
- package/scripts/spikes/failure-summary.mjs +29 -0
|
@@ -0,0 +1,664 @@
|
|
|
1
|
+
const TEMPLATE = `<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>OrangePro · Behavior Coverage Report</title>
|
|
7
|
+
<style>
|
|
8
|
+
/* ============================================================
|
|
9
|
+
OrangePro behavior-coverage report — single-file vision demo.
|
|
10
|
+
Identity: GitHub-dark product UI. Honest tier layer added on
|
|
11
|
+
top of the v5 mockup: every flow hop is tagged hard-reachable
|
|
12
|
+
vs framework-derived, and dynamic-proof is first-class.
|
|
13
|
+
All view data lives in window.DATA (the backend graph contract).
|
|
14
|
+
============================================================ */
|
|
15
|
+
:root {
|
|
16
|
+
--bg: #0d1117;
|
|
17
|
+
--surface: #161b22;
|
|
18
|
+
--surface-2: #1b2129;
|
|
19
|
+
--surface-3: #21262d;
|
|
20
|
+
--border: #2b313a;
|
|
21
|
+
--border-2: #383f49;
|
|
22
|
+
--ink: #e6edf3;
|
|
23
|
+
--ink-soft: #c9d3df;
|
|
24
|
+
--muted: #9aa4b1;
|
|
25
|
+
--faint: #6e7681;
|
|
26
|
+
|
|
27
|
+
--green: #3fb950; --green-bg: rgba(63,185,80,.12); --green-bd: rgba(63,185,80,.35);
|
|
28
|
+
--amber: #e3b341; --amber-bg: rgba(227,179,65,.12); --amber-bd: rgba(227,179,65,.35);
|
|
29
|
+
--red: #f85149; --red-bg: rgba(248,81,73,.11); --red-bd: rgba(248,81,73,.32);
|
|
30
|
+
--blue: #58a6ff; --blue-bg: rgba(88,166,255,.12); --blue-bd: rgba(88,166,255,.35);
|
|
31
|
+
--purple: #bc8cff; --purple-bg: rgba(188,140,255,.12);--purple-bd:rgba(188,140,255,.35);
|
|
32
|
+
--accent: #f0883e; --accent-bg: rgba(240,136,62,.12); --accent-bd:rgba(240,136,62,.40);
|
|
33
|
+
|
|
34
|
+
--radius: 12px;
|
|
35
|
+
--radius-sm: 8px;
|
|
36
|
+
--mono: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Code", Menlo, Consolas, monospace;
|
|
37
|
+
--sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
38
|
+
|
|
39
|
+
--z-sticky: 100; --z-tooltip: 600;
|
|
40
|
+
--ease: cubic-bezier(.22,1,.36,1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
* { box-sizing: border-box; }
|
|
44
|
+
html { -webkit-text-size-adjust: 100%; }
|
|
45
|
+
body {
|
|
46
|
+
margin: 0;
|
|
47
|
+
background: var(--bg);
|
|
48
|
+
color: var(--ink);
|
|
49
|
+
font-family: var(--sans);
|
|
50
|
+
font-size: 14px;
|
|
51
|
+
line-height: 1.55;
|
|
52
|
+
-webkit-font-smoothing: antialiased;
|
|
53
|
+
text-rendering: optimizeLegibility;
|
|
54
|
+
}
|
|
55
|
+
::selection { background: var(--accent-bg); }
|
|
56
|
+
a { color: var(--blue); text-decoration: none; }
|
|
57
|
+
|
|
58
|
+
/* scrollbar — keep native behavior, only tint */
|
|
59
|
+
::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
60
|
+
::-webkit-scrollbar-thumb { background: #30363d; border-radius: 6px; border: 2px solid var(--bg); }
|
|
61
|
+
::-webkit-scrollbar-thumb:hover { background: #3d444d; }
|
|
62
|
+
|
|
63
|
+
.wrap { max-width: 1120px; margin: 0 auto; padding: 30px 28px 80px; }
|
|
64
|
+
|
|
65
|
+
/* ---------- header ---------- */
|
|
66
|
+
header.top { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; padding-bottom: 22px; border-bottom: 1px solid var(--border); }
|
|
67
|
+
.brand { display: flex; align-items: center; gap: 13px; }
|
|
68
|
+
.logo { width: 38px; height: 38px; border-radius: 9px; background: linear-gradient(150deg, #f0883e, #db5a28); display: grid; place-items: center; color: #1a0f06; font-weight: 800; font-size: 17px; box-shadow: 0 2px 10px rgba(219,90,40,.35); flex-shrink: 0; }
|
|
69
|
+
.brand h1 { margin: 0; font-size: 17px; font-weight: 650; letter-spacing: -.01em; }
|
|
70
|
+
.brand p { margin: 2px 0 0; font-size: 12.5px; color: var(--muted); }
|
|
71
|
+
.meta { text-align: right; font-size: 12px; color: var(--muted); line-height: 1.7; white-space: nowrap; }
|
|
72
|
+
.meta .repo-state { color: var(--ink-soft); }
|
|
73
|
+
|
|
74
|
+
/* ---------- KPI cards ---------- */
|
|
75
|
+
.kpis { display: grid; grid-template-columns: repeat(5, 1fr); gap: 14px; margin: 22px 0 8px; }
|
|
76
|
+
.kpi { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px 18px 17px; position: relative; overflow: hidden; transition: border-color .18s var(--ease), transform .18s var(--ease); }
|
|
77
|
+
.kpi:hover { border-color: var(--border-2); transform: translateY(-1px); }
|
|
78
|
+
/* a quiet per-tier corner wash ties the status dot to the number's tone */
|
|
79
|
+
.kpi::before { content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; opacity: .6; background: radial-gradient(150px 105px at 100% 0%, var(--kpi-wash, transparent), transparent 70%); }
|
|
80
|
+
.kpi .label, .kpi .num, .kpi .sub { position: relative; z-index: 1; }
|
|
81
|
+
.kpi .label { font-size: 11px; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); font-weight: 600; }
|
|
82
|
+
.kpi .num { font-size: 32px; font-weight: 750; letter-spacing: -.02em; margin: 9px 0 3px; line-height: 1; font-variant-numeric: tabular-nums; }
|
|
83
|
+
.kpi .sub { font-size: 12px; color: var(--muted); }
|
|
84
|
+
.kpi[data-tone="proven"] { --kpi-wash: var(--green-bg); }
|
|
85
|
+
.kpi[data-tone="assoc"] { --kpi-wash: var(--amber-bg); }
|
|
86
|
+
.kpi[data-tone="none"] { --kpi-wash: var(--red-bg); }
|
|
87
|
+
.kpi[data-tone="proven"] .num { color: var(--green); }
|
|
88
|
+
.kpi[data-tone="assoc"] .num { color: var(--amber); }
|
|
89
|
+
.kpi[data-tone="none"] .num { color: var(--red); }
|
|
90
|
+
.kpi .spark { position: absolute; right: 14px; top: 14px; width: 7px; height: 7px; border-radius: 50%; }
|
|
91
|
+
.kpi[data-tone="proven"] .spark { background: var(--green); box-shadow: 0 0 0 4px var(--green-bg); }
|
|
92
|
+
.kpi[data-tone="assoc"] .spark { background: var(--amber); box-shadow: 0 0 0 4px var(--amber-bg); }
|
|
93
|
+
.kpi[data-tone="none"] .spark { background: var(--red); box-shadow: 0 0 0 4px var(--red-bg); }
|
|
94
|
+
|
|
95
|
+
/* ---------- pipeline strip (which PR populated what) ---------- */
|
|
96
|
+
.pipeline { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 16px 0 4px; padding: 11px 14px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: 12px; }
|
|
97
|
+
.pipeline .plabel { color: var(--muted); font-weight: 600; margin-right: 2px; }
|
|
98
|
+
.pstage { display: inline-flex; align-items: center; gap: 6px; padding: 4px 9px; border-radius: 20px; border: 1px solid var(--border-2); color: var(--ink-soft); background: var(--surface-2); font-variant-numeric: tabular-nums; }
|
|
99
|
+
.pstage .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--faint); }
|
|
100
|
+
.pstage[data-on="1"] { color: var(--green); border-color: var(--green-bd); background: var(--green-bg); }
|
|
101
|
+
.pstage[data-on="1"] .dot { background: var(--green); }
|
|
102
|
+
.pstage[data-on="partial"] { color: var(--amber); border-color: var(--amber-bd); background: var(--amber-bg); }
|
|
103
|
+
.pstage[data-on="partial"] .dot { background: var(--amber); }
|
|
104
|
+
.pstage .pr { opacity: .7; font-size: 11px; }
|
|
105
|
+
.pstage .arrow { color: var(--faint); }
|
|
106
|
+
|
|
107
|
+
.proof-callout { display: grid; grid-template-columns: auto 1fr; gap: 10px 12px; margin: 12px 0 4px; padding: 13px 15px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); }
|
|
108
|
+
.proof-callout .mark { width: 9px; height: 9px; border-radius: 50%; margin-top: 5px; background: var(--blue); box-shadow: 0 0 0 4px var(--blue-bg); }
|
|
109
|
+
.proof-callout h2 { margin: 0 0 3px; font-size: 13.5px; font-weight: 650; }
|
|
110
|
+
.proof-callout p { margin: 0; color: var(--muted); font-size: 12.5px; max-width: 92ch; text-wrap: pretty; }
|
|
111
|
+
.proof-callout .action { margin-top: 6px; color: var(--ink-soft); }
|
|
112
|
+
.proof-callout code { font-family: var(--mono); color: var(--blue); background: var(--blue-bg); border: 1px solid var(--blue-bd); border-radius: 5px; padding: 1px 5px; }
|
|
113
|
+
.proof-callout[data-state="proven"] .mark { background: var(--green); box-shadow: 0 0 0 4px var(--green-bg); }
|
|
114
|
+
.proof-callout[data-state="attempted"] .mark { background: var(--amber); box-shadow: 0 0 0 4px var(--amber-bg); }
|
|
115
|
+
|
|
116
|
+
/* ---------- zero-dynamic-proof explainer (rendered only when Dynamically Proven == 0) ---------- */
|
|
117
|
+
.zero-proof { margin: 12px 0 4px; padding: 15px 18px; background: var(--surface); border: 1px solid var(--amber-bd); border-radius: var(--radius-sm); }
|
|
118
|
+
.zero-proof[hidden] { display: none; }
|
|
119
|
+
.zero-proof h2 { margin: 0 0 8px; font-size: 13.5px; font-weight: 650; color: var(--amber); }
|
|
120
|
+
.zero-proof p { margin: 0 0 8px; color: var(--muted); font-size: 12.5px; max-width: 92ch; text-wrap: pretty; }
|
|
121
|
+
.zero-proof p:last-child { margin-bottom: 0; }
|
|
122
|
+
|
|
123
|
+
/* ---------- tier definitions legend (four honest display tiers) ---------- */
|
|
124
|
+
.tier-legend { margin: 8px 0 4px; }
|
|
125
|
+
.tier-legend .li { align-items: baseline; }
|
|
126
|
+
|
|
127
|
+
/* ---------- tabs ---------- */
|
|
128
|
+
nav.tabs { display: flex; gap: 4px; margin: 22px 0 0; border-bottom: 1px solid var(--border); }
|
|
129
|
+
.tab { appearance: none; background: none; border: 0; color: var(--muted); font: inherit; font-weight: 550; padding: 11px 14px 13px; cursor: pointer; position: relative; display: inline-flex; align-items: center; gap: 8px; border-radius: 7px 7px 0 0; transition: color .15s; }
|
|
130
|
+
.tab:hover { color: var(--ink-soft); }
|
|
131
|
+
.tab[aria-selected="true"] { color: var(--ink); }
|
|
132
|
+
.tab[aria-selected="true"]::after { content: ""; position: absolute; left: 6px; right: 6px; bottom: -1px; height: 2px; background: var(--accent); border-radius: 2px 2px 0 0; }
|
|
133
|
+
.tab .count { font-size: 11px; font-weight: 600; color: var(--muted); background: var(--surface-3); padding: 1px 7px; border-radius: 20px; font-variant-numeric: tabular-nums; }
|
|
134
|
+
.tab[aria-selected="true"] .count { color: var(--ink-soft); }
|
|
135
|
+
.tab:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
|
|
136
|
+
|
|
137
|
+
/* ---------- panels ---------- */
|
|
138
|
+
.panel { display: none; padding-top: 22px; }
|
|
139
|
+
.panel.active { display: block; animation: fade .26s var(--ease); }
|
|
140
|
+
@keyframes fade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
|
|
141
|
+
|
|
142
|
+
/* note box — NO side stripe (full border + inline marker) */
|
|
143
|
+
.note { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 15px 18px; margin-bottom: 20px; }
|
|
144
|
+
.note h3 { margin: 0 0 5px; font-size: 13.5px; font-weight: 650; display: flex; align-items: center; gap: 8px; }
|
|
145
|
+
.note h3 .ic { color: var(--accent); display: inline-flex; }
|
|
146
|
+
.note p { margin: 0; font-size: 13px; color: var(--muted); max-width: 74ch; text-wrap: pretty; }
|
|
147
|
+
.note b { color: var(--ink-soft); font-weight: 600; }
|
|
148
|
+
|
|
149
|
+
/* ---------- two-column scan layout ---------- */
|
|
150
|
+
.cols { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
|
151
|
+
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; }
|
|
152
|
+
.card .ttl { font-size: 11px; letter-spacing: .07em; text-transform: uppercase; color: var(--accent); font-weight: 650; margin: 0 0 12px; }
|
|
153
|
+
.pill { display: inline-block; background: var(--accent-bg); border: 1px solid var(--accent-bd); color: #f6b079; border-radius: 7px; padding: 6px 11px; font-size: 12.5px; font-family: var(--mono); }
|
|
154
|
+
.bignum { font-size: 30px; font-weight: 750; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
|
|
155
|
+
.split2 { display: flex; gap: 30px; margin-top: 4px; }
|
|
156
|
+
.split2 .v { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; }
|
|
157
|
+
.split2 .v.b { color: var(--blue); } .split2 .v.p { color: var(--purple); }
|
|
158
|
+
.split2 .k { font-size: 12px; color: var(--muted); }
|
|
159
|
+
.svc-list { margin-top: 4px; max-height: 268px; overflow-y: auto; }
|
|
160
|
+
.svc { display: flex; justify-content: space-between; align-items: center; padding: 8px 2px; border-bottom: 1px solid var(--border); font-size: 13px; }
|
|
161
|
+
.svc:last-child { border-bottom: 0; }
|
|
162
|
+
.svc .nm { font-family: var(--mono); color: var(--ink-soft); }
|
|
163
|
+
.svc .ct { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
164
|
+
.excl { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 13px 15px; font-size: 12.5px; color: var(--muted); margin-top: 10px; }
|
|
165
|
+
.excl b { color: var(--ink-soft); }
|
|
166
|
+
|
|
167
|
+
/* ---------- behaviors ---------- */
|
|
168
|
+
.beh-layout { display: grid; grid-template-columns: 220px 1fr; gap: 16px; }
|
|
169
|
+
.beh-side { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 8px; height: max-content; position: sticky; top: 16px; }
|
|
170
|
+
.grp { width: 100%; text-align: left; appearance: none; border: 0; background: none; font: inherit; color: var(--muted); padding: 9px 12px; border-radius: var(--radius-sm); cursor: pointer; display: flex; justify-content: space-between; align-items: center; transition: background .14s, color .14s; }
|
|
171
|
+
.grp:hover { background: var(--surface-2); color: var(--ink-soft); }
|
|
172
|
+
.grp:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
|
|
173
|
+
.grp[aria-pressed="true"] { background: var(--accent-bg); color: var(--accent); font-weight: 600; }
|
|
174
|
+
.grp .gc { font-size: 11.5px; color: inherit; opacity: .8; font-variant-numeric: tabular-nums; }
|
|
175
|
+
.beh-main { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
|
|
176
|
+
.beh-row { padding: 13px 18px; border-bottom: 1px solid var(--border); transition: background .14s; cursor: pointer; }
|
|
177
|
+
.beh-row:last-child { border-bottom: 0; }
|
|
178
|
+
.beh-row:hover { background: var(--surface-2); }
|
|
179
|
+
.beh-row:focus-visible { outline: 2px solid var(--blue); outline-offset: -2px; background: var(--surface-2); }
|
|
180
|
+
.beh-row .sig { font-family: var(--mono); font-size: 13px; color: var(--ink); }
|
|
181
|
+
.beh-row .desc { font-size: 12.5px; color: var(--muted); margin-top: 4px; max-width: 82ch; text-wrap: pretty; }
|
|
182
|
+
.beh-row .filemeta { font-family: var(--mono); font-size: 11.5px; color: var(--faint); }
|
|
183
|
+
|
|
184
|
+
/* badges — shared semantic vocabulary */
|
|
185
|
+
.badge { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 20px; border: 1px solid transparent; vertical-align: middle; letter-spacing: .01em; }
|
|
186
|
+
.badge .d { width: 6px; height: 6px; border-radius: 50%; }
|
|
187
|
+
.b-proven { color: var(--green); background: var(--green-bg); border-color: var(--green-bd); } .b-proven .d { background: var(--green); }
|
|
188
|
+
.b-assoc { color: var(--amber); background: var(--amber-bg); border-color: var(--amber-bd); } .b-assoc .d { background: var(--amber); }
|
|
189
|
+
.b-none { color: var(--red); background: var(--red-bg); border-color: var(--red-bd); } .b-none .d { background: var(--red); }
|
|
190
|
+
.b-hard { color: var(--blue); background: var(--blue-bg); border-color: var(--blue-bd); } .b-hard .d { background: var(--blue); }
|
|
191
|
+
.b-fw { color: var(--amber); background: var(--amber-bg); border-color: var(--amber-bd); } .b-fw .d { background: var(--amber); }
|
|
192
|
+
.b-entry { color: var(--purple);background: var(--purple-bg);border-color: var(--purple-bd); }
|
|
193
|
+
.b-info { color: var(--muted); background: var(--surface-3); border-color: var(--border-2); }
|
|
194
|
+
.b-risk { color: var(--red); background: var(--red-bg); border-color: var(--red-bd); }
|
|
195
|
+
|
|
196
|
+
.beh-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
|
|
197
|
+
|
|
198
|
+
/* ---------- behavior detail modal ---------- */
|
|
199
|
+
.drill-overlay { display: none; position: fixed; inset: 0; z-index: var(--z-tooltip); padding: 24px; background: rgba(1,4,9,.66); justify-content: center; align-items: center; }
|
|
200
|
+
.drill-overlay.open { display: flex; animation: fade .18s var(--ease); }
|
|
201
|
+
.drill-card { position: relative; background: var(--surface); border: 1px solid var(--border-2); border-radius: var(--radius); padding: 22px 24px 24px; max-width: 620px; width: 100%; max-height: 82vh; overflow-y: auto; box-shadow: 0 16px 48px rgba(0,0,0,.5); }
|
|
202
|
+
.drill-close { position: absolute; top: 12px; right: 14px; appearance: none; background: none; border: 0; color: var(--muted); font-size: 22px; line-height: 1; cursor: pointer; padding: 4px 8px; border-radius: 6px; }
|
|
203
|
+
.drill-close:hover { color: var(--ink); background: var(--surface-2); }
|
|
204
|
+
.drill-close:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
|
|
205
|
+
.drill-title { font-family: var(--mono); font-size: 15px; font-weight: 650; color: var(--ink); margin: 0 34px 18px 0; text-wrap: balance; }
|
|
206
|
+
.drill-section { margin-bottom: 15px; }
|
|
207
|
+
.drill-section:last-child { margin-bottom: 0; }
|
|
208
|
+
.drill-section h4 { font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--accent); font-weight: 650; margin: 0 0 6px; }
|
|
209
|
+
.drill-section p { margin: 0; font-size: 13px; color: var(--muted); text-wrap: pretty; }
|
|
210
|
+
.drill-section p.mono { font-family: var(--mono); font-size: 12px; color: var(--blue); word-break: break-all; }
|
|
211
|
+
.drill-section .why-box { font-size: 12.5px; color: var(--ink-soft); background: var(--blue-bg); border: 1px solid var(--blue-bd); border-radius: var(--radius-sm); padding: 10px 12px; text-wrap: pretty; }
|
|
212
|
+
.drill-section .action-box { font-size: 12.5px; color: var(--ink-soft); background: var(--green-bg); border: 1px solid var(--green-bd); border-radius: var(--radius-sm); padding: 10px 12px; text-wrap: pretty; }
|
|
213
|
+
|
|
214
|
+
/* ---------- flows ---------- */
|
|
215
|
+
.legend { display: flex; flex-wrap: wrap; gap: 18px; align-items: center; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 11px 16px; margin-bottom: 18px; font-size: 12px; color: var(--muted); }
|
|
216
|
+
.legend .li { display: inline-flex; align-items: center; gap: 7px; }
|
|
217
|
+
.legend .rail-demo { width: 22px; height: 0; border-top: 2px solid var(--blue); }
|
|
218
|
+
.legend .rail-demo.fw { border-top: 2px dashed var(--amber); }
|
|
219
|
+
.legend b { color: var(--ink-soft); font-weight: 600; }
|
|
220
|
+
|
|
221
|
+
.flow { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px 22px; margin-bottom: 16px; transition: border-color .16s var(--ease); }
|
|
222
|
+
.flow:hover { border-color: var(--border-2); }
|
|
223
|
+
.flow-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
|
|
224
|
+
.flow-head h3 { margin: 0; font-size: 15.5px; font-weight: 650; letter-spacing: -.01em; text-wrap: balance; }
|
|
225
|
+
.flow-tags { display: flex; gap: 7px; flex-wrap: wrap; }
|
|
226
|
+
.trigger { font-family: var(--mono); font-size: 12.5px; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 9px 13px; margin: 14px 0 6px; display: flex; align-items: center; gap: 10px; }
|
|
227
|
+
.trigger .tk { color: var(--faint); font-size: 11px; letter-spacing: .05em; text-transform: uppercase; }
|
|
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
|
+
}
|
|
289
|
+
</style>
|
|
290
|
+
</head>
|
|
291
|
+
<body>
|
|
292
|
+
<div class="wrap">
|
|
293
|
+
|
|
294
|
+
<header class="top">
|
|
295
|
+
<div class="brand">
|
|
296
|
+
<div class="logo">O</div>
|
|
297
|
+
<div>
|
|
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>
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
<div class="pipeline" id="pipeline" aria-label="Evidence pipeline status"></div>
|
|
318
|
+
<section class="proof-callout" id="proof-guidance" aria-label="Dynamic proof guidance"></section>
|
|
319
|
+
<section class="zero-proof" id="zero-proof" aria-label="Why dynamic proof is zero" hidden></section>
|
|
320
|
+
|
|
321
|
+
<nav class="tabs" role="tablist" aria-label="Report sections">
|
|
322
|
+
<button class="tab" role="tab" aria-selected="true" data-tab="codebase">Your Codebase</button>
|
|
323
|
+
<button class="tab" role="tab" aria-selected="false" data-tab="behaviors">Behaviors <span class="count" id="t-beh">0</span></button>
|
|
324
|
+
<button class="tab" role="tab" aria-selected="false" data-tab="flows">Flows <span class="count" id="t-flow">0</span></button>
|
|
325
|
+
<button class="tab" role="tab" aria-selected="false" data-tab="risks">Priority Risks <span class="count" id="t-risk">0</span></button>
|
|
326
|
+
</nav>
|
|
327
|
+
|
|
328
|
+
<!-- YOUR CODEBASE -->
|
|
329
|
+
<section class="panel active" id="panel-codebase" role="tabpanel">
|
|
330
|
+
<div class="note">
|
|
331
|
+
<h3><span class="ic" aria-hidden="true">◆</span> What we scanned</h3>
|
|
332
|
+
<p>We read your repository's source, identified service classes, route handlers, and test files, then classified each public method as a <b>testable behavior</b> or infrastructure plumbing. No code is executed in this pass.</p>
|
|
333
|
+
</div>
|
|
334
|
+
<div class="cols">
|
|
335
|
+
<div class="card">
|
|
336
|
+
<p class="ttl">Framework</p>
|
|
337
|
+
<span class="pill" id="framework">—</span>
|
|
338
|
+
<p class="ttl" style="margin-top:22px">Services discovered <span id="svc-count" style="color:var(--ink);font-family:var(--mono)"></span></p>
|
|
339
|
+
<div class="svc-list" id="svc-list"></div>
|
|
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>
|
|
376
|
+
</div>
|
|
377
|
+
<div id="flow-list"></div>
|
|
378
|
+
|
|
379
|
+
<!-- AI-SUGGESTED CANDIDATE FLOWS — worklist only, never evidence, never counted -->
|
|
380
|
+
<div class="ai-flows" id="ai-flows" hidden>
|
|
381
|
+
<div class="note">
|
|
382
|
+
<h3><span class="ic" aria-hidden="true" style="color:var(--muted)">◇</span> AI-suggested flows — verify these</h3>
|
|
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>
|
|
384
|
+
</div>
|
|
385
|
+
<div id="ai-flow-list"></div>
|
|
386
|
+
</div>
|
|
387
|
+
</section>
|
|
388
|
+
|
|
389
|
+
<!-- PRIORITY RISKS -->
|
|
390
|
+
<section class="panel" id="panel-risks" role="tabpanel">
|
|
391
|
+
<div class="note">
|
|
392
|
+
<h3><span class="ic" aria-hidden="true">◆</span> What to test first</h3>
|
|
393
|
+
<p>Ranked by the OrangePro risk score: blast radius (how many parts depend on this), code churn, and entry-point proximity. Fix these first to cut production-incident probability. This list is ranked, not exhaustive.</p>
|
|
394
|
+
</div>
|
|
395
|
+
<div id="risk-list"></div>
|
|
396
|
+
</section>
|
|
397
|
+
|
|
398
|
+
</div>
|
|
399
|
+
|
|
400
|
+
<!-- BEHAVIOR DETAIL MODAL — display-only drill into one behavior row -->
|
|
401
|
+
<div class="drill-overlay" id="drill">
|
|
402
|
+
<div class="drill-card" role="dialog" aria-modal="true" aria-labelledby="drill-title">
|
|
403
|
+
<button class="drill-close" id="drill-close" type="button" aria-label="Close behavior details">×</button>
|
|
404
|
+
<div id="drill-content"></div>
|
|
405
|
+
</div>
|
|
406
|
+
</div>
|
|
407
|
+
|
|
408
|
+
<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
|
+
window.DATA = __ORANGEPRO_DATA__;
|
|
419
|
+
|
|
420
|
+
/* ===================== render ===================== */
|
|
421
|
+
(function () {
|
|
422
|
+
const D = window.DATA, $ = (s, r=document) => r.querySelector(s), el = (t, c, h) => { const e = document.createElement(t); if (c) e.className = c; if (h != null) e.innerHTML = h; return e; };
|
|
423
|
+
const esc = s => String(s).replace(/[&<>]/g, m => ({ "&":"&","<":"<",">":">" }[m]));
|
|
424
|
+
const tap = (v, fn) => { fn(v); return v; };
|
|
425
|
+
|
|
426
|
+
// header
|
|
427
|
+
$("#repo-name").textContent = D.repo;
|
|
428
|
+
$("#scan-date").textContent = new Date(D.scanned + "T00:00:00").toLocaleDateString();
|
|
429
|
+
$("#framework").textContent = D.framework;
|
|
430
|
+
$("#analysis-kind").textContent = D.analysisKind === "static+dynamic" ? "Static + dynamic proof" : "Static analysis";
|
|
431
|
+
|
|
432
|
+
// KPIs — four honest tiers (Dynamically Proven / Statically Linked / Reachable Untested / No Signal) + the total.
|
|
433
|
+
const kpis = [
|
|
434
|
+
["Behaviors mapped", D.summary.total, "Testable units identified (static)", "total", ""],
|
|
435
|
+
["Dynamically Proven", D.summary.proven, "Real test kills a targeted mutant; the only Proven", "proven", "Mutation killed — a test broke when OrangePro changed this behavior. Highest confidence."],
|
|
436
|
+
["Statically Linked", D.summary.associated, "Static test link, not verified", "assoc", "A test file appears to import or call this behavior. Not dynamically verified."],
|
|
437
|
+
["Reachable Untested", D.summary.reachableUntested, "In a static flow, no test signal", "none", "Appears in a static flow from an entry point, but no test signal was found."],
|
|
438
|
+
["No Signal", D.summary.noSignal, "No test or static-flow signal yet", "none", "No test or static-flow signal found yet."]
|
|
439
|
+
];
|
|
440
|
+
$("#kpis").append(...kpis.map(([l, n, s, tone, tip]) => tap(el("div", "kpi", \`<span class="spark"></span><div class="label">\${l}</div><div class="num">\${Number(n || 0).toLocaleString()}</div><div class="sub">\${s}</div>\`), e => { e.dataset.tone = tone; if (tip) e.title = tip; })));
|
|
441
|
+
|
|
442
|
+
// pipeline strip
|
|
443
|
+
const ps = $("#pipeline");
|
|
444
|
+
ps.append(el("span", "plabel", "Evidence pipeline"));
|
|
445
|
+
D.pipeline.forEach((s, i) => {
|
|
446
|
+
if (i) ps.append(tap(el("span", "arrow", "→"), e => e.style.color = "var(--faint)"));
|
|
447
|
+
ps.append(tap(el("span", "pstage", \`<span class="dot"></span>\${s.label} <span class="pr">\${s.pr}</span>\`), e => e.dataset.on = s.on));
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
const pg = D.proofGuidance || {
|
|
451
|
+
state: D.summary.proven > 0 ? "proven" : "not_started",
|
|
452
|
+
title: D.summary.proven > 0 ? "Dynamically Proven is active" : "0 Dynamically Proven means no dynamic proof has run yet",
|
|
453
|
+
body: D.summary.proven > 0 ? "At least one behavior has a dynamic targeted-proof certificate." : "This report is an analysis pass. Statically Linked signals are useful signal, but Dynamically Proven starts only after a targeted mutant is killed by a real test.",
|
|
454
|
+
action: "Run OrangePro from a coding agent with a model key and follow the returned proof handoff."
|
|
455
|
+
};
|
|
456
|
+
const proof = $("#proof-guidance");
|
|
457
|
+
proof.dataset.state = pg.state;
|
|
458
|
+
proof.innerHTML = \`<span class="mark" aria-hidden="true"></span><div><h2>\${esc(pg.title)}</h2><p>\${esc(pg.body)}</p><p class="action">\${esc(pg.action).replace(/orangepro_prove_loop|opro prove-loop/g, m => \`<code>\${m}</code>\`)}</p></div>\`;
|
|
459
|
+
|
|
460
|
+
// Zero-dynamic-proof explainer — data-gated: D.zeroProofExplainer is present ONLY when proven === 0.
|
|
461
|
+
const zp = D.zeroProofExplainer;
|
|
462
|
+
if (zp) {
|
|
463
|
+
const zpEl = $("#zero-proof");
|
|
464
|
+
zpEl.hidden = false;
|
|
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."
|
|
490
|
+
};
|
|
491
|
+
function tierFor(b) {
|
|
492
|
+
const reach = b.tier === "none" && b.reachable;
|
|
493
|
+
const [bc, blBase] = tierBadge[b.tier] || tierBadge.none;
|
|
494
|
+
return { bc, bl: reach ? "Reachable Untested" : blBase, tip: reach ? tierTip.reachable : tierTip[b.tier] };
|
|
495
|
+
}
|
|
496
|
+
const behMain = $("#beh-list"), behSide = $("#beh-groups");
|
|
497
|
+
let activeGroup = null;
|
|
498
|
+
function renderBeh() {
|
|
499
|
+
behMain.innerHTML = "";
|
|
500
|
+
const rows = D.behaviors.filter(b => !activeGroup || b.group === activeGroup);
|
|
501
|
+
if (!rows.length) { behMain.append(el("div","empty",\`<div class="ic">◇</div><b>No sampled behaviors in this package</b><div>The full list is graph-driven from the live analysis.</div>\`)); return; }
|
|
502
|
+
rows.forEach(b => {
|
|
503
|
+
const { bc, bl, tip } = tierFor(b);
|
|
504
|
+
const row = el("div","beh-row",
|
|
505
|
+
\`<div class="beh-head"><span class="sig">\${esc(b.sig)}</span><span class="badge \${bc}" title="\${esc(tip)}"><span class="d"></span>\${bl}</span></div>
|
|
506
|
+
<div class="filemeta">\${esc(b.file)}</div>
|
|
507
|
+
<div class="desc">\${esc(b.desc)}</div>\`);
|
|
508
|
+
row.setAttribute("role", "button");
|
|
509
|
+
row.tabIndex = 0;
|
|
510
|
+
row.setAttribute("aria-haspopup", "dialog");
|
|
511
|
+
const idx = D.behaviors.indexOf(b);
|
|
512
|
+
row.addEventListener("click", () => showBehaviorDrill(idx, row));
|
|
513
|
+
row.addEventListener("keydown", e => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); showBehaviorDrill(idx, row); } });
|
|
514
|
+
behMain.append(row);
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
D.behaviorGroups.forEach(g => {
|
|
518
|
+
behSide.append(tap(el("button","grp",\`<span>\${esc(g.key)}</span><span class="gc">\${g.count}</span>\`), b => {
|
|
519
|
+
b.setAttribute("aria-pressed", "false");
|
|
520
|
+
b.onclick = () => {
|
|
521
|
+
activeGroup = activeGroup === g.key ? null : g.key;
|
|
522
|
+
[...behSide.children].forEach(c => c.setAttribute("aria-pressed", String(c === b && activeGroup)));
|
|
523
|
+
renderBeh();
|
|
524
|
+
};
|
|
525
|
+
}));
|
|
526
|
+
});
|
|
527
|
+
renderBeh();
|
|
528
|
+
|
|
529
|
+
// ---- behavior detail modal (display-only; every field comes straight from window.DATA) ----
|
|
530
|
+
const drill = $("#drill"), drillContent = $("#drill-content");
|
|
531
|
+
let drillReturnFocus = null;
|
|
532
|
+
function showBehaviorDrill(i, trigger) {
|
|
533
|
+
const b = D.behaviors[i];
|
|
534
|
+
if (!b) return;
|
|
535
|
+
const { bc, bl } = tierFor(b);
|
|
536
|
+
const why = b.sig.includes(".")
|
|
537
|
+
? \`<b>\${esc(b.sig.split(".")[0])}</b> exposes a public method a user or system can trigger, with an observable outcome and side effects: the smallest unit you'd write an acceptance criterion for.\`
|
|
538
|
+
: "A discrete operation with an observable outcome and side effects: the smallest unit you'd write an acceptance criterion for.";
|
|
539
|
+
const action = b.tier === "proven"
|
|
540
|
+
? "Dynamically proven: a real test kills a targeted mutant with the target running unmocked. Keep the test green as the code evolves."
|
|
541
|
+
: b.tier === "assoc"
|
|
542
|
+
? "Statically Linked: a test file imports or calls this behavior, but it is not dynamically verified. Add a targeted test that runs it unmocked and asserts its contract to move it to Dynamically Proven."
|
|
543
|
+
: (b.tier === "none" && b.reachable)
|
|
544
|
+
? "Reachable Untested: this behavior appears in a static flow from an entry point, but no test signal was found. Write an integration or behavior test that calls it and asserts the observable outcome."
|
|
545
|
+
: "No Signal: no test or static-flow signal found yet. Write an integration or behavior test that calls this behavior and asserts the observable outcome.";
|
|
546
|
+
drillContent.innerHTML =
|
|
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>\`));
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// ---- AI-suggested candidate flows (worklist only — excluded from every count above) ----
|
|
600
|
+
const cf = D.candidateFlows;
|
|
601
|
+
if (cf) {
|
|
602
|
+
$("#ai-flows").hidden = false;
|
|
603
|
+
// Artifact-derived values are untrusted: Number-coerce every numeric and
|
|
604
|
+
// esc() every string before it reaches innerHTML.
|
|
605
|
+
const num = v => (Number.isFinite(Number(v)) ? Number(v) : 0);
|
|
606
|
+
$("#ai-flow-headline").innerHTML = \`Model proposed <b>\${num(cf.proposed)}</b> → accepted <b>\${num(cf.accepted)}</b> after closed-set validation.\`;
|
|
607
|
+
const cl = $("#ai-flow-list");
|
|
608
|
+
cf.flows.forEach(f => {
|
|
609
|
+
const steps = f.steps.map((s, i) => {
|
|
610
|
+
const hop = s.hop === "matches_known_edge" ? \`<span class="hop-tag match">matches known edge</span>\`
|
|
611
|
+
: s.hop === "unverified" ? \`<span class="hop-tag unv">unverified</span>\` : "";
|
|
612
|
+
return \`<li class="step cstep"><span class="node"></span>
|
|
613
|
+
<span class="sig"><span class="idx">\${i+1}</span>\${esc(s.sig)} \${hop}</span>
|
|
614
|
+
<div class="desc">\${esc(s.desc)}</div></li>\`;
|
|
615
|
+
}).join("");
|
|
616
|
+
cl.append(el("div","cflow",
|
|
617
|
+
\`<div class="flow-head"><h3>\${esc(f.title)}</h3>
|
|
618
|
+
<div class="flow-tags"><span class="badge b-ai">AI-suggested · \${esc(cf.model)} · \${esc(cf.prompt_version)}</span><span class="badge b-ai">confidence \${Math.round(num(f.confidence)*100)}%</span></div></div>
|
|
619
|
+
<ul class="steps">\${steps}</ul>
|
|
620
|
+
<div class="flow-foot"><span class="why">\${esc(f.rationale || "Candidate chain proposed by AI.")} <b>Not evidence</b> until a real test proves it.</span></div>\`));
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// ---- risks ----
|
|
625
|
+
const rl = $("#risk-list");
|
|
626
|
+
D.risks.forEach(r => {
|
|
627
|
+
const tags = r.tags.map(([t, k]) => \`<span class="badge b-\${k}">\${k==="entry"?"":'<span class="d"></span>'}\${esc(t)}</span>\`).join("");
|
|
628
|
+
rl.append(el("div","risk",
|
|
629
|
+
\`<div class="rk">#\${r.rank}</div>
|
|
630
|
+
<div class="ep"><span class="verb">\${r.verb}</span> <span class="path">\${esc(r.path)}</span></div>
|
|
631
|
+
<div class="rdesc">\${esc(r.desc)}</div>
|
|
632
|
+
<div class="rtags">\${tags}</div>
|
|
633
|
+
<div class="todo"><div class="th">What to do</div><p>\${esc(r.todo)}</p></div>\`));
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
// ---- tabs ----
|
|
637
|
+
const tabs = [...document.querySelectorAll(".tab")];
|
|
638
|
+
tabs.forEach(t => t.addEventListener("click", () => {
|
|
639
|
+
tabs.forEach(x => x.setAttribute("aria-selected", String(x === t)));
|
|
640
|
+
document.querySelectorAll(".panel").forEach(p => p.classList.toggle("active", p.id === "panel-" + t.dataset.tab));
|
|
641
|
+
}));
|
|
642
|
+
// keyboard arrows on tablist
|
|
643
|
+
document.querySelector(".tabs").addEventListener("keydown", e => {
|
|
644
|
+
if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
|
|
645
|
+
const i = tabs.indexOf(document.activeElement); if (i < 0) return;
|
|
646
|
+
const n = (i + (e.key === "ArrowRight" ? 1 : tabs.length - 1)) % tabs.length;
|
|
647
|
+
tabs[n].focus(); tabs[n].click();
|
|
648
|
+
});
|
|
649
|
+
})();
|
|
650
|
+
</script>
|
|
651
|
+
</body>
|
|
652
|
+
</html>
|
|
653
|
+
`;
|
|
654
|
+
/** JSON safe to embed inside an inline <script> (neutralize </script> and JS line separators). */
|
|
655
|
+
function safeJson(value) {
|
|
656
|
+
return JSON.stringify(value)
|
|
657
|
+
.replace(/</g, "\\u003c")
|
|
658
|
+
.replace(/\u2028/g, "\\u2028")
|
|
659
|
+
.replace(/\u2029/g, "\\u2029");
|
|
660
|
+
}
|
|
661
|
+
/** Render the self-contained behavior report HTML from the report data object. */
|
|
662
|
+
export function renderBehaviorReport(data) {
|
|
663
|
+
return TEMPLATE.replace("__ORANGEPRO_DATA__", () => safeJson(data));
|
|
664
|
+
}
|