copilot-tracer 1.0.2 → 1.0.4

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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # copilot-tracer
2
2
 
3
- Real-time tracing and monitoring tool for **GitHub Copilot CLI** and **VS Code Copilot extension**.
3
+ Real-time tracing and prompt-refinement tool for **GitHub Copilot CLI** and **VS Code Copilot extension**.
4
4
 
5
- Captures every prompt, response, token usage, AI credits, tool calls, skill invocations and duration — all in one place. Works via native **OpenTelemetry (OTLP)** integration built into GitHub Copilot. No wrapper, no binary replacement, no ACP proxy needed.
5
+ Captures every prompt, response, token usage, AI credits, tool calls, skill invocations, and duration — all in one place. It also includes a prompt optimizer that rewrites prompts using role grounding, imperative clarity, output formatting, constraint injection, and noise removal. Works via native **OpenTelemetry (OTLP)** integration built into GitHub Copilot, with an optional ACP proxy mode for live CLI tracing.
6
6
 
7
7
  ---
8
8
 
@@ -10,11 +10,13 @@ Captures every prompt, response, token usage, AI credits, tool calls, skill invo
10
10
 
11
11
  - **Zero-intrusion capture** — uses Copilot's built-in OTel support. Set 2 env vars, done.
12
12
  - **Works everywhere** — captures both Copilot CLI (`copilot -p "..."`) and VS Code Copilot Chat
13
+ - **Prompt refinement** — rewrites prompts with stronger instructions, clearer outputs, and less noise
13
14
  - **Real-time web UI** — live dashboard at `http://localhost:4747` with dark theme
14
15
  - **Full prompt & response** — see exactly what you sent and what Copilot replied
15
16
  - **Token breakdown** — input, output, cached, reasoning, written tokens per request
16
17
  - **AI Credits tracking** — matches exactly what Copilot terminal reports (e.g. `2.59 cr`)
17
18
  - **Tool call visibility** — see every tool/skill/MCP invoked during a session
19
+ - **ACP proxy mode** — wrap the Copilot CLI to trace live JSON-RPC traffic when needed
18
20
  - **Persistent storage** — SQLite at `~/.copilot-tracer/traces.db`, survives restarts
19
21
  - **Console + Web UI** — CLI table view or browser dashboard, your choice
20
22
 
@@ -175,6 +177,44 @@ Live updating table in terminal. Same columns as web UI. TOTALS row pinned at to
175
177
 
176
178
  ---
177
179
 
180
+ ## Prompt Refinement
181
+
182
+ The web UI exposes `POST /api/refine` to improve a raw prompt before sending it to Copilot.
183
+
184
+ It currently applies these techniques:
185
+
186
+ - **Role grounding** — adds an expert persona when the prompt has none
187
+ - **Imperative clarity** — turns soft phrasing into direct instructions
188
+ - **Output format** — asks for JSON, markdown, bullets, or steps when missing
189
+ - **Reasoning guidance** — adds step-by-step thinking for multi-step tasks
190
+ - **Noise removal** — strips filler like “please”, “could you”, and “I think”
191
+ - **Constraint injection** — adds language, tone, length, and audience constraints
192
+ - **Redundancy cleanup** — removes repeated or conflicting instructions
193
+
194
+ Example request:
195
+
196
+ ```bash
197
+ curl -s http://localhost:4747/api/refine \
198
+ -H 'content-type: application/json' \
199
+ -d '{"prompt":"help me write a plan for migrating a monolith"}'
200
+ ```
201
+
202
+ Response shape:
203
+
204
+ ```json
205
+ {
206
+ "ok": true,
207
+ "optimized": "…",
208
+ "issues": [],
209
+ "techniques": [],
210
+ "origTok": 10,
211
+ "newTok": 22,
212
+ "inputSavingPct": 0
213
+ }
214
+ ```
215
+
216
+ ---
217
+
178
218
  ## CLI Flags
179
219
 
180
220
  | Flag | Description |
@@ -108,15 +108,14 @@ function processSpans(spans, sessionId) {
108
108
  return text.trim();
109
109
  })
110
110
  .join(' ')
111
- .trim()
112
- .slice(0, 300);
111
+ .trim();
113
112
  }
114
113
  else {
115
- promptText = (userMsg?.content ?? String(raw)).slice(0, 300);
114
+ promptText = (userMsg?.content ?? String(raw)).trim();
116
115
  }
117
116
  }
118
117
  catch {
119
- promptText = String(raw).slice(0, 300);
118
+ promptText = String(raw).trim();
120
119
  }
121
120
  }
122
121
  }
package/dist/webServer.js CHANGED
@@ -3,6 +3,7 @@ import { createServer } from 'http';
3
3
  import { Server } from 'socket.io';
4
4
  import path from 'path';
5
5
  import { fileURLToPath } from 'url';
6
+ import { execSync } from 'child_process';
6
7
  import { getTraces, getTrace, getSessionSummary } from './db.js';
7
8
  import { traceEvents } from './proxy.js';
8
9
  import { registerOtlpRoutes } from './otlpReceiver.js';
@@ -22,6 +23,52 @@ export function startWebServer(port = 4747, sessionId) {
22
23
  const traces = getTraces(sid, 200);
23
24
  res.json(traces);
24
25
  });
26
+ app.post('/api/refine', async (req, res) => {
27
+ const { prompt } = req.body;
28
+ if (!prompt?.trim()) {
29
+ res.status(400).json({ error: 'prompt required' });
30
+ return;
31
+ }
32
+ const origTok = Math.ceil(prompt.trim().length / 4);
33
+ // Meta-prompt grounded in prompt engineering best practices (promptingguide.ai)
34
+ const metaPrompt = `You are a world-class prompt engineering expert. Rewrite the user prompt below using these techniques where applicable:
35
+ 1. Role grounding — prepend "You are a <expert role>" if no persona is set
36
+ 2. Imperative clarity — replace indirect/hedging phrases with direct imperatives (Explain / List / Generate / Analyze)
37
+ 3. Output format — specify format (JSON, markdown, numbered steps, bullet list) when missing
38
+ 4. Chain-of-thought — add "Think step by step." for multi-step reasoning or debugging tasks
39
+ 5. Remove noise — remove filler (please, could you, I want you to, thank you, if you don't mind, maybe, I think, kind of, sort of)
40
+ 6. Add constraints — specify language, length, audience, tone if not present
41
+ 7. Redundancy — collapse repeated or contradictory instructions
42
+
43
+ Respond ONLY with a valid JSON object (no markdown, no code fences):
44
+ {"optimized":"<rewritten prompt>","issues":[{"type":"warn","msg":"<what was wrong>"}],"techniques":["<applied>"]}
45
+
46
+ Prompt to optimize:
47
+ ${prompt.trim()}`;
48
+ try {
49
+ const raw = execSync(`copilot -p ${JSON.stringify(metaPrompt)} --model claude-sonnet-4.6`, { timeout: 45000, encoding: 'utf8', maxBuffer: 2 * 1024 * 1024 });
50
+ // Strip copilot CLI chrome (trailing "Changes +0 -0" line, ANSI codes)
51
+ const cleaned = raw
52
+ .replace(/\x1b\[[0-9;]*m/g, '') // ANSI
53
+ .replace(/\r/g, '')
54
+ .split('\n')
55
+ .filter(l => !/^Changes\s+\+\d/.test(l.trim()))
56
+ .join('\n')
57
+ .trim();
58
+ // Extract JSON — copilot may wrap with prose
59
+ const jsonMatch = cleaned.match(/\{[\s\S]*\}/);
60
+ if (!jsonMatch)
61
+ throw new Error('No JSON in response: ' + cleaned.slice(0, 200));
62
+ const result = JSON.parse(jsonMatch[0]);
63
+ const newTok = Math.ceil((result.optimized ?? '').length / 4);
64
+ const inputSavingPct = origTok > 0 ? Math.max(0, Math.round((1 - newTok / origTok) * 100)) : 0;
65
+ res.json({ ok: true, ...result, origTok, newTok, inputSavingPct });
66
+ }
67
+ catch (err) {
68
+ const msg = err instanceof Error ? err.message : String(err);
69
+ res.status(500).json({ ok: false, error: msg });
70
+ }
71
+ });
25
72
  app.get('/api/traces/:id', (req, res) => {
26
73
  const trace = getTrace(req.params.id);
27
74
  if (!trace)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "copilot-tracer",
3
- "version": "1.0.2",
4
- "description": "Real-time monitor and tracing tool for GitHub Copilot CLI — tracks tokens, AI credits, MCP/skill/agent calls with console and web UI",
3
+ "version": "1.0.4",
4
+ "description": "Real-time tracing and prompt-refinement companion for GitHub Copilot CLI and VS Code Copilot — tracks tokens, AI credits, tool calls, and refined prompts with console and web UI",
5
5
  "keywords": [
6
6
  "copilot",
7
7
  "github-copilot",
package/web/index.html CHANGED
@@ -5,6 +5,126 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
6
  <title>Copilot Tracer</title>
7
7
  <script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
8
+ <script type="module">
9
+ import * as webllm from 'https://esm.run/@mlc-ai/web-llm';
10
+
11
+ // ─── REFINE ENGINE — Phase 1: instant heuristics · Phase 2: webLLM ───────
12
+ let refineOpen = false;
13
+ let engine = null;
14
+
15
+ const tok = t => Math.ceil((t || '').length / 4);
16
+
17
+ // Phase 1 — instant local analysis
18
+ function analyzePrompt(t) {
19
+ const issues = [], suggestions = [];
20
+ if ([/\bplease\b/,/\bcould you\b/,/\bcan you\b/,/\bI want you to\b/,/\bkindly\b/,/\bthank you\b/,/\bif you don.t mind\b/].some(r => r.test(t)))
21
+ issues.push({ type:'warn', msg:'Filler / polite noise (please, can you, thank you…)' });
22
+ if ([/\bI think\b/,/\bmaybe\b/,/\bkind of\b/,/\bsort of\b/].some(r => r.test(t)))
23
+ issues.push({ type:'warn', msg:'Hedging language reduces precision (I think, maybe, kind of…)' });
24
+ if (/\bhelp me with\b|\bhelp me understand\b/i.test(t))
25
+ issues.push({ type:'warn', msg:'Indirect phrasing — use an imperative verb: Explain / List / Generate / Analyze' });
26
+ if (/\bmake it (?:good|nice|better|great)\b/i.test(t))
27
+ issues.push({ type:'warn', msg:'Vague quality instruction — specify criteria (concise, bullet-list, ≤200 words…)' });
28
+ const hasFormat = /\bjson\b|\bmarkdown\b|\bbullet\b|\bnumbered\b|\bstep.by.step\b/i.test(t);
29
+ if (!hasFormat && t.length > 60) issues.push({ type:'info', msg:'No output format — add: "Respond in bullet points / JSON / numbered steps"' });
30
+ const hasRole = /\byou are\b|\bact as\b|\bexpert\b|\bsenior\b/i.test(t);
31
+ if (!hasRole && t.length > 80) issues.push({ type:'info', msg:'No role/persona — prepend "You are a <expert>." for better quality' });
32
+ if (!/\bstep by step\b|\bthink\b/i.test(t) && /\bdebug\b|\banalyze\b|\bcompare\b|\bdesign\b/i.test(t))
33
+ issues.push({ type:'info', msg:'Complex task — add "Think step by step." for chain-of-thought reasoning' });
34
+ if (!issues.length) issues.push({ type:'info', msg:'No obvious issues — prompt looks well-structured' });
35
+ if (!hasRole && t.length > 80) suggestions.push('Add a role: "You are a senior software engineer."');
36
+ if (!hasFormat && t.length > 60) suggestions.push('Specify output: "Respond in numbered steps."');
37
+ return { issues, suggestions };
38
+ }
39
+
40
+ function escHtml(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
41
+
42
+ window.toggleRefine = async function(prompt) {
43
+ const panel = document.getElementById('refine-panel');
44
+ if (!panel) return;
45
+ if (refineOpen) { panel.classList.remove('open'); refineOpen = false; return; }
46
+ refineOpen = true;
47
+ panel.classList.add('open');
48
+
49
+ const origTok = tok(prompt);
50
+ const { issues, suggestions } = analyzePrompt(prompt);
51
+
52
+ const issueHtml = issues.map(i => `<div class="refine-issue ${i.type}">⚠ ${escHtml(i.msg)}</div>`).join('');
53
+ const suggHtml = suggestions.length
54
+ ? `<div class=refine-section-title>Quick Suggestions</div><div class=refine-suggestions>${suggestions.map(s=>`<div class=refine-suggestion>💡 ${escHtml(s)}</div>`).join('')}</div>`
55
+ : '';
56
+
57
+ panel.innerHTML = `
58
+ <div class=refine-section-title>Issues Found</div>
59
+ <div class=refine-issues>${issueHtml}</div>
60
+ ${suggHtml}
61
+ <div class=refine-section-title>Optimized Prompt</div>
62
+ <div id=refine-optimizing style="color:#8b949e;font-size:12px;padding:6px 0">
63
+ <span class=refine-spinner></span> Optimizing with WebLLM…
64
+ <div id=refine-progress style="margin-top:5px;font-size:10px;color:#58a6ff"></div>
65
+ </div>
66
+ <div class=refine-optimized id=refined-text style="display:none"></div>
67
+ <div id=refine-actions style="display:none">
68
+ <button class="refine-copy-btn" onclick="copyRefined()">📋 Copy optimized prompt</button>
69
+ <div class=refine-stats id=refine-stats></div>
70
+ </div>`;
71
+
72
+ // Phase 2 — webLLM
73
+ const progressEl = () => document.getElementById('refine-progress');
74
+ const optimizingEl = document.getElementById('refine-optimizing');
75
+ const refinedEl = document.getElementById('refined-text');
76
+ const actionsEl = document.getElementById('refine-actions');
77
+
78
+ try {
79
+ if (!engine) {
80
+ progressEl() && (progressEl().textContent = 'Loading model… (first run ~650 MB, cached after)');
81
+ engine = await webllm.CreateMLCEngine('Llama-3.2-1B-Instruct-q4f16_1-MLC', {
82
+ initProgressCallback: p => { const el = progressEl(); if (el) el.textContent = p.text || ''; }
83
+ });
84
+ }
85
+ if (progressEl()) progressEl().textContent = 'Generating…';
86
+
87
+ const sys = `You are a prompt engineering expert. Rewrite the user prompt by:
88
+ 1. Adding a role ("You are a <expert>.") if missing
89
+ 2. Replacing indirect/hedging phrases with direct imperatives (Explain / List / Generate)
90
+ 3. Adding output format (numbered steps / bullet list / JSON) if missing
91
+ 4. Adding "Think step by step." for complex tasks (debug, analyze, compare)
92
+ 5. Removing filler words (please, can you, thank you, maybe, I think)
93
+ 6. Adding constraints (language, length, audience) if missing
94
+ Reply with ONLY the rewritten prompt. No explanation, no preamble.`;
95
+
96
+ const reply = await engine.chat.completions.create({
97
+ messages: [{ role:'system', content:sys }, { role:'user', content:prompt }],
98
+ temperature: 0.3,
99
+ max_tokens: 512,
100
+ });
101
+
102
+ const optimized = (reply.choices[0]?.message?.content || '').trim();
103
+ const newTok = tok(optimized);
104
+ const pct = origTok > 0 ? Math.max(0, Math.round((1 - newTok / origTok) * 100)) : 0;
105
+ const col = pct >= 20 ? '#7ee787' : pct >= 5 ? '#d29922' : '#8b949e';
106
+
107
+ optimizingEl.style.display = 'none';
108
+ refinedEl.textContent = optimized;
109
+ refinedEl.style.display = 'block';
110
+ actionsEl.style.display = 'block';
111
+ document.getElementById('refine-stats').innerHTML = `
112
+ <div class=refine-stat><div class=rv style=color:${col}>${pct > 0 ? '-'+pct+'%' : '0%'}</div><div class=rl>Input Saving</div></div>
113
+ <div class=refine-stat><div class=rv style=color:#8b949e>${origTok} → ${newTok}</div><div class=rl>Approx Tokens</div></div>`;
114
+ } catch(e) {
115
+ optimizingEl.innerHTML = `<span style="color:#f85149">⚠ WebLLM error: ${escHtml(e.message)}</span>`;
116
+ }
117
+ };
118
+
119
+ window.copyRefined = function() {
120
+ const el = document.getElementById('refined-text');
121
+ if (!el) return;
122
+ navigator.clipboard.writeText(el.innerText).then(() => {
123
+ const btn = document.querySelector('.refine-copy-btn');
124
+ if (btn) { btn.textContent = '✅ Copied!'; setTimeout(() => btn.textContent = '📋 Copy optimized prompt', 1500); }
125
+ });
126
+ };
127
+ </script>
8
128
  <style>
9
129
  * { box-sizing: border-box; margin: 0; padding: 0; }
10
130
  body { background: #0d1117; color: #c9d1d9; font-family: 'Segoe UI', system-ui, monospace; font-size: 13px; }
@@ -68,7 +188,7 @@
68
188
  .detail-box { background: #0d1117; border: 1px solid #30363d; border-radius: 6px; padding: 10px; font-size: 12px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; max-height: 160px; overflow: auto; }
69
189
  .detail-box.reasoning { color: #d2a8ff; }
70
190
  .detail-box.response { color: #56d364; }
71
- .detail-box.prompt { color: #e6edf3; }
191
+ .detail-box.prompt { color: #e6edf3; max-height: none; overflow: visible; }
72
192
 
73
193
  /* Call graph */
74
194
  .call-tree { font-family: monospace; font-size: 12px; }
@@ -83,7 +203,30 @@
83
203
  .call-io .label { color: #58a6ff; margin-right: 4px; }
84
204
  .call-io.error { color: #f85149; }
85
205
 
86
- /* Token detail modal */
206
+ /* Refine panel */
207
+ .refine-btn { background: #1f2d1f; border: 1px solid #238636; color: #7ee787; border-radius: 6px; padding: 5px 12px; font-size: 11px; cursor: pointer; margin-top: 8px; display: inline-flex; align-items: center; gap: 6px; transition: all .15s; }
208
+ .refine-btn:hover { background: #238636; color: #fff; }
209
+ .refine-panel { margin-top: 12px; background: #0d1117; border: 1px solid #238636; border-radius: 8px; padding: 14px; display: none; }
210
+ .refine-panel.open { display: block; }
211
+ .refine-section-title { font-size: 10px; text-transform: uppercase; letter-spacing: .6px; color: #8b949e; margin-bottom: 6px; margin-top: 12px; }
212
+ .refine-section-title:first-child { margin-top: 0; }
213
+ .refine-issues { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; }
214
+ .refine-issue { font-size: 11px; padding: 4px 8px; border-radius: 4px; background: #21262d; border-left: 3px solid; }
215
+ .refine-issue.warn { border-color: #d29922; color: #d29922; }
216
+ .refine-issue.info { border-color: #58a6ff; color: #8b949e; }
217
+ .refine-optimized { background: #161b22; border: 1px solid #30363d; border-radius: 6px; padding: 10px; font-size: 12px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; color: #7ee787; max-height: 260px; overflow: auto; }
218
+ .refine-stats { display: flex; gap: 12px; margin-top: 10px; flex-wrap: wrap; }
219
+ .refine-stat { background: #21262d; border-radius: 6px; padding: 6px 12px; text-align: center; }
220
+ .refine-stat .rv { font-size: 18px; font-weight: 700; }
221
+ .refine-stat .rl { font-size: 10px; color: #8b949e; margin-top: 1px; }
222
+ .refine-copy-btn { margin-top: 10px; background: #21262d; border: 1px solid #30363d; color: #8b949e; border-radius: 6px; padding: 4px 12px; font-size: 11px; cursor: pointer; }
223
+ .refine-copy-btn:hover { color: #e6edf3; border-color: #58a6ff; }
224
+ .refine-techniques { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 8px; }
225
+ .refine-tag { background: #0d2230; border: 1px solid #1f6feb; color: #58a6ff; border-radius: 10px; padding: 2px 9px; font-size: 10px; }
226
+ .refine-suggestions { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; }
227
+ .refine-suggestion { font-size: 11px; padding: 4px 8px; border-radius: 4px; background: #0d2230; border-left: 3px solid #1f6feb; color: #8b949e; }
228
+ @keyframes refine-spin { to { transform: rotate(360deg); } }
229
+ .refine-spinner { display: inline-block; width: 10px; height: 10px; border: 2px solid #30363d; border-top-color: #58a6ff; border-radius: 50%; animation: refine-spin .7s linear infinite; vertical-align: middle; margin-right: 4px; }
87
230
  .modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.7); z-index: 100; align-items: center; justify-content: center; }
88
231
  .modal-overlay.open { display: flex; }
89
232
  .modal { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 20px; width: 560px; max-height: 80vh; overflow: auto; }
@@ -265,12 +408,15 @@
265
408
  }
266
409
 
267
410
  function renderDetail(t) {
411
+ refineOpen = false;
268
412
  document.getElementById('detail-title').textContent = truncate(t.prompt || 'Trace Detail', 50);
269
413
  const body = document.getElementById('detail-body');
270
414
  body.innerHTML = `
271
415
  <div class="detail-section">
272
416
  <h4>Prompt</h4>
273
417
  <div class="detail-box prompt">${escHtml(t.prompt||'')}</div>
418
+ <button class="refine-btn" onclick="toggleRefine(${JSON.stringify(t.prompt||'').replace(/</g,'\\u003c').replace(/"/g,'&quot;')})">✨ Refine Prompt</button>
419
+ <div class="refine-panel" id="refine-panel"></div>
274
420
  </div>
275
421
  ${t.reasoning ? `
276
422
  <div class="detail-section">
File without changes