ziprin-context-optimizer 8.0.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.
Files changed (56) hide show
  1. package/README.md +48 -0
  2. package/bin/ziprin-context-mcp.js +34 -0
  3. package/bin/ziprin-context-setup.js +10 -0
  4. package/install-source.json +5 -0
  5. package/media/context-activity.svg +7 -0
  6. package/package.json +154 -0
  7. package/releases/ziprin-context-optimizer-8.0.0.vsix +0 -0
  8. package/scripts/install.mjs +204 -0
  9. package/scripts/package-vsix.mjs +38 -0
  10. package/scripts/paths.mjs +40 -0
  11. package/scripts/publish.mjs +37 -0
  12. package/scripts/update.mjs +101 -0
  13. package/src/adaptive-budget.js +46 -0
  14. package/src/analyzer.js +456 -0
  15. package/src/audit-history.js +137 -0
  16. package/src/bm25.js +132 -0
  17. package/src/collector.js +343 -0
  18. package/src/compression.js +67 -0
  19. package/src/config.js +28 -0
  20. package/src/context-memory.js +151 -0
  21. package/src/context-profiles.js +124 -0
  22. package/src/dependency-graph.js +90 -0
  23. package/src/estimate.js +109 -0
  24. package/src/eval-harness.js +76 -0
  25. package/src/extension.js +322 -0
  26. package/src/firewall.js +35 -0
  27. package/src/fts-index.js +319 -0
  28. package/src/gateway-api.js +404 -0
  29. package/src/git-recency.js +43 -0
  30. package/src/glob.js +42 -0
  31. package/src/identifier.js +37 -0
  32. package/src/inspector-panel.js +470 -0
  33. package/src/language.js +157 -0
  34. package/src/mcp-event-stream.js +179 -0
  35. package/src/mcp-health-cli.js +34 -0
  36. package/src/mcp-lifecycle-manager.js +427 -0
  37. package/src/mcp-server.js +372 -0
  38. package/src/mmr.js +57 -0
  39. package/src/pagerank.js +176 -0
  40. package/src/profiles.js +74 -0
  41. package/src/pruner.js +130 -0
  42. package/src/quality-guard.js +122 -0
  43. package/src/query-rewrite.js +32 -0
  44. package/src/relevance-scorer.js +197 -0
  45. package/src/repo-map.js +134 -0
  46. package/src/retrieve.js +350 -0
  47. package/src/serena.js +126 -0
  48. package/src/session-ledger.js +83 -0
  49. package/src/session-store.js +78 -0
  50. package/src/skeleton.js +129 -0
  51. package/src/slice-pack.js +70 -0
  52. package/src/supervisor.js +113 -0
  53. package/src/task-analyzer.js +189 -0
  54. package/src/tool-router.js +92 -0
  55. package/src/version.js +5 -0
  56. package/templates/ziprin-context-mcp.mjs +68 -0
@@ -0,0 +1,470 @@
1
+ 'use strict';
2
+
3
+ function nonce() {
4
+ return Buffer.from(String(Date.now()) + Math.random()).toString('base64').slice(0, 24);
5
+ }
6
+
7
+ function inspectorHtml(n) {
8
+ return `<!DOCTYPE html>
9
+ <html lang="en">
10
+ <head>
11
+ <meta charset="UTF-8"/>
12
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'nonce-${n}'; script-src 'nonce-${n}';"/>
13
+ <style nonce="${n}">
14
+ :root { color-scheme: dark; }
15
+ * { box-sizing: border-box; }
16
+ body {
17
+ margin: 0;
18
+ font: 11px/1.4 var(--vscode-font-family, ui-sans-serif, system-ui);
19
+ color: var(--vscode-foreground, #ccc);
20
+ background: var(--vscode-sideBar-background, #1e1e1e);
21
+ }
22
+ .shell { display: flex; flex-direction: column; min-height: 100vh; }
23
+ .status-bar {
24
+ display: flex; align-items: center; gap: 8px;
25
+ padding: 6px 10px; border-bottom: 1px solid var(--vscode-panel-border, #333);
26
+ background: var(--vscode-sideBarSectionHeader-background, #252526);
27
+ position: sticky; top: 0; z-index: 2;
28
+ }
29
+ .dot {
30
+ width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
31
+ background: #666;
32
+ }
33
+ .dot.on { background: #3fb950; box-shadow: 0 0 0 2px rgba(63,185,80,.2); }
34
+ .dot.warn { background: #d29922; }
35
+ .dot.off { background: #f85149; }
36
+ .status-label { flex: 1; color: var(--vscode-descriptionForeground, #888); }
37
+ .status-label b { color: var(--vscode-foreground, #ddd); font-weight: 600; }
38
+ .banner {
39
+ margin: 8px 10px 0; padding: 6px 8px; border-radius: 3px;
40
+ border: 1px solid var(--vscode-panel-border, #333);
41
+ background: rgba(56,139,253,.08); color: var(--vscode-descriptionForeground, #9ab);
42
+ font-size: 10px;
43
+ }
44
+ .sections { padding: 6px 0 12px; }
45
+ .acc {
46
+ border-bottom: 1px solid var(--vscode-panel-border, #2a2a2a);
47
+ }
48
+ .acc-h {
49
+ display: flex; align-items: center; gap: 6px;
50
+ width: 100%; padding: 8px 10px; margin: 0;
51
+ background: transparent; border: 0; cursor: pointer;
52
+ color: inherit; font: inherit; text-align: left;
53
+ }
54
+ .acc-h:hover { background: var(--vscode-list-hoverBackground, #2a2a2a); }
55
+ .acc.open .acc-h { background: var(--vscode-list-inactiveSelectionBackground, #2a2d2e); }
56
+ .chev {
57
+ width: 0; height: 0; border: 4px solid transparent;
58
+ border-left-color: var(--vscode-descriptionForeground, #888);
59
+ margin-left: 2px; transition: transform .12s ease;
60
+ }
61
+ .acc.open .chev { transform: rotate(90deg); }
62
+ .acc-title {
63
+ flex: 1; font-size: 10px; font-weight: 600;
64
+ letter-spacing: .04em; text-transform: uppercase;
65
+ color: var(--vscode-sideBarSectionHeader-foreground, #bbb);
66
+ }
67
+ .badge {
68
+ font-size: 10px; padding: 1px 6px; border-radius: 8px;
69
+ background: var(--vscode-badge-background, #4d4d4d);
70
+ color: var(--vscode-badge-foreground, #fff);
71
+ }
72
+ .acc-b { display: none; padding: 0 10px 10px; }
73
+ .acc.open .acc-b { display: block; }
74
+ .muted { color: var(--vscode-descriptionForeground, #888); }
75
+ .empty {
76
+ padding: 14px 8px; text-align: center;
77
+ color: var(--vscode-descriptionForeground, #888); font-size: 11px;
78
+ }
79
+ .kv {
80
+ display: grid; grid-template-columns: 88px 1fr; gap: 3px 8px;
81
+ padding: 4px 0;
82
+ }
83
+ .kv .k { color: var(--vscode-descriptionForeground, #888); }
84
+ .kv .v { word-break: break-word; }
85
+ .ok { color: #3fb950; }
86
+ .weak { color: #d29922; }
87
+ .item, .card {
88
+ padding: 6px 8px; margin-bottom: 4px; border-radius: 3px;
89
+ border: 1px solid transparent; cursor: pointer;
90
+ }
91
+ .item:hover, .card:hover { background: var(--vscode-list-hoverBackground, #2a2a2a); }
92
+ .item.on, .card.on {
93
+ background: var(--vscode-list-activeSelectionBackground, #094771);
94
+ border-color: var(--vscode-focusBorder, #007fd4);
95
+ }
96
+ .card .t { font-weight: 600; margin-bottom: 2px; }
97
+ .card .meta { display: flex; flex-wrap: wrap; gap: 6px; font-size: 10px; }
98
+ .pill {
99
+ display: inline-block; padding: 0 5px; border-radius: 3px;
100
+ background: rgba(255,255,255,.06); color: var(--vscode-descriptionForeground, #aaa);
101
+ }
102
+ .pill.ok { color: #3fb950; background: rgba(63,185,80,.12); }
103
+ .pill.weak { color: #d29922; background: rgba(210,153,34,.12); }
104
+ .row { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
105
+ button.act {
106
+ font: inherit; color: inherit; cursor: pointer;
107
+ background: var(--vscode-button-secondaryBackground, #3a3a3a);
108
+ border: 1px solid var(--vscode-panel-border, #444);
109
+ border-radius: 3px; padding: 4px 8px;
110
+ }
111
+ button.act:hover { background: var(--vscode-button-secondaryHoverBackground, #454545); }
112
+ .tabs { display: flex; flex-wrap: wrap; gap: 3px; margin: 8px 0 6px; }
113
+ .tabs button {
114
+ font: inherit; color: inherit; cursor: pointer;
115
+ background: transparent; border: 1px solid var(--vscode-panel-border, #444);
116
+ border-radius: 3px; padding: 2px 6px; font-size: 10px;
117
+ }
118
+ .tabs button.on {
119
+ background: var(--vscode-button-background, #0e639c);
120
+ border-color: transparent;
121
+ }
122
+ pre, .panel-body {
123
+ white-space: pre-wrap; word-break: break-word;
124
+ background: var(--vscode-textCodeBlock-background, #111);
125
+ padding: 6px; border-radius: 3px; max-height: 40vh; overflow: auto;
126
+ margin: 0; font-size: 10px;
127
+ }
128
+ ul.filelist { margin: 0; padding-left: 14px; max-height: 40vh; overflow: auto; }
129
+ ul.filelist li { margin-bottom: 3px; }
130
+ </style>
131
+ </head>
132
+ <body>
133
+ <div class="shell">
134
+ <div class="status-bar">
135
+ <span class="dot" id="connDot"></span>
136
+ <span class="status-label" id="connLabel">Connecting…</span>
137
+ </div>
138
+ <div class="banner">Observer only — <b>Ziprin Context Inspector</b>. Chat stays in Cursor. MCP publishes live events; this panel never runs optimize.</div>
139
+ <div class="sections">
140
+ <section class="acc" data-sec="services">
141
+ <button type="button" class="acc-h" data-toggle="services">
142
+ <span class="chev"></span>
143
+ <span class="acc-title">MCP Services</span>
144
+ <span class="badge" id="servicesBadge">—</span>
145
+ </button>
146
+ <div class="acc-b" id="services"></div>
147
+ </section>
148
+ <section class="acc" data-sec="profiles">
149
+ <button type="button" class="acc-h" data-toggle="profiles">
150
+ <span class="chev"></span>
151
+ <span class="acc-title">Profiles</span>
152
+ <span class="badge" id="profilesBadge">0</span>
153
+ </button>
154
+ <div class="acc-b" id="profiles"></div>
155
+ </section>
156
+ <section class="acc" data-sec="live">
157
+ <button type="button" class="acc-h" data-toggle="live">
158
+ <span class="chev"></span>
159
+ <span class="acc-title">Live Context</span>
160
+ <span class="badge" id="liveBadge">idle</span>
161
+ </button>
162
+ <div class="acc-b" id="live"></div>
163
+ </section>
164
+ <section class="acc" data-sec="history">
165
+ <button type="button" class="acc-h" data-toggle="history">
166
+ <span class="chev"></span>
167
+ <span class="acc-title">MCP History</span>
168
+ <span class="badge" id="historyBadge">0</span>
169
+ </button>
170
+ <div class="acc-b">
171
+ <div id="history"></div>
172
+ <div id="detail"></div>
173
+ <div class="row"><button type="button" class="act" id="clear">Clear history</button></div>
174
+ </div>
175
+ </section>
176
+ </div>
177
+ </div>
178
+ <script nonce="${n}">
179
+ const vscode = acquireVsCodeApi();
180
+ let model = {
181
+ profiles: [], history: [], profileId: 'standard',
182
+ latestSession: null, session: null, connection: null, mcpServices: null
183
+ };
184
+ let openSec = null;
185
+ let tab = 'Selected';
186
+ const DETAIL_TABS = ['Selected','Removed','Tokens','Quality','Prompt'];
187
+
188
+ function el(id){ return document.getElementById(id); }
189
+ function esc(s){
190
+ return String(s ?? '').replace(/[&<>"']/g, c =>
191
+ ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
192
+ }
193
+ function reduction(s){
194
+ if (!s) return null;
195
+ if (s.reductionPct != null) return s.reductionPct;
196
+ if (s.tokenAnalysis && s.tokenAnalysis.reductionPct != null) return s.tokenAnalysis.reductionPct;
197
+ const b = Number(s.tokensBeforePrune)||0, a = Number(s.tokensAfterPrune)||0;
198
+ return b > 0 ? Math.round((1 - a / b) * 100) : null;
199
+ }
200
+ function statusOf(s){
201
+ if (!s) return '—';
202
+ if (s.status) return s.status;
203
+ if (s.qualityReport && s.qualityReport.ok === false) return 'weak';
204
+ return 'ok';
205
+ }
206
+ function fmtTime(ts){
207
+ if (!ts) return '—';
208
+ return String(ts).replace('T',' ').slice(0,19);
209
+ }
210
+ function setOpen(sec){
211
+ openSec = openSec === sec ? null : sec;
212
+ document.querySelectorAll('.acc').forEach(n => {
213
+ n.classList.toggle('open', n.dataset.sec === openSec);
214
+ });
215
+ }
216
+
217
+ window.addEventListener('message', (e) => {
218
+ const m = e.data || {};
219
+ if (m.type === 'model') {
220
+ model = Object.assign(model, m);
221
+ if (!model.session && m.latestSession) model.session = m.latestSession;
222
+ if (m.latestSession && (!model.session || model.session.id === m.latestSession.id)) {
223
+ model.session = m.latestSession;
224
+ }
225
+ render();
226
+ }
227
+ if (m.type === 'session') {
228
+ model.session = m.session;
229
+ openSec = 'live';
230
+ tab = 'Selected';
231
+ render();
232
+ }
233
+ });
234
+
235
+ document.querySelectorAll('[data-toggle]').forEach(btn => {
236
+ btn.onclick = () => { setOpen(btn.dataset.toggle); };
237
+ });
238
+ el('clear').onclick = () => vscode.postMessage({ type: 'clear' });
239
+
240
+ function renderConnection(){
241
+ const c = model.connection || {};
242
+ const services = model.mcpServices || {};
243
+ const overall = services.overall || c.state || 'idle';
244
+ const dot = el('connDot');
245
+ const ok = overall === 'connected' || overall === 'watching' || overall === 'observing';
246
+ const recovering = overall === 'degraded' || overall === 'restarting' || overall === 'starting';
247
+ dot.className = 'dot ' + (ok ? 'on' : recovering ? 'warn' : overall === 'no-workspace' || overall === 'failed' ? 'off' : 'warn');
248
+ if (services.services && services.services.length) {
249
+ const failed = services.services.filter(s => s.state === 'failed').length;
250
+ const label = ok ? 'MCP Connected' : recovering ? 'MCP Recovering' : 'MCP Failed';
251
+ el('connLabel').innerHTML = '<b>'+esc(label)+'</b>' +
252
+ (failed ? ' · '+failed+' issue(s)' : '') +
253
+ (services.checkedAt ? ' · '+esc(fmtTime(services.checkedAt)) : '');
254
+ } else {
255
+ el('connLabel').innerHTML = overall === 'watching' || overall === 'observing'
256
+ ? '<b>Observing</b> · live event stream' + (c.lastSessionAt ? ' · last ' + esc(fmtTime(c.lastSessionAt)) : '')
257
+ : overall === 'no-workspace'
258
+ ? '<b>No workspace</b> · open a folder'
259
+ : '<b>Idle</b> · waiting for MCP events';
260
+ }
261
+ }
262
+
263
+ function renderServices(){
264
+ const svc = model.mcpServices || {};
265
+ const list = svc.services || [];
266
+ el('servicesBadge').textContent = svc.overall || '—';
267
+ if (!list.length) {
268
+ el('services').innerHTML = '<div class="empty">Waiting for MCP event stream (.cursor/context-events.jsonl)</div>';
269
+ return;
270
+ }
271
+ el('services').innerHTML = list.map(s => {
272
+ const st = s.state || 'unknown';
273
+ const pill = st === 'connected' ? 'ok' : (st === 'failed' ? 'weak' : 'weak');
274
+ return '<div class="card">'+
275
+ '<div class="t">'+esc(s.name)+'</div>'+
276
+ '<div class="meta">'+
277
+ '<span class="pill '+pill+'">'+esc(s.label || st)+'</span>'+
278
+ (s.toolsCount!=null?'<span class="pill">tools '+esc(s.toolsCount)+'</span>':'')+
279
+ (s.lastHeartbeat?'<span class="pill">hb '+esc(fmtTime(s.lastHeartbeat))+'</span>':'')+
280
+ (s.lastAnalysis?'<span class="pill">analysis '+esc(fmtTime(s.lastAnalysis))+'</span>':'')+
281
+ (s.lastRestart?'<span class="pill">retry '+esc(fmtTime(s.lastRestart))+'</span>':'')+
282
+ '</div>'+
283
+ ((s.lastError||s.error)?'<div class="muted" style="margin-top:4px">'+esc(s.lastError||s.error)+'</div>':'')+
284
+ '</div>';
285
+ }).join('') +
286
+ (svc.mcpConfigOk===false?'<div class="empty">mcp.json invalid — fix portable paths</div>':'');
287
+ }
288
+
289
+ function renderProfiles(){
290
+ const list = model.profiles || [];
291
+ el('profilesBadge').textContent = String(list.length);
292
+ el('profiles').innerHTML = list.map(p =>
293
+ '<div class="item'+(p.id===model.profileId?' on':'')+'" data-p="'+esc(p.id)+'">'+
294
+ '<b>'+esc(p.label)+'</b>'+
295
+ '<div class="muted">'+esc(p.description)+'</div>'+
296
+ '</div>'
297
+ ).join('') || '<div class="empty">No profiles</div>';
298
+ el('profiles').querySelectorAll('.item').forEach(n => {
299
+ n.onclick = () => vscode.postMessage({ type: 'setProfile', id: n.dataset.p });
300
+ });
301
+ }
302
+
303
+ function liveSource(){
304
+ return model.latestSession || model.session || null;
305
+ }
306
+
307
+ function renderLive(){
308
+ const s = liveSource();
309
+ el('liveBadge').textContent = s ? 'active' : 'idle';
310
+ if (!s) {
311
+ el('live').innerHTML = '<div class="empty">Waiting for Cursor Agent optimization session</div>';
312
+ return;
313
+ }
314
+ const red = reduction(s);
315
+ const st = statusOf(s);
316
+ const dr = s.decisionReport || {};
317
+ el('live').innerHTML =
318
+ '<div class="kv">'+
319
+ '<div class="k">Task</div><div class="v">'+esc(s.task||s.prompt||'')+'</div>'+
320
+ '<div class="k">Profile</div><div class="v">'+esc(s.profileId||'—')+'</div>'+
321
+ '<div class="k">Intent</div><div class="v">'+esc((s.intent&&s.intent.action)||'—')+' / '+esc((s.intent&&s.intent.scope)||'—')+'</div>'+
322
+ '<div class="k">Selected</div><div class="v">'+(s.filesIncluded||[]).length+' files</div>'+
323
+ '<div class="k">Removed</div><div class="v">'+(s.removedItems||[]).length+' items</div>'+
324
+ '<div class="k">Token before</div><div class="v">'+esc(s.tokensBeforePrune)+'</div>'+
325
+ '<div class="k">Token after</div><div class="v">'+esc(s.tokensAfterPrune)+'</div>'+
326
+ '<div class="k">Reduction</div><div class="v">'+(red!=null?'−'+esc(red)+'%':'—')+'</div>'+
327
+ '<div class="k">Quality</div><div class="v '+(st==='ok'?'ok':'weak')+'">'+esc(st)+'</div>'+
328
+ (dr.topSelected&&dr.topSelected.length?'<div class="k">Top pick</div><div class="v muted">'+esc(dr.topSelected[0].path)+'</div>':'')+
329
+ '</div>';
330
+ }
331
+
332
+ function renderHistory(){
333
+ const hist = model.history || [];
334
+ el('historyBadge').textContent = String(hist.length);
335
+ if (!hist.length) {
336
+ el('history').innerHTML = '<div class="empty">No MCP sessions yet. Use Cursor Chat — Agent will call ziprin_optimize_context.</div>';
337
+ } else {
338
+ const selId = model.session && model.session.id;
339
+ el('history').innerHTML = hist.map(h => {
340
+ const red = reduction(h);
341
+ const st = statusOf(h);
342
+ const summary = (h.decisionSummary || h.task || h.promptPreview || 'session').slice(0, 72);
343
+ return '<div class="card'+(h.id===selId?' on':'')+'" data-id="'+esc(h.id)+'">'+
344
+ '<div class="t">'+esc(summary)+'</div>'+
345
+ '<div class="meta">'+
346
+ '<span class="pill">'+esc(fmtTime(h.timestamp))+'</span>'+
347
+ '<span class="pill">'+esc(h.profileId||'—')+'</span>'+
348
+ '<span class="pill">'+(red!=null?'−'+esc(red)+'%':esc(h.tokensBeforePrune)+'→'+esc(h.tokensAfterPrune))+'</span>'+
349
+ '<span class="pill '+esc(st)+'">'+esc(st)+'</span>'+
350
+ '</div>'+
351
+ '</div>';
352
+ }).join('');
353
+ el('history').querySelectorAll('.card').forEach(n => {
354
+ n.onclick = () => {
355
+ openSec = 'history';
356
+ vscode.postMessage({ type: 'openSession', id: n.dataset.id });
357
+ };
358
+ });
359
+ }
360
+ renderDetail();
361
+ }
362
+
363
+ function renderDetail(){
364
+ const cur = model.session;
365
+ const box = el('detail');
366
+ if (!cur) { box.innerHTML = ''; return; }
367
+ box.innerHTML =
368
+ '<div class="kv" style="margin-top:8px;border-top:1px solid #333;padding-top:8px">'+
369
+ '<div class="k">Session</div><div class="v">'+esc(fmtTime(cur.timestamp))+'</div>'+
370
+ '<div class="k">Class</div><div class="v">'+esc(cur.taskLabel||cur.taskClassification||'')+'</div>'+
371
+ '<div class="k">Files</div><div class="v">'+(cur.filesIncluded||[]).length+' in / '+(cur.removedItems||[]).length+' out</div>'+
372
+ '</div>'+
373
+ '<div class="tabs" id="tabs"></div>'+
374
+ '<div id="panel"></div>';
375
+ const tabs = el('tabs');
376
+ tabs.innerHTML = DETAIL_TABS.map(t =>
377
+ '<button type="button" class="'+(t===tab?'on':'')+'" data-t="'+t+'">'+t+'</button>'
378
+ ).join('');
379
+ tabs.querySelectorAll('button').forEach(b => {
380
+ b.onclick = () => { tab = b.dataset.t; renderDetail(); };
381
+ });
382
+ const p = el('panel');
383
+ if (tab === 'Selected') {
384
+ p.innerHTML = '<ul class="filelist">'+(cur.filesIncluded||[]).map(f => '<li>'+esc(f)+'</li>').join('')+'</ul>';
385
+ } else if (tab === 'Removed') {
386
+ p.innerHTML = '<ul class="filelist">'+(cur.removedItems||[]).map(r =>
387
+ '<li><b>'+esc(r.file)+'</b><div class="muted">'+esc(r.reason)+' · −'+esc(r.tokenSaving)+' tok</div></li>'
388
+ ).join('')+'</ul>';
389
+ } else if (tab === 'Tokens') {
390
+ p.innerHTML = '<pre>'+esc(JSON.stringify(cur.tokenAnalysis||{
391
+ before: cur.tokensBeforePrune, after: cur.tokensAfterPrune, reductionPct: reduction(cur)
392
+ }, null, 2))+'</pre>';
393
+ } else if (tab === 'Quality') {
394
+ p.innerHTML = '<pre>'+esc(JSON.stringify(cur.qualityReport||{note:cur.result}, null, 2))+
395
+ '\\n\\n'+esc(cur.result||'')+'</pre>';
396
+ } else {
397
+ p.innerHTML = '<pre>'+esc(cur.prompt||cur.task||'')+'</pre>';
398
+ }
399
+ }
400
+
401
+ function render(){
402
+ document.querySelectorAll('.acc').forEach(n => {
403
+ n.classList.toggle('open', n.dataset.sec === openSec);
404
+ });
405
+ renderConnection();
406
+ renderServices();
407
+ renderProfiles();
408
+ renderLive();
409
+ renderHistory();
410
+ }
411
+
412
+ render();
413
+ vscode.postMessage({ type: 'ready' });
414
+ </script>
415
+ </body>
416
+ </html>`;
417
+ }
418
+
419
+ class InspectorViewProvider {
420
+ constructor(handlers) {
421
+ this.handlers = handlers;
422
+ this._view = null;
423
+ this._model = {
424
+ type: 'model',
425
+ profiles: [],
426
+ history: [],
427
+ profileId: 'standard',
428
+ latestSession: null,
429
+ session: null,
430
+ connection: { state: 'idle' },
431
+ mcpServices: null,
432
+ };
433
+ }
434
+
435
+ resolveWebviewView(webviewView) {
436
+ this._view = webviewView;
437
+ const n = nonce();
438
+ webviewView.webview.options = { enableScripts: true };
439
+ webviewView.webview.html = inspectorHtml(n);
440
+ webviewView.webview.onDidReceiveMessage((msg) => {
441
+ if (msg.type === 'ready') {
442
+ this.push();
443
+ return;
444
+ }
445
+ if (!this.handlers) return;
446
+ if (msg.type === 'setProfile') this.handlers.setProfile(msg.id);
447
+ if (msg.type === 'openSession') this.handlers.openSession(msg.id);
448
+ if (msg.type === 'clear') this.handlers.clear();
449
+ });
450
+ this.push();
451
+ }
452
+
453
+ setModel(partial) {
454
+ Object.assign(this._model, partial);
455
+ this.push();
456
+ }
457
+
458
+ showSession(session) {
459
+ if (!this._view) return;
460
+ this._model.session = session;
461
+ this._view.webview.postMessage({ type: 'session', session });
462
+ }
463
+
464
+ push() {
465
+ if (!this._view) return;
466
+ this._view.webview.postMessage(this._model);
467
+ }
468
+ }
469
+
470
+ module.exports = { InspectorViewProvider, inspectorHtml };
@@ -0,0 +1,157 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Bilingual prompt understanding for Ziprin (FA + EN).
5
+ * Never drop Unicode. Expand Persian task words onto English repo paths.
6
+ * v8: identifier split + query rewrite hooks.
7
+ */
8
+
9
+ const { splitIdentifier, splitMany } = require('./identifier');
10
+ const { rewriteQuery } = require('./query-rewrite');
11
+
12
+ const SYNONYMS = [
13
+ [['محصول', 'کالا', 'product'], ['product', 'products']],
14
+ [['سبد', 'cart', 'basket'], ['cart', 'checkout']],
15
+ [['خرید', 'checkout', 'پرداخت'], ['checkout', 'payment', 'cart']],
16
+ [['پرداخت', 'payment', 'pay'], ['payment', 'checkout']],
17
+ [['تیکت', 'ticket', 'پشتیبانی'], ['ticket', 'tickets', 'support']],
18
+ [['فروشنده', 'vendor', 'غرفه'], ['vendor', 'bss']],
19
+ [['دسته', 'category', 'categories'], ['category', 'categories']],
20
+ [['ورود', 'لاگین', 'login', 'auth', 'احراز'], ['auth', 'login', 'otp']],
21
+ [['داشبورد', 'dashboard', 'پنل'], ['dashboard', 'bss']],
22
+ [['مارکت', 'بازار', 'فروشگاه', 'marketplace', 'شاپ'], ['marketplace', 'shop']],
23
+ [['فرم', 'form'], ['form']],
24
+ [['صفحه', 'اسکرین', 'screen', 'page'], ['screen', 'page']],
25
+ [['باگ', 'خطا', 'ارور', 'خراب', 'bug', 'error', 'crash'], ['bug', 'fix', 'error']],
26
+ [['فیکس', 'درست', 'تعمیر', 'fix', 'repair'], ['fix']],
27
+ [['اضافه', 'بساز', 'پیاده', 'implement', 'add', 'create'], ['add', 'implement']],
28
+ [['بازسازی', 'refactor', 'ساده'], ['refactor']],
29
+ [['معماری', 'لایه', 'مرز', 'architecture', 'boundary'], ['architecture', 'boundary']],
30
+ [['قانون', 'رول', 'rule', 'policy'], ['rule', 'policy', 'governance']],
31
+ [['کامپوننت', 'component'], ['component']],
32
+ [['هوک', 'hook'], ['hook']],
33
+ [['ایپیای', 'api', 'اندپوینت', 'endpoint'], ['api', 'endpoint']],
34
+ [['مسیر', 'روت', 'route', 'router'], ['route', 'router']],
35
+ [['نشست', 'session'], ['session']],
36
+ [['نقشه', 'mapper'], ['mapper']],
37
+ [['ویژگی', 'فیچر', 'feature'], ['feature']],
38
+ [['لیست', 'list'], ['list']],
39
+ [['جزئیات', 'detail'], ['detail']],
40
+ [['وضعیت', 'status'], ['status']],
41
+ [['افزودن محصول', 'add product'], ['add-product']],
42
+ [['وضعیت محصول', 'product status'], ['product-status']],
43
+ ];
44
+
45
+ const STOP = new Set([
46
+ 'the', 'and', 'for', 'with', 'this', 'that', 'from', 'into', 'please',
47
+ 'یک', 'این', 'اون', 'رو', 'را', 'به', 'از', 'در', 'که', 'من', 'تو', 'کن',
48
+ 'بکن', 'بده', 'کنه', 'می', 'های', 'ها', 'یا', 'تا',
49
+ ]);
50
+
51
+ function tokenize(text) {
52
+ const raw = String(text || '').toLowerCase();
53
+ const parts = raw.split(/[^\p{L}\p{N}_./-]+/u).filter((t) => t.length > 1 && !STOP.has(t));
54
+ return parts;
55
+ }
56
+
57
+ function expandSynonyms(tokens) {
58
+ const extra = [];
59
+ const joined = tokens.join(' ');
60
+ for (const [keys, vals] of SYNONYMS) {
61
+ if (keys.some((k) => joined.includes(k) || tokens.includes(k))) {
62
+ extra.push(...vals);
63
+ }
64
+ }
65
+ return [...new Set([...tokens, ...extra])];
66
+ }
67
+
68
+ function extractSymbols(prompt) {
69
+ const s = String(prompt || '');
70
+ const pascal = s.match(/\b[A-Z][a-zA-Z0-9]{3,}\b/g) || [];
71
+ const quoted = s.match(/[`'"«»]([^`'"]{2,80})[`'"«»]/g) || [];
72
+ const fromQuotes = quoted.map((q) => q.replace(/^[`'"]+|['`"]+$/g, ''));
73
+ return [...new Set([...pascal, ...fromQuotes.filter((x) => /[A-Za-z]/.test(x))])];
74
+ }
75
+
76
+ function extractPathHints(prompt) {
77
+ const s = String(prompt || '');
78
+ const hints = [];
79
+ const pathish = s.match(/[a-z0-9]+(?:[_/-][a-z0-9]+){1,}/gi) || [];
80
+ for (const m of pathish) hints.push(m.toLowerCase());
81
+ const p = s.toLowerCase();
82
+ if (/product[\s_-]?status|وضعیت\s*محصول/.test(p)) hints.push('product-status');
83
+ if (/add[\s_-]?product|افزودن\s*محصول|اضافه\s*کردن\s*محصول/.test(p)) hints.push('add-product');
84
+ if (/vendor|فروشنده/.test(p)) hints.push('vendor');
85
+ if (/checkout|پرداخت|سبد/.test(p)) hints.push('checkout');
86
+ if (/ticket|تیکت/.test(p)) hints.push('ticket');
87
+ for (const sym of extractSymbols(prompt)) {
88
+ const parts = splitIdentifier(sym);
89
+ if (parts.includes('product') && parts.includes('status')) hints.push('product-status');
90
+ if (parts.includes('add') && parts.includes('product')) hints.push('add-product');
91
+ if (parts.includes('checkout')) hints.push('checkout');
92
+ if (parts.includes('cart')) hints.push('cart');
93
+ }
94
+ return [...new Set(hints.filter((h) => h.length > 2))];
95
+ }
96
+
97
+ function detectDomain(prompt) {
98
+ const p = String(prompt || '');
99
+ const has = (re) => re.test(p);
100
+ if (has(/marketplace|shop|\bcart\b|checkout|catalog|مارکت|بازار|فروشگاه|سبد/i)) {
101
+ return {
102
+ id: 'marketplace',
103
+ label: 'Marketplace',
104
+ priorityPaths: ['apps/marketplace/src'],
105
+ };
106
+ }
107
+ if (has(/vendor|product.?status|add.?product|فروشنده|افزودن\s*محصول|وضعیت\s*محصول/i)) {
108
+ return {
109
+ id: 'bss_vendor',
110
+ label: 'BSS Vendor',
111
+ priorityPaths: [
112
+ 'apps/dashboard/bss/src/portals/vendor',
113
+ 'apps/dashboard/bss/src',
114
+ ],
115
+ };
116
+ }
117
+ if (has(/\bbss\b|dashboard|تیکت|\bticket\b|پنل/i)) {
118
+ return {
119
+ id: 'bss_dashboard',
120
+ label: 'BSS Dashboard',
121
+ priorityPaths: ['apps/dashboard/bss/src', 'apps/dashboard'],
122
+ };
123
+ }
124
+ return null;
125
+ }
126
+
127
+ function analyzePrompt(prompt, memory) {
128
+ const rewritten = rewriteQuery(prompt, memory || {});
129
+ const source = rewritten.rewritten || prompt;
130
+ const tokens = tokenize(source);
131
+ const symbols = extractSymbols(source);
132
+ const identifierParts = splitMany(symbols);
133
+ const expanded = expandSynonyms([...tokens, ...identifierParts]);
134
+ const pathHints = extractPathHints(source);
135
+ const domain = detectDomain(source);
136
+ return {
137
+ tokens,
138
+ expanded,
139
+ symbols,
140
+ pathHints,
141
+ domain,
142
+ identifierParts,
143
+ rewrite: rewritten,
144
+ };
145
+ }
146
+
147
+ module.exports = {
148
+ SYNONYMS,
149
+ tokenize,
150
+ expandSynonyms,
151
+ extractSymbols,
152
+ extractPathHints,
153
+ detectDomain,
154
+ analyzePrompt,
155
+ splitIdentifier,
156
+ rewriteQuery,
157
+ };