@pharaoh-so/mcp 0.3.51 → 0.3.53
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/.mcp.json +1 -1
- package/CHANGELOG.md +22 -0
- package/README.md +2 -1
- package/dist/backend.d.ts +31 -0
- package/dist/backend.js +49 -1
- package/dist/helpers.js +1 -1
- package/dist/index.js +16 -3
- package/dist/install-skills.d.ts +3 -3
- package/dist/install-skills.js +36 -6
- package/dist/proxy-server.d.ts +1 -0
- package/dist/proxy-server.js +19 -2
- package/dist/setup.d.ts +19 -0
- package/dist/setup.js +45 -11
- package/package.json +1 -1
- package/trace-app.html +299 -112
- package/commands/sessions.md +0 -101
- package/skills/parallel/SKILL.md +0 -75
- package/skills/sessions/SKILL.md +0 -102
package/trace-app.html
CHANGED
|
@@ -6,60 +6,117 @@
|
|
|
6
6
|
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'none'; img-src 'none'; font-src 'none'; object-src 'none'; frame-src 'none'; base-uri 'none'; form-action 'none'">
|
|
7
7
|
<title>Pharaoh Trace</title>
|
|
8
8
|
<style>
|
|
9
|
-
:root { color-scheme:dark; --
|
|
10
|
-
|
|
9
|
+
:root { color-scheme:dark; --stone:#2a2520; --rule:#3a352e; --track:#342e28; --hover:#332d27; --gold:#d4a843; --papyrus:#e8dcc8; --text:#c4b99a; --muted:#9d927b; --red:#f87171; --amber:#f0c855; --green:#4ade80;
|
|
10
|
+
--s0:#d4a843; --s1:#b08a3a; --s2:#8c6e31; --s3:#6e5728; --s4:#574522; --s-muted:#5f574a; --s-warning:#f0c855; --s-error:#ef4444;
|
|
11
|
+
--sans:"DM Sans",system-ui,-apple-system,"Segoe UI",sans-serif; --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace; }
|
|
12
|
+
:root[data-theme="light"] { color-scheme:light; --stone:#f7f3ea; --rule:#e2d8c4; --track:#ece4d3; --hover:#efe8da; --gold:#946c0f; --papyrus:#2a2520; --text:#4a4235; --muted:#71675a; --red:#b42318; --amber:#9a5b00; --green:#15803d;
|
|
13
|
+
--s0:#b08515; --s1:#c9a24a; --s2:#d9bb72; --s3:#e6cf98; --s4:#efdfb8; --s-muted:#cfc4ad; --s-warning:#e0a526; --s-error:#d93f2f; }
|
|
11
14
|
* { box-sizing:border-box; }
|
|
12
|
-
body { margin:0; min-width:0; }
|
|
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
|
-
|
|
15
|
+
html,body { margin:0; background:transparent; min-width:0; }
|
|
16
|
+
.label,.detail,.badge { overflow-wrap:anywhere; min-width:0; unicode-bidi:plaintext; }
|
|
17
|
+
.mark-s { font:600 12px var(--mono); width:1ch; }
|
|
18
|
+
#trace { background:var(--stone); color:var(--text); border:1px solid var(--rule); border-radius:6px; padding:9px 12px; font:13px/1.45 var(--sans); min-width:0; }
|
|
19
|
+
.head { display:flex; flex-wrap:wrap; align-items:baseline; gap:2px 10px; }
|
|
20
|
+
.mark { color:var(--gold); font-size:14px; line-height:1; }
|
|
21
|
+
.headline { flex:1 1 16em; margin:0; min-width:0; overflow-wrap:anywhere; }
|
|
22
|
+
#kind { color:var(--muted); }
|
|
23
|
+
#target { color:var(--papyrus); font:600 13px/1.45 var(--mono); }
|
|
24
|
+
#target,.legend > span,.rows .label { unicode-bidi:isolate; }
|
|
25
|
+
#title { color:var(--papyrus); font-weight:600; }
|
|
26
|
+
#summary { margin-left:8px; }
|
|
27
|
+
.num { color:var(--gold); font:600 12.5px var(--mono); }
|
|
28
|
+
.meta { margin-left:auto; display:flex; gap:10px; white-space:nowrap; font-size:12px; }
|
|
29
|
+
#verdict[data-state="error"] { color:var(--red); } #verdict[data-state="warning"] { color:var(--amber); } #verdict[data-state="ok"] { color:var(--green); }
|
|
30
|
+
#sha { color:var(--muted); font:11.5px var(--mono); }
|
|
31
|
+
.bar { position:relative; display:flex; gap:2px; height:6px; margin:8px 0 0; border-radius:3px; overflow:hidden; background:var(--track); }
|
|
32
|
+
.bar i,.mini i { display:block; flex:0 1 0; min-width:0; }
|
|
33
|
+
.bar i[data-shown] { min-width:3px; }
|
|
34
|
+
.s0 { background:var(--s0); } .s1 { background:var(--s1); } .s2 { background:var(--s2); } .s3 { background:var(--s3); } .s4 { background:var(--s4); }
|
|
35
|
+
.muted { background:var(--s-muted); } .warning { background:var(--s-warning); } .error { background:var(--s-error); }
|
|
36
|
+
.foot { display:flex; align-items:baseline; gap:4px 14px; margin-top:6px; flex-wrap:wrap; }
|
|
37
|
+
.legend { display:flex; flex-wrap:wrap; gap:2px 14px; margin:0; color:var(--muted); font-size:12px; min-width:0; flex:1 1 auto; }
|
|
38
|
+
#trace[data-reveal] .legend { opacity:0; }
|
|
39
|
+
.legend b { color:var(--text); font:500 11.5px var(--mono); margin-left:4px; }
|
|
40
|
+
.sw { display:inline-block; width:8px; height:8px; border-radius:1px; margin-right:5px; }
|
|
41
|
+
.toggle { margin-left:auto; display:flex; align-items:baseline; gap:4px; border:0; background:none; padding:2px 0; color:var(--gold); font:12px var(--sans); cursor:pointer; white-space:nowrap; }
|
|
42
|
+
.toggle:hover { color:var(--papyrus); }
|
|
43
|
+
.toggle:focus-visible,summary:focus-visible { outline:2px solid var(--gold); outline-offset:2px; border-radius:2px; }
|
|
44
|
+
.chev { display:inline-block; font-size:10px; }
|
|
45
|
+
.toggle[aria-expanded="true"] .chev { transform:rotate(90deg); }
|
|
46
|
+
.caveat { margin:6px 0 0; color:var(--amber); font-size:12px; overflow-wrap:anywhere; }
|
|
47
|
+
.caveat span + span::before { content:" · "; color:var(--muted); }
|
|
48
|
+
.rows { list-style:none; margin:4px 0 0; padding:0; }
|
|
49
|
+
.rows li { display:flex; flex-wrap:wrap; align-items:baseline; gap:0 8px; padding:4px 0 5px; border-top:1px solid var(--rule); min-width:0; }
|
|
50
|
+
.rows li:first-child { border-top:0; }
|
|
51
|
+
.rows .kind { color:var(--muted); font-size:12.5px; }
|
|
52
|
+
.rows .label { color:var(--papyrus); font:12.5px var(--mono); }
|
|
53
|
+
.rows .prose { color:var(--text); font:12.5px var(--sans); }
|
|
54
|
+
.rows .detail { color:var(--text); font-size:12.5px; }
|
|
55
|
+
.rows .end { margin-left:auto; font-size:12px; white-space:nowrap; }
|
|
56
|
+
.rows .more { color:var(--muted); font-size:12px; }
|
|
57
|
+
.rows .mini { display:flex; gap:1px; flex:1 0 100%; height:3px; margin-top:3px; border-radius:2px; overflow:hidden; background:var(--track); }
|
|
58
|
+
[data-state="ok"] > .mark-s { color:var(--green); } [data-state="warning"] > .mark-s, [data-state="pending"] > .mark-s { color:var(--amber); } [data-state="error"] > .mark-s, .end[data-state="error"] { color:var(--red); } .end[data-state="warning"] { color:var(--amber); }
|
|
59
|
+
.drawer { display:grid; grid-template-rows:0fr; }
|
|
60
|
+
.drawer[data-open] { grid-template-rows:1fr; }
|
|
61
|
+
.drawer-inner { overflow:hidden; min-height:0; }
|
|
62
|
+
.drawer[data-open] .drawer-inner { border-top:1px solid var(--rule); margin-top:8px; padding-top:4px; }
|
|
63
|
+
ul.tree,.tree ul { list-style:none; margin:0; padding:0; }
|
|
64
|
+
.tree ul { margin:0 0 4px 6px; padding-left:10px; border-left:1px solid var(--rule); }
|
|
65
|
+
.tree summary,.leaf { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:10px; align-items:baseline; padding:3px 0; font-size:12.5px; min-width:0; }
|
|
66
|
+
.tree summary { list-style:none; cursor:pointer; color:var(--text); }
|
|
67
|
+
.tree summary::-webkit-details-marker { display:none; }
|
|
68
|
+
.tree summary:hover { background:var(--hover); }
|
|
69
|
+
.tree summary .label::before { content:"▸"; display:inline-block; color:var(--gold); font-size:10px; width:12px; }
|
|
70
|
+
.tree details[open] > summary .label::before { transform:rotate(90deg); }
|
|
71
|
+
.tree details[open] > summary .label { color:var(--gold); }
|
|
72
|
+
.leaf .label { color:var(--papyrus); font:12px var(--mono); }
|
|
73
|
+
.tree > li > .leaf { padding-left:12px; }
|
|
74
|
+
.tree > li > .leaf .label { color:var(--text); font:12.5px var(--sans); }
|
|
75
|
+
.tree .label .detail { color:var(--muted); font:11px var(--mono); margin-left:8px; }
|
|
76
|
+
.tree .label .caveat { color:var(--amber); font:11.5px var(--sans); margin:0 0 0 8px; }
|
|
77
|
+
.badge { color:var(--muted); font:11.5px var(--mono); text-align:right; white-space:nowrap; }
|
|
78
|
+
.badge[data-state="ok"] { color:var(--green); } .badge[data-state="warning"],.badge[data-state="pending"] { color:var(--amber); } .badge[data-state="error"] { color:var(--red); }
|
|
79
|
+
.meter { display:block; height:3px; margin-top:3px; border-radius:2px; background:var(--track); max-width:240px; }
|
|
80
|
+
.meter i { display:block; height:100%; border-radius:2px; background:var(--gold); }
|
|
81
|
+
.notes,.provenance { color:var(--muted); font-size:11.5px; margin:6px 0 2px; }
|
|
82
|
+
.notes p { margin:2px 0; overflow-wrap:anywhere; white-space:pre-wrap; }
|
|
83
|
+
#fallback { font:11.5px/1.5 var(--mono); color:var(--text); margin:6px 0 0; white-space:pre-wrap; overflow-wrap:anywhere; unicode-bidi:plaintext; }
|
|
84
|
+
@media(max-width:400px) { #trace { padding:8px 10px; } #summary { margin-left:0; } #summary:not([hidden]) { display:block; } .tree summary,.leaf { grid-template-columns:minmax(0,1fr); gap:0; } .badge { text-align:left; } }
|
|
85
|
+
/* Motion only for people who have not asked to reduce it. */
|
|
86
|
+
@media(prefers-reduced-motion:no-preference) {
|
|
87
|
+
.bar i,.mini i { transition:flex-grow .7s cubic-bezier(.22,.8,.24,1); }
|
|
88
|
+
.legend { transition:opacity .4s ease .2s; }
|
|
89
|
+
.chev { transition:transform .3s ease; }
|
|
90
|
+
.drawer { transition:grid-template-rows .3s ease; }
|
|
91
|
+
.tree summary .label::before { transition:transform .2s ease; }
|
|
92
|
+
#trace[data-state="pending"] .bar::after,#trace[data-state="waiting"] .bar::after { content:""; position:absolute; top:0; bottom:0; left:0; width:35%; background:linear-gradient(90deg,transparent,var(--gold),transparent); opacity:.55; animation:sweep 1.3s ease-in-out infinite; }
|
|
93
|
+
@keyframes sweep { from { transform:translateX(-100%); } to { transform:translateX(290%); } }
|
|
94
|
+
}
|
|
48
95
|
[hidden] { display:none; }
|
|
49
|
-
@media(max-width:400px) { #trace { padding:8px 10px 7px; } summary,.row { grid-template-columns:15px minmax(0,1fr); gap:4px; } .badge { grid-column:2; text-align:left; } .children { margin-left:9px; padding-left:7px; } }
|
|
50
|
-
@media(prefers-reduced-motion:reduce) { #trace[data-pending="true"] #status { animation:none; } }
|
|
51
96
|
</style>
|
|
52
97
|
</head>
|
|
53
98
|
<body>
|
|
54
|
-
<main id="trace" data-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<
|
|
61
|
-
<
|
|
62
|
-
|
|
99
|
+
<main id="trace" data-state="waiting">
|
|
100
|
+
<div class="head">
|
|
101
|
+
<span class="mark" aria-hidden="true">☥</span>
|
|
102
|
+
<p class="headline"><span id="kind"></span><span id="target"></span><span id="title">Pharaoh</span><span id="summary" role="status" aria-live="polite">Waiting for the tool</span></p>
|
|
103
|
+
<span id="meta" class="meta"><span id="verdict" hidden></span><span id="sha" hidden></span></span>
|
|
104
|
+
</div>
|
|
105
|
+
<div id="bar" class="bar" aria-hidden="true"></div>
|
|
106
|
+
<div id="foot" class="foot">
|
|
107
|
+
<p id="legend" class="legend"></p>
|
|
108
|
+
<button id="toggle" class="toggle" type="button" aria-expanded="false" aria-controls="drawer" hidden><span id="toggle-label">Details</span><span class="chev" aria-hidden="true">▸</span></button>
|
|
109
|
+
</div>
|
|
110
|
+
<p id="caveat" class="caveat" hidden></p>
|
|
111
|
+
<ul id="rows" class="rows" hidden></ul>
|
|
112
|
+
<div id="drawer" class="drawer" inert>
|
|
113
|
+
<div class="drawer-inner">
|
|
114
|
+
<ul id="tree" class="tree" aria-label="Result evidence"></ul>
|
|
115
|
+
<pre id="fallback" hidden></pre>
|
|
116
|
+
<div id="notes" class="notes" hidden></div>
|
|
117
|
+
<p id="provenance" class="provenance" hidden></p>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
63
120
|
</main>
|
|
64
121
|
<script>
|
|
65
122
|
(() => {
|
|
@@ -67,11 +124,18 @@
|
|
|
67
124
|
const META_KEY = "so.pharaoh/trace";
|
|
68
125
|
const PROTOCOL_VERSION = "2026-01-26";
|
|
69
126
|
// Kept in parity with TRACE_LIMITS in trace-contract.ts by the resource test.
|
|
70
|
-
const LIMITS = { bytes:65536, nodes:120, depth:6, label:512, detail:2048, badge:128, summary:1024, notes:16, note:1024, repo:256 };
|
|
127
|
+
const LIMITS = { bytes:65536, nodes:120, depth:6, label:512, detail:2048, badge:128, summary:1024, notes:16, note:1024, repo:256, segments:8 };
|
|
71
128
|
const MAX_TEXT = 32768;
|
|
129
|
+
const MAX_ROWS = 5;
|
|
130
|
+
const TONES = ["warning","error","muted"];
|
|
131
|
+
const STATES = ["ok","pending","warning","error"];
|
|
132
|
+
// What each tool is doing while it runs, shown with the argument it targets.
|
|
133
|
+
const VERBS = { get_blast_radius:"Tracing callers of", get_unused_code:"Checking reachability in", search_functions:"Searching for", get_codebase_map:"Mapping", get_module_context:"Reading module", pharaoh_recon:"Running recon on", pharaoh_account:"Checking account" };
|
|
72
134
|
const root = document.getElementById("trace");
|
|
73
|
-
const fields = Object.fromEntries(["
|
|
135
|
+
const fields = Object.fromEntries(["kind","target","title","summary","meta","verdict","sha","bar","caveat","rows","foot","legend","toggle","toggle-label","drawer","tree","fallback","notes","provenance"].map(id => [id,document.getElementById(id)]));
|
|
136
|
+
const compact = new Intl.NumberFormat("en",{notation:"compact",maximumFractionDigits:1});
|
|
74
137
|
const initId = "pharaoh-trace-initialize";
|
|
138
|
+
let tool = "";
|
|
75
139
|
let ready = false;
|
|
76
140
|
let closed = false;
|
|
77
141
|
let cancelled = false;
|
|
@@ -81,37 +145,76 @@
|
|
|
81
145
|
const record = value => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
82
146
|
const string = (value,max) => typeof value === "string" && value.length <= max;
|
|
83
147
|
const send = message => { if (!closed) window.parent.postMessage({jsonrpc:"2.0",...message},"*"); };
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
148
|
+
function element(tag,className,text) {
|
|
149
|
+
const node = document.createElement(tag);
|
|
150
|
+
if (className) node.className = className;
|
|
151
|
+
if (text !== undefined) node.textContent = text;
|
|
152
|
+
return node;
|
|
153
|
+
}
|
|
154
|
+
// Counts read at a glance: digit runs become gold monospace spans, everything else stays text.
|
|
155
|
+
function rich(target,text) {
|
|
156
|
+
target.replaceChildren();
|
|
157
|
+
for (const part of String(text || "").split(/(\b\d[\d,.]*\b)/)) {
|
|
158
|
+
if (part) target.append(/^\d/.test(part) ? element("span","num",part) : part);
|
|
159
|
+
}
|
|
88
160
|
}
|
|
89
161
|
function display(field,text) {
|
|
90
162
|
fields[field].textContent = text || "";
|
|
91
163
|
fields[field].hidden = !text;
|
|
92
164
|
}
|
|
165
|
+
// "Impact: attachTraceView" reads as a muted kind and a monospace target.
|
|
166
|
+
const splitTitle = title => /^([^:]{1,40}): (.+)$/.exec(title);
|
|
167
|
+
function headline(title,summary) {
|
|
168
|
+
const split = splitTitle(title);
|
|
169
|
+
display("kind",split ? `${split[1]} ` : "");
|
|
170
|
+
display("target",split ? split[2] : "");
|
|
171
|
+
display("title",split ? "" : title);
|
|
172
|
+
rich(fields.summary,summary);
|
|
173
|
+
fields.summary.hidden = !summary;
|
|
174
|
+
}
|
|
175
|
+
// The toggle ends the legend line, or the headline when there is no legend.
|
|
176
|
+
function place(withLegend) {
|
|
177
|
+
(withLegend ? fields.foot : fields.meta).append(fields.toggle);
|
|
178
|
+
fields.foot.hidden = !withLegend;
|
|
179
|
+
}
|
|
180
|
+
function setOpen(open) {
|
|
181
|
+
fields.drawer.toggleAttribute("data-open",open);
|
|
182
|
+
fields.drawer.toggleAttribute("inert",!open);
|
|
183
|
+
fields.toggle.setAttribute("aria-expanded",String(open));
|
|
184
|
+
fields["toggle-label"].textContent = open ? "Hide" : "Details";
|
|
185
|
+
}
|
|
186
|
+
function clearResult() {
|
|
187
|
+
setOpen(false);
|
|
188
|
+
for (const field of ["bar","rows","legend","tree","notes"]) fields[field].replaceChildren();
|
|
189
|
+
for (const field of ["verdict","sha","caveat","provenance","fallback"]) display(field,"");
|
|
190
|
+
delete fields.verdict.dataset.state;
|
|
191
|
+
fields.sha.title = "";
|
|
192
|
+
fields.bar.hidden = true;
|
|
193
|
+
fields.rows.hidden = true;
|
|
194
|
+
fields.notes.hidden = true;
|
|
195
|
+
fields.toggle.hidden = true;
|
|
196
|
+
}
|
|
93
197
|
function notes(items) {
|
|
94
|
-
fields.notes.
|
|
95
|
-
for (const text of items) {
|
|
96
|
-
const paragraph = document.createElement("p");
|
|
97
|
-
paragraph.textContent = text;
|
|
98
|
-
fields.notes.append(paragraph);
|
|
99
|
-
}
|
|
198
|
+
for (const text of items) fields.notes.append(element("p","",text));
|
|
100
199
|
fields.notes.hidden = items.length === 0;
|
|
101
200
|
}
|
|
102
|
-
function
|
|
103
|
-
fields.
|
|
104
|
-
fields.
|
|
105
|
-
display("summary","");
|
|
106
|
-
display("provenance","");
|
|
107
|
-
notes([]);
|
|
201
|
+
function caveats(items) {
|
|
202
|
+
fields.caveat.replaceChildren(...items.map(text => element("span","",text)));
|
|
203
|
+
fields.caveat.hidden = items.length === 0;
|
|
108
204
|
}
|
|
109
205
|
function fallback(title,text,notice,state="neutral") {
|
|
110
206
|
clearResult();
|
|
111
|
-
|
|
207
|
+
root.dataset.state = state === "error" ? "error" : "ready";
|
|
208
|
+
const first = text.split("\n").find(line => line.trim()) || "";
|
|
209
|
+
headline(title,first.length > 160 ? `${first.slice(0,159)}…` : first);
|
|
112
210
|
display("fallback",text);
|
|
113
|
-
|
|
211
|
+
if (state === "error" || state === "cancelled") {
|
|
212
|
+
fields.verdict.dataset.state = state === "error" ? "error" : "warning";
|
|
213
|
+
display("verdict",state === "error" ? "Failed" : "Cancelled");
|
|
214
|
+
}
|
|
114
215
|
if (notice) notes([notice]);
|
|
216
|
+
fields.toggle.hidden = !text;
|
|
217
|
+
place(false);
|
|
115
218
|
}
|
|
116
219
|
function readableText(result) {
|
|
117
220
|
if (!record(result) || !Array.isArray(result.content)) return {text:"No readable text was returned. Read the tool response in chat.",omitted:false};
|
|
@@ -129,12 +232,20 @@
|
|
|
129
232
|
}
|
|
130
233
|
return {text:text || "No readable text was returned. Read the tool response in chat.",omitted};
|
|
131
234
|
}
|
|
235
|
+
function validGlance(glance) {
|
|
236
|
+
if (glance === undefined) return true;
|
|
237
|
+
if (!record(glance)) return false;
|
|
238
|
+
if (glance.segments !== undefined && (!Array.isArray(glance.segments) || glance.segments.length > LIMITS.segments || !glance.segments.every(s => record(s) && string(s.label,LIMITS.badge) && Number.isFinite(s.value) && s.value >= 0 && (s.tone === undefined || TONES.includes(s.tone))))) return false;
|
|
239
|
+
if (glance.verdict !== undefined && (!record(glance.verdict) || !string(glance.verdict.label,LIMITS.badge) || (glance.verdict.status !== undefined && !["ok","warning","error"].includes(glance.verdict.status)))) return false;
|
|
240
|
+
return glance.caveat === undefined || string(glance.caveat,LIMITS.note);
|
|
241
|
+
}
|
|
132
242
|
function validView(view) {
|
|
133
243
|
try {
|
|
134
244
|
if (!record(view) || view.version !== 1 || !string(view.title,LIMITS.label) || !view.title.trim() || !Array.isArray(view.nodes)) return false;
|
|
135
245
|
if (new TextEncoder().encode(JSON.stringify(view)).byteLength > LIMITS.bytes) return false;
|
|
136
246
|
if (view.repo !== undefined && !string(view.repo,LIMITS.repo)) return false;
|
|
137
247
|
if (view.summary !== undefined && !string(view.summary,LIMITS.summary)) return false;
|
|
248
|
+
if (!validGlance(view.glance)) return false;
|
|
138
249
|
if (view.notes !== undefined && (!Array.isArray(view.notes) || view.notes.length > LIMITS.notes || !view.notes.every(note => string(note,LIMITS.note)))) return false;
|
|
139
250
|
if (view.freshness !== undefined) {
|
|
140
251
|
const value = view.freshness;
|
|
@@ -145,71 +256,145 @@
|
|
|
145
256
|
if (++count > LIMITS.nodes || depth > LIMITS.depth || !record(node) || !string(node.label,LIMITS.label) || !node.label.trim()) return false;
|
|
146
257
|
if (node.detail !== undefined && !string(node.detail,LIMITS.detail)) return false;
|
|
147
258
|
if (node.badge !== undefined && !string(node.badge,LIMITS.badge)) return false;
|
|
148
|
-
if (node.status !== undefined && !
|
|
259
|
+
if (node.status !== undefined && !STATES.includes(node.status)) return false;
|
|
149
260
|
if (node.meter !== undefined && (!record(node.meter) || !Number.isFinite(node.meter.value) || !Number.isFinite(node.meter.total) || node.meter.total <= 0 || node.meter.value < 0 || node.meter.value > node.meter.total)) return false;
|
|
261
|
+
if (!validGlance(node.glance)) return false;
|
|
150
262
|
return node.children === undefined || (Array.isArray(node.children) && node.children.every(child => visit(child,depth + 1)));
|
|
151
263
|
};
|
|
152
264
|
return view.nodes.every(node => visit(node,1));
|
|
153
265
|
} catch { return false; }
|
|
154
266
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
|
|
267
|
+
// Segments start empty and grow to their share once painted: the one motion on arrival.
|
|
268
|
+
function segments(target,items) {
|
|
269
|
+
const total = items.reduce((sum,item) => sum + item.value,0);
|
|
270
|
+
if (!total) return [];
|
|
271
|
+
let shade = 0;
|
|
272
|
+
return items.filter(item => item.value > 0).map((item,index) => {
|
|
273
|
+
const slice = element("i",item.tone || `s${Math.min(shade++,4)}`);
|
|
274
|
+
slice.style.transitionDelay = `${index * 70}ms`;
|
|
275
|
+
slice.dataset.share = String(item.value / total);
|
|
276
|
+
target.append(slice);
|
|
277
|
+
return slice;
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
function grow(slices) {
|
|
281
|
+
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
282
|
+
for (const slice of slices) { slice.style.flexGrow = slice.dataset.share; slice.dataset.shown = ""; }
|
|
283
|
+
delete root.dataset.reveal;
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
function legend(items) {
|
|
287
|
+
let shade = 0;
|
|
288
|
+
for (const item of items.filter(item => item.value > 0)) {
|
|
289
|
+
const entry = element("span");
|
|
290
|
+
entry.append(element("span",`sw ${item.tone || `s${Math.min(shade++,4)}`}`),item.label,element("b","",compact.format(item.value)));
|
|
291
|
+
fields.legend.append(entry);
|
|
292
|
+
}
|
|
160
293
|
}
|
|
161
|
-
function
|
|
294
|
+
function verdict(target,value) {
|
|
295
|
+
if (!value) return;
|
|
296
|
+
target.textContent = value.label;
|
|
297
|
+
if (value.status) target.dataset.state = value.status;
|
|
298
|
+
target.hidden = false;
|
|
299
|
+
}
|
|
300
|
+
// Sections (recon) and status lines (states, named checks) stay visible; groups open on demand.
|
|
301
|
+
function rowFor(node,slices) {
|
|
302
|
+
const item = element("li");
|
|
303
|
+
if (node.status) {
|
|
304
|
+
item.dataset.state = node.status;
|
|
305
|
+
item.append(element("span","mark-s",{ok:"✓",pending:"…",warning:"!",error:"×"}[node.status]));
|
|
306
|
+
}
|
|
307
|
+
const split = splitTitle(node.label);
|
|
308
|
+
if (split) item.append(element("span","kind",split[1]));
|
|
309
|
+
// Names read in monospace; sentences (state messages) read as prose.
|
|
310
|
+
item.append(element("span",split || !/\s/.test(node.label) ? "label" : "label prose",split ? split[2] : node.label));
|
|
311
|
+
if (node.detail) { const detail = element("span","detail"); rich(detail,node.detail); item.append(detail); }
|
|
312
|
+
const end = element("span","end",node.badge || "");
|
|
313
|
+
end.hidden = !node.badge;
|
|
314
|
+
if (node.glance?.verdict) { end.hidden = true; verdict(end,node.glance.verdict); }
|
|
315
|
+
item.append(end);
|
|
316
|
+
if (node.glance?.caveat) item.append(element("span","detail caveat",node.glance.caveat));
|
|
317
|
+
if (node.glance?.segments?.length) {
|
|
318
|
+
const mini = element("span","mini");
|
|
319
|
+
mini.setAttribute("aria-hidden","true");
|
|
320
|
+
slices.push(...segments(mini,node.glance.segments));
|
|
321
|
+
item.append(mini);
|
|
322
|
+
}
|
|
323
|
+
return item;
|
|
324
|
+
}
|
|
325
|
+
function renderNode(node) {
|
|
162
326
|
const item = element("li");
|
|
163
327
|
const group = Array.isArray(node.children) && node.children.length > 0;
|
|
164
|
-
const
|
|
165
|
-
if (group && open) container.open = true;
|
|
166
|
-
const row = element(group ? "summary" : "div",group ? "" : "row");
|
|
167
|
-
const branch = element("span","branch",group ? "" : last ? "└─" : "├─");
|
|
168
|
-
branch.setAttribute("aria-hidden","true");
|
|
328
|
+
const row = element(group ? "summary" : "div",group ? "" : "leaf");
|
|
169
329
|
const label = element("span","label",node.label);
|
|
170
330
|
if (node.detail) label.append(element("span","detail",node.detail));
|
|
171
|
-
const symbol = {ok:"[ok]",pending:"[...]",warning:"[!]",error:"[!]"}[node.status] || "";
|
|
172
|
-
const badge = element("span","badge",[symbol,node.badge].filter(Boolean).join(" "));
|
|
173
|
-
if (node.status) badge.dataset.state = node.status;
|
|
174
|
-
row.append(branch,label,badge);
|
|
175
|
-
container.append(row);
|
|
176
331
|
if (node.meter) {
|
|
177
|
-
const filled = Math.round(node.meter.value / node.meter.total * 12);
|
|
178
332
|
const meter = element("span","meter");
|
|
179
|
-
meter.
|
|
333
|
+
meter.title = `${node.meter.value} / ${node.meter.total}`;
|
|
334
|
+
const fill = element("i");
|
|
335
|
+
fill.style.width = `${Math.max(1,Math.round(node.meter.value / node.meter.total * 100))}%`;
|
|
336
|
+
meter.append(fill);
|
|
180
337
|
label.append(meter);
|
|
181
338
|
}
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
339
|
+
if (node.glance?.caveat) label.append(element("span","caveat",node.glance.caveat));
|
|
340
|
+
const symbol = {ok:"✓",pending:"…",warning:"!",error:"×"}[node.status] || "";
|
|
341
|
+
const badge = element("span","badge",[symbol,node.badge || node.glance?.verdict?.label].filter(Boolean).join(" "));
|
|
342
|
+
const state = node.status || node.glance?.verdict?.status;
|
|
343
|
+
if (state) badge.dataset.state = state;
|
|
344
|
+
row.append(label,badge);
|
|
345
|
+
if (!group) { item.append(row); return item; }
|
|
346
|
+
const details = element("details");
|
|
347
|
+
const children = element("ul");
|
|
348
|
+
for (const child of node.children) children.append(renderNode(child));
|
|
349
|
+
details.append(row,children);
|
|
350
|
+
item.append(details);
|
|
188
351
|
return item;
|
|
189
352
|
}
|
|
190
353
|
function renderView(view) {
|
|
191
354
|
clearResult();
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
355
|
+
cancelled = false;
|
|
356
|
+
root.dataset.state = "ready";
|
|
357
|
+
root.dataset.reveal = "";
|
|
358
|
+
const glance = view.glance || {};
|
|
359
|
+
headline(view.title,view.summary);
|
|
360
|
+
verdict(fields.verdict,glance.verdict);
|
|
361
|
+
const freshness = view.freshness;
|
|
362
|
+
if (freshness?.mappedCommit) {
|
|
363
|
+
display("sha",`@${/^[0-9a-f]{7,}$/i.test(freshness.mappedCommit) ? freshness.mappedCommit.slice(0,7) : freshness.mappedCommit.slice(0,12)}`);
|
|
364
|
+
fields.sha.title = `Mapped ${freshness.mappedCommit}${freshness.lastAnalyzed ? ` as of ${freshness.lastAnalyzed}` : ""}`;
|
|
199
365
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
366
|
+
if (freshness) display("provenance",[freshness.mappedCommit ? `Mapped ${freshness.mappedCommit}` : "Mapped commit unknown",freshness.lastAnalyzed ? `as of ${freshness.lastAnalyzed}` : "map time unknown",`status: ${freshness.status}`].join(" · "));
|
|
367
|
+
caveats([glance.caveat,freshness?.status === "stale" ? "Map is behind the latest push" : ""].filter(Boolean));
|
|
368
|
+
const slices = segments(fields.bar,glance.segments || []);
|
|
369
|
+
fields.bar.hidden = slices.length === 0;
|
|
370
|
+
legend(glance.segments || []);
|
|
371
|
+
// Up to MAX_ROWS rows, warnings and verdicts first, shown in their original order.
|
|
372
|
+
const candidates = view.nodes.filter(node => node.glance || node.status);
|
|
373
|
+
const urgent = node => node.status === "error" || node.status === "warning" || node.glance?.verdict || node.glance?.caveat;
|
|
374
|
+
const chosen = new Set([...new Set([...candidates.filter(urgent),...candidates])].slice(0,MAX_ROWS));
|
|
375
|
+
const visible = candidates.filter(node => chosen.has(node));
|
|
376
|
+
for (const node of visible) fields.rows.append(rowFor(node,slices));
|
|
377
|
+
if (candidates.length > visible.length) fields.rows.append(element("li","more",`+${candidates.length - visible.length} more in Details`));
|
|
378
|
+
fields.rows.hidden = visible.length === 0;
|
|
379
|
+
for (const node of view.nodes) if (!visible.includes(node) || node.children?.length) fields.tree.append(renderNode(node));
|
|
380
|
+
// One click on Details shows evidence: the first group is already open inside the drawer.
|
|
381
|
+
fields.tree.querySelector("details")?.setAttribute("open","");
|
|
203
382
|
notes(view.notes || []);
|
|
204
|
-
|
|
383
|
+
fields.toggle.hidden = !fields.tree.childElementCount && fields.notes.hidden && fields.provenance.hidden;
|
|
384
|
+
place(fields.legend.childElementCount > 0);
|
|
385
|
+
grow(slices);
|
|
205
386
|
}
|
|
206
387
|
function input(params) {
|
|
207
388
|
cancelled = false;
|
|
208
389
|
clearResult();
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
390
|
+
root.dataset.state = "pending";
|
|
391
|
+
const args = record(params.arguments) ? params.arguments : {};
|
|
392
|
+
const target = ["entity","query","module","repo"].map(key => args[key]).find(value => string(value,LIMITS.repo) && value.trim());
|
|
393
|
+
const verb = Object.hasOwn(VERBS,tool) ? VERBS[tool] : "Querying the Pharaoh graph";
|
|
394
|
+
headline("","");
|
|
395
|
+
display("kind",target ? `${verb} ` : `${verb}…`);
|
|
396
|
+
display("target",target ? `${target}…` : "");
|
|
397
|
+
fields.bar.hidden = false;
|
|
213
398
|
}
|
|
214
399
|
function result(params) {
|
|
215
400
|
if (cancelled) return;
|
|
@@ -226,7 +411,7 @@
|
|
|
226
411
|
function context(params) {
|
|
227
412
|
if (params.theme === "dark" || params.theme === "light") document.documentElement.dataset.theme = params.theme;
|
|
228
413
|
const name = params.toolInfo?.tool?.name;
|
|
229
|
-
if (string(name,128))
|
|
414
|
+
if (string(name,128)) tool = name;
|
|
230
415
|
}
|
|
231
416
|
function size() {
|
|
232
417
|
if (!ready || closed || resizeFrame) return;
|
|
@@ -244,7 +429,9 @@
|
|
|
244
429
|
const observer = typeof ResizeObserver === "function" ? new ResizeObserver(size) : null;
|
|
245
430
|
observer?.observe(root);
|
|
246
431
|
root.addEventListener("toggle",size,true);
|
|
432
|
+
root.addEventListener("transitionend",size);
|
|
247
433
|
window.addEventListener("resize",size);
|
|
434
|
+
fields.toggle.addEventListener("click",() => { setOpen(fields.toggle.getAttribute("aria-expanded") !== "true"); size(); });
|
|
248
435
|
function receive(event) {
|
|
249
436
|
if (closed || event.source !== window.parent || !record(event.data) || event.data.jsonrpc !== "2.0") return;
|
|
250
437
|
const message = event.data;
|
|
@@ -271,7 +458,7 @@
|
|
|
271
458
|
clearTimeout(initTimeout);
|
|
272
459
|
cancelAnimationFrame(resizeFrame);
|
|
273
460
|
observer?.disconnect();
|
|
274
|
-
root.dataset.
|
|
461
|
+
root.dataset.state = "ready";
|
|
275
462
|
window.removeEventListener("message",receive);
|
|
276
463
|
} else send({id:message.id,error:{code:-32601,message:"Unsupported request"}});
|
|
277
464
|
return;
|
|
@@ -289,7 +476,7 @@
|
|
|
289
476
|
const initTimeout = setTimeout(() => {
|
|
290
477
|
if (!ready && !closed) fallback("Text response available","Read the tool response in chat.","Trace has not received a host connection.");
|
|
291
478
|
},10000);
|
|
292
|
-
send({id:initId,method:"ui/initialize",params:{appInfo:{name:"Pharaoh Trace",version:"1.
|
|
479
|
+
send({id:initId,method:"ui/initialize",params:{appInfo:{name:"Pharaoh Trace",version:"1.2.0"},appCapabilities:{availableDisplayModes:["inline"]},protocolVersion:PROTOCOL_VERSION}});
|
|
293
480
|
})();
|
|
294
481
|
</script>
|
|
295
482
|
</body>
|