mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
1108 lines
36 KiB
HTML
1108 lines
36 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<title>Mercury Next Edit — Testing Playground (Kilo Code)</title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<style>
|
||
:root {
|
||
--bg: #0d1117;
|
||
--panel: #161b22;
|
||
--border: #30363d;
|
||
--fg: #e6edf3;
|
||
--muted: #8b949e;
|
||
--accent: #7ee787;
|
||
--accent2: #79c0ff;
|
||
--warn: #f0883e;
|
||
--danger: #ff7b72;
|
||
--code: #c9d1d9;
|
||
--code-bg: #0d1117;
|
||
}
|
||
@media (prefers-color-scheme: light) {
|
||
:root {
|
||
--bg: #ffffff;
|
||
--panel: #f6f8fa;
|
||
--border: #d0d7de;
|
||
--fg: #1f2328;
|
||
--muted: #57606a;
|
||
--accent: #1a7f37;
|
||
--accent2: #0969da;
|
||
--warn: #bc4c00;
|
||
--danger: #cf222e;
|
||
--code: #1f2328;
|
||
--code-bg: #f6f8fa;
|
||
}
|
||
}
|
||
* {
|
||
box-sizing: border-box;
|
||
}
|
||
html {
|
||
font-size: 16px;
|
||
}
|
||
body {
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
||
line-height: 1.55;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
.wrap {
|
||
max-width: 920px;
|
||
margin: 0 auto;
|
||
padding: 2.5rem 1.5rem 4rem;
|
||
}
|
||
h1 {
|
||
font-size: 2rem;
|
||
margin-top: 0;
|
||
letter-spacing: -0.01em;
|
||
}
|
||
h2 {
|
||
font-size: 1.4rem;
|
||
margin-top: 2.5rem;
|
||
border-bottom: 1px solid var(--border);
|
||
padding-bottom: 0.4rem;
|
||
}
|
||
h3 {
|
||
font-size: 1.1rem;
|
||
margin-top: 1.75rem;
|
||
}
|
||
p,
|
||
li {
|
||
color: var(--fg);
|
||
}
|
||
.muted {
|
||
color: var(--muted);
|
||
}
|
||
.pill {
|
||
display: inline-block;
|
||
padding: 2px 8px;
|
||
border-radius: 999px;
|
||
font-size: 0.78rem;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
color: var(--muted);
|
||
margin-right: 6px;
|
||
vertical-align: middle;
|
||
}
|
||
.pill.same-line {
|
||
color: var(--accent);
|
||
border-color: color-mix(in srgb, var(--accent) 50%, var(--border));
|
||
}
|
||
.pill.off-cursor {
|
||
color: var(--accent2);
|
||
border-color: color-mix(in srgb, var(--accent2) 50%, var(--border));
|
||
}
|
||
.pill.suppressed {
|
||
color: var(--warn);
|
||
border-color: color-mix(in srgb, var(--warn) 50%, var(--border));
|
||
}
|
||
code,
|
||
pre {
|
||
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
||
}
|
||
code {
|
||
background: var(--panel);
|
||
padding: 1px 5px;
|
||
border-radius: 4px;
|
||
font-size: 0.92em;
|
||
}
|
||
pre {
|
||
background: var(--code-bg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
padding: 0.9rem 1rem;
|
||
overflow-x: auto;
|
||
margin: 0.5rem 0;
|
||
}
|
||
pre code {
|
||
background: none;
|
||
padding: 0;
|
||
color: var(--code);
|
||
font-size: 0.88rem;
|
||
line-height: 1.5;
|
||
}
|
||
.card {
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
padding: 1.1rem 1.25rem;
|
||
margin: 1rem 0;
|
||
}
|
||
.card h3 {
|
||
margin-top: 0;
|
||
}
|
||
.card .label {
|
||
font-weight: 600;
|
||
color: var(--muted);
|
||
text-transform: uppercase;
|
||
font-size: 0.75rem;
|
||
letter-spacing: 0.06em;
|
||
margin-top: 0.6rem;
|
||
}
|
||
.step-list {
|
||
padding-left: 1.4rem;
|
||
margin: 0.4rem 0 0;
|
||
}
|
||
table {
|
||
border-collapse: collapse;
|
||
width: 100%;
|
||
margin: 0.75rem 0;
|
||
}
|
||
th,
|
||
td {
|
||
text-align: left;
|
||
padding: 0.55rem 0.7rem;
|
||
border-bottom: 1px solid var(--border);
|
||
font-size: 0.92rem;
|
||
}
|
||
th {
|
||
color: var(--muted);
|
||
font-weight: 600;
|
||
}
|
||
.toc {
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
padding: 0.9rem 1.1rem;
|
||
margin: 1rem 0 2rem;
|
||
}
|
||
.toc h2 {
|
||
font-size: 1rem;
|
||
margin: 0 0 0.4rem;
|
||
border: 0;
|
||
padding: 0;
|
||
color: var(--muted);
|
||
}
|
||
.toc ul {
|
||
margin: 0;
|
||
padding-left: 1.2rem;
|
||
}
|
||
.toc li {
|
||
margin: 0.15rem 0;
|
||
font-size: 0.95rem;
|
||
}
|
||
a {
|
||
color: var(--accent2);
|
||
text-decoration: none;
|
||
}
|
||
a:hover {
|
||
text-decoration: underline;
|
||
}
|
||
blockquote {
|
||
margin: 1rem 0;
|
||
padding: 0.6rem 1rem;
|
||
border-left: 3px solid var(--accent2);
|
||
background: var(--panel);
|
||
border-radius: 0 6px 6px 0;
|
||
color: var(--fg);
|
||
}
|
||
blockquote.warn {
|
||
border-left-color: var(--warn);
|
||
}
|
||
.footer {
|
||
color: var(--muted);
|
||
font-size: 0.85rem;
|
||
margin-top: 3rem;
|
||
border-top: 1px solid var(--border);
|
||
padding-top: 1rem;
|
||
}
|
||
kbd {
|
||
font-family: ui-monospace, monospace;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-bottom-width: 2px;
|
||
border-radius: 4px;
|
||
padding: 1px 6px;
|
||
font-size: 0.85em;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
<h1>Mercury Next Edit — Testing Playground</h1>
|
||
<p class="muted">
|
||
A walk-through guide for Kilo Code reviewers to validate the new
|
||
<strong>Next Edit Suggestion</strong> integration powered by Mercury Edit 2 from Inception Labs.
|
||
</p>
|
||
|
||
<div class="toc">
|
||
<h2>On this page</h2>
|
||
<ul>
|
||
<li><a href="#what">What is Mercury Next Edit?</a></li>
|
||
<li><a href="#install">Install the PR locally</a> — 3 minutes from clone to running Dev Host</li>
|
||
<li><a href="#setup">Enabling the feature</a></li>
|
||
<li><a href="#flow">How the integration works</a></li>
|
||
<li><a href="#tests">Test cases</a> — 20 scenarios across Python, TS, Go, Rust, JS, SQL, MD</li>
|
||
<li><a href="#troubleshooting">Troubleshooting</a></li>
|
||
<li><a href="#feedback">Feedback we're looking for</a></li>
|
||
</ul>
|
||
</div>
|
||
|
||
<h2 id="what">What is Mercury Next Edit?</h2>
|
||
<p>
|
||
<strong>Mercury Edit 2</strong> is a code-edit model from <a href="https://inceptionlabs.ai">Inception Labs</a>.
|
||
Unlike FIM completion, it predicts the user's <em>next multi-line edit</em> given the current file, cursor
|
||
position, and recent edit history. It typically responds in under 250 ms.
|
||
</p>
|
||
<p>
|
||
This PR adds Mercury Next Edit as a <strong>new, opt-in</strong> autocomplete option in Kilo Code. It lives
|
||
alongside everything that's already shipping — Codestral FIM and Mercury Edit 2 via the Kilo gateway are
|
||
bit-for-bit unchanged. Selecting <strong>Mercury Next Edit (Inception)</strong> from the model dropdown switches
|
||
to a separate render pipeline:
|
||
</p>
|
||
<ul>
|
||
<li>
|
||
<strong>Same-line predictions</strong> render as inline ghost text (just like FIM —
|
||
<kbd>Tab</kbd> accepts).
|
||
</li>
|
||
<li>
|
||
<strong>Off-cursor predictions</strong> render as a decoration (red strikethrough + green ghost annotation) at
|
||
the predicted edit location. First <kbd>Tab</kbd> teleports the cursor there; second <kbd>Tab</kbd> applies.
|
||
</li>
|
||
<li>
|
||
After any accept, the integration immediately re-triggers Mercury so the user can walk a refactor with
|
||
repeated <kbd>Tab</kbd> presses ("Tab-Tab-Tab").
|
||
</li>
|
||
</ul>
|
||
|
||
<h2 id="install">Install the PR locally</h2>
|
||
<p>
|
||
You'll need to pull this PR's branch and run the extension in a development VSCode window ("Extension
|
||
Development Host"). The whole loop is about 3 minutes once you have the prerequisites.
|
||
</p>
|
||
|
||
<h3>Prerequisites</h3>
|
||
<ul>
|
||
<li><strong>VSCode</strong> ≥ 1.105.1 (matches kilocode's <code>engines.vscode</code>)</li>
|
||
<li>
|
||
<strong>Bun</strong> ≥ 1.3.14 (the build script checks the version) — install via
|
||
<code>brew install bun</code> or <a href="https://bun.sh">bun.sh</a>
|
||
</li>
|
||
<li><strong>GitHub CLI</strong> (<code>gh</code>) — optional but makes the PR checkout one command</li>
|
||
<li>
|
||
<strong>An Inception API key</strong> — create one at
|
||
<a href="https://platform.inceptionlabs.ai">platform.inceptionlabs.ai</a> if you don't already have one
|
||
</li>
|
||
</ul>
|
||
|
||
<h3>1. Check out the PR branch</h3>
|
||
<p>From an empty directory:</p>
|
||
<pre><code>gh repo clone Kilo-Org/kilocode
|
||
cd kilocode
|
||
gh pr checkout 10536</code></pre>
|
||
<p>Or without <code>gh</code>:</p>
|
||
<pre><code>git clone https://github.com/Kilo-Org/kilocode.git
|
||
cd kilocode
|
||
git fetch origin pull/10536/head:mercury-next-edit-integration
|
||
git checkout mercury-next-edit-integration</code></pre>
|
||
|
||
<h3>2. Install dependencies</h3>
|
||
<pre><code>bun install</code></pre>
|
||
<p>(First install pulls the full monorepo — takes 30–60 seconds.)</p>
|
||
|
||
<h3>3. Start the dev build</h3>
|
||
<pre><code>cd packages/kilo-vscode
|
||
bun run watch</code></pre>
|
||
<p>
|
||
Leave that terminal running. It rebuilds the extension on every save and runs the TypeScript compiler in watch
|
||
mode.
|
||
</p>
|
||
|
||
<h3>4. Open kilocode in VSCode and launch the Extension Development Host</h3>
|
||
<p>From a separate terminal (or your IDE launcher):</p>
|
||
<pre><code>code /path/to/kilocode</code></pre>
|
||
<p>
|
||
Inside that VSCode window, press <kbd>F5</kbd> (or <strong>Run → Start Debugging</strong>). A second VSCode
|
||
window opens, titled <strong>[Extension Development Host]</strong>. That window has this PR's build of the
|
||
kilocode extension loaded.
|
||
</p>
|
||
|
||
<blockquote class="warn">
|
||
<p>
|
||
Pre-push <code>turbo typecheck</code> may fail on packages that need Java (JetBrains plugin). That's
|
||
environment, not code — not relevant to the NES feature. Use <code>--no-verify</code> on any local
|
||
pushes if you hit it.
|
||
</p>
|
||
</blockquote>
|
||
|
||
<h3>5. Open the test playground</h3>
|
||
<p>
|
||
In the Dev Host window: <strong>File → Open Folder…</strong> → choose
|
||
<code>packages/kilo-vscode/docs/nes-examples/</code> inside this same repo. That gives you the 20 self-contained
|
||
test files described below.
|
||
</p>
|
||
|
||
<h3>6. Configure NES</h3>
|
||
<p>Settings (<kbd>Cmd</kbd>+<kbd>,</kbd>) in the Dev Host, search <code>kilo-code.new.autocomplete</code>:</p>
|
||
<ul>
|
||
<li>
|
||
<code>model</code> → <strong>Mercury Next Edit (Inception)</strong> — <em>not</em> "Mercury Edit 2",
|
||
which is the classic FIM-via-gateway option
|
||
</li>
|
||
<li>
|
||
<code>nextEdit.apiKey</code> → paste your <code>sk_…</code> Inception API key (or set
|
||
<code>INCEPTION_API_KEY</code> env before launching)
|
||
</li>
|
||
<li><code>enableAutoTrigger</code> → ✓ (already the default)</li>
|
||
</ul>
|
||
|
||
<h3>7. Watch the pipeline live</h3>
|
||
<p>
|
||
In the Dev Host: <strong>View → Output</strong> → in the dropdown, select
|
||
<strong>"Kilo Code · Next Edit"</strong>. Every request, response, and render decision is logged here with
|
||
timestamps. Keep this panel visible while testing — it's the single best diagnostic.
|
||
</p>
|
||
|
||
<p>You're set. Skip to the <a href="#tests">test cases</a> below.</p>
|
||
|
||
<h2 id="setup">Enabling the feature (settings reference)</h2>
|
||
<p>In VSCode Settings (<kbd>Cmd</kbd>+<kbd>,</kbd>), search <code>kilo-code.new.autocomplete</code>:</p>
|
||
<table>
|
||
<tr>
|
||
<th>Setting</th>
|
||
<th>Value</th>
|
||
</tr>
|
||
<tr>
|
||
<td><code>model</code></td>
|
||
<td>
|
||
<strong>Mercury Next Edit (Inception)</strong> — <em>not</em> "Mercury Edit 2", which is the original
|
||
FIM-via-gateway option
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>nextEdit.apiKey</code></td>
|
||
<td>your Inception API key (<code>sk_...</code>); also accepts <code>INCEPTION_API_KEY</code> env var</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>enableAutoTrigger</code></td>
|
||
<td>✓ (default)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>nextEdit.baseUrl</code></td>
|
||
<td>(optional) override the API base, defaults to <code>https://api.inceptionlabs.ai/v1</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>nextEdit.debug</code></td>
|
||
<td>(optional) mirror diagnostic logs to DevTools console</td>
|
||
</tr>
|
||
</table>
|
||
<p>
|
||
To watch the pipeline live: <strong>View → Output</strong> in the Dev Host, choose the
|
||
<strong>"Kilo Code · Next Edit"</strong> channel.
|
||
</p>
|
||
|
||
<h2 id="flow">How the integration works</h2>
|
||
<p>
|
||
The <code>AutocompleteServiceManager</code> instantiates both providers up front. Provider
|
||
<em>registration</em> with <code>vscode.languages.registerInlineCompletionItemProvider</code> is driven by the
|
||
configured model:
|
||
</p>
|
||
<ul>
|
||
<li><code>inception/mercury-next-edit</code> → <strong>NES provider</strong> (this PR's new pipeline)</li>
|
||
<li>anything else → <strong>classic FIM provider</strong> (unchanged)</li>
|
||
</ul>
|
||
<p>The NES provider, per keystroke:</p>
|
||
<ol>
|
||
<li>Debounces 250 ms (skipped for explicit invocations).</li>
|
||
<li>
|
||
Builds a Mercury prompt: current file + cursor + an editable region <code>[cursor − 5, cursor + 10]</code> +
|
||
3–5 recently-viewed-snippet ranges (from the shared <code>RecentlyVisitedRangesService</code>) + the last 5
|
||
debounced unidiffs (from a new per-file <code>EditHistoryTracker</code>).
|
||
</li>
|
||
<li>
|
||
Sends a single <code>role: "user"</code> message to <code>POST /v1/edit/completions</code> with
|
||
<code>max_tokens: 512</code>.
|
||
</li>
|
||
<li>
|
||
Parses the triple-backtick fenced reply, strips Mercury's sentinel tokens, computes the minimal line-diff
|
||
against the current document.
|
||
</li>
|
||
<li>
|
||
Branches: same-line diff → <code>InlineCompletionItem</code>; off-cursor diff →
|
||
<code>NextEditSuggestionManager</code> with a decoration + Tab/Esc keybinding gated on a context flag
|
||
(<code>kilo-code.nextEdit.hasPendingSuggestion</code>).
|
||
</li>
|
||
</ol>
|
||
|
||
<h2 id="tests">Test cases</h2>
|
||
<p>
|
||
Each test below is a self-contained file at <code>packages/kilo-vscode/docs/nes-examples/</code> in this repo.
|
||
Open that folder in the Extension Development Host (step 5 above), then work through the cases. Place your
|
||
cursor where indicated, wait ~300 ms idle, and observe.
|
||
</p>
|
||
<p>
|
||
Tip: keep this page open in a separate window from the Dev Host — the descriptions below would otherwise
|
||
leak into Mercury's prompt context and bias the test.
|
||
</p>
|
||
|
||
<p>Render-path legend:</p>
|
||
<p>
|
||
<span class="pill same-line">same-line ghost</span> appears as inline ghost text at the cursor (<kbd>Tab</kbd>
|
||
accepts) · <span class="pill off-cursor">off-cursor decoration</span> renders away from the cursor; first
|
||
<kbd>Tab</kbd> jumps, second <kbd>Tab</kbd> applies ·
|
||
<span class="pill suppressed">suppressed</span> negative case — nothing should render
|
||
</p>
|
||
|
||
<h3>Python — core tests</h3>
|
||
|
||
<div class="card">
|
||
<h3>01 — Finish a recursive function body <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>def factorial(n):
|
||
if n <= 1:
|
||
return 1
|
||
|
||
</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>The empty indented line at the end of <code>factorial</code> (column 4).</p>
|
||
<div class="label">Expected</div>
|
||
<p>
|
||
Ghost text proposing the recursive case (e.g. <code>return n * factorial(n - 1)</code>).
|
||
<kbd>Tab</kbd> accepts.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>02 — Pattern continuation <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>COLOR_RED = "#ff0000"
|
||
COLOR_GREEN = "#00ff00"
|
||
COLOR_BLUE =</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>End of line 3 (right after <code>=</code>).</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text appending a hex color like <code> "#0000ff"</code>.</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>03 — Mid-identifier completion <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>def calculate_total(items):
|
||
total = 0
|
||
for item in items:
|
||
total += item.price
|
||
return tot</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>End of file (after <code>return tot</code>).</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text completing the identifier (likely <code>al</code> → <code>total</code>).</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>04 — Loop body inference <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>def calculate_total(items):
|
||
total = 0
|
||
for item in items:
|
||
|
||
return total</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>The empty indented line inside the <code>for</code> loop (column 8).</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text proposing the accumulator update.</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>05 — Sibling method body <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>class Stack:
|
||
def __init__(self):
|
||
self.items = []
|
||
|
||
def push(self, item):
|
||
self.items.append(item)
|
||
|
||
def pop(self):
|
||
|
||
|
||
def peek(self):
|
||
return self.items[-1] if self.items else None</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>Empty indented line inside <code>pop</code> (column 8).</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text proposing a body consistent with the symmetric <code>push</code>.</p>
|
||
</div>
|
||
|
||
<h3>Python — advanced</h3>
|
||
|
||
<div class="card">
|
||
<h3>07 — Multi-line rename refactor <span class="pill off-cursor">off-cursor</span></h3>
|
||
<pre><code>def compute_user_score(u, w):
|
||
base = u * 10
|
||
bonus = w * 5
|
||
penalty = u - w
|
||
return base + bonus - penalty
|
||
|
||
|
||
def compute_user_score(user_id, weight):
|
||
base = u * 10
|
||
bonus = w * 5
|
||
penalty = u - w
|
||
return base + bonus - penalty</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>End of the renamed signature line (<code>def compute_user_score(user_id, weight):</code>).</p>
|
||
<div class="label">Expected</div>
|
||
<p>
|
||
Strikethrough on the body lines below + ghost showing the renamed body. First <kbd>Tab</kbd> jumps, second
|
||
applies.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>08 — Mixed insert + replace <span class="pill off-cursor">off-cursor</span></h3>
|
||
<pre><code>def sum_prices(items):
|
||
total = 0
|
||
for item in items:
|
||
return total</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>End of <code>total = 0</code>.</p>
|
||
<div class="label">Expected</div>
|
||
<p>Decoration on the broken for-loop area showing the corrected body (insertion + replacement combined).</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>10 — Mid-token completion <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>def fibonacci(n):
|
||
if n <= 1:
|
||
return n
|
||
return fibonacci(n - 1) + fibonacci(n - 2)
|
||
|
||
|
||
result = fib</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>End of the file (after <code>result = fib</code>).</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text extending the identifier and supplying a call, e.g. <code>onacci(10)</code>.</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>11 — Stub method with implemented siblings <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>class Queue:
|
||
def __init__(self):
|
||
self.items = []
|
||
|
||
def enqueue(self, item):
|
||
self.items.append(item)
|
||
|
||
def peek(self):
|
||
return self.items[0] if self.items else None
|
||
|
||
def size(self):
|
||
return len(self.items)
|
||
|
||
def is_empty(self):
|
||
return not self.items
|
||
|
||
def dequeue(self):
|
||
|
||
|
||
def clear(self):
|
||
self.items.clear()</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>Empty indented line inside <code>dequeue</code> (column 8).</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text proposing a FIFO pop, e.g. <code>return self.items.pop(0)</code>.</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>
|
||
12 — Type annotation insertion <span class="pill same-line">same-line</span> /
|
||
<span class="pill off-cursor">off-cursor</span>
|
||
</h3>
|
||
<pre><code>def multiply(a: int, b: int) -> int:
|
||
return a * b
|
||
|
||
|
||
def subtract(a: int, b: int) -> int:
|
||
return a - b
|
||
|
||
|
||
def add(a, b):
|
||
return a + b</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>End of <code>def add(a, b):</code> (the only un-annotated function).</p>
|
||
<div class="label">Expected</div>
|
||
<p>
|
||
Strikethrough on the signature line + ghost showing the typed version (<code
|
||
>def add(a: int, b: int) -> int:</code
|
||
>). May render same-line or off-cursor depending on where on the line you clicked.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>13 — Docstring generation <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>import datetime
|
||
|
||
|
||
def parse_iso_datetime(s):
|
||
"""Parse an ISO 8601 datetime string into a datetime.datetime."""
|
||
return datetime.datetime.fromisoformat(s)
|
||
|
||
|
||
def parse_iso_date(s):
|
||
|
||
return datetime.date.fromisoformat(s)</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>Empty indented line under <code>def parse_iso_date(s):</code> (column 4).</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text inserting a one-line docstring matching the sibling's style.</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>14 — No-op suppression <span class="pill suppressed">suppressed</span></h3>
|
||
<pre><code>def add(a: int, b: int) -> int:
|
||
"""Return the sum of two integers."""
|
||
return a + b
|
||
|
||
|
||
def multiply(a: int, b: int) -> int:
|
||
"""Return the product of two integers."""
|
||
return a * b
|
||
|
||
|
||
def subtract(a: int, b: int) -> int:
|
||
"""Return a minus b."""
|
||
return a - b</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>End of <code>return a + b</code>.</p>
|
||
<div class="label">Expected</div>
|
||
<p>
|
||
<strong>Nothing</strong>. The code is already correct — either Mercury returns an identical reply or our
|
||
suppression branch drops the proposal. Channel should show "no-op" or skip lines, never a render.
|
||
</p>
|
||
<div class="label">Failure mode</div>
|
||
<p>Any visible suggestion that just replays the existing code is a false positive worth reporting.</p>
|
||
</div>
|
||
|
||
<h3>TypeScript</h3>
|
||
|
||
<div class="card">
|
||
<h3>ts_07 — Array transform completion <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>interface User {
|
||
id: number;
|
||
name: string;
|
||
active: boolean;
|
||
}
|
||
|
||
function getActiveUserNames(users: User[]): string[] {
|
||
return users
|
||
}
|
||
|
||
const sample: User[] = [
|
||
{ id: 1, name: "ada", active: true },
|
||
{ id: 2, name: "lin", active: false },
|
||
{ id: 3, name: "rin", active: true },
|
||
];
|
||
|
||
console.log(getActiveUserNames(sample));</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>End of <code>return users</code> inside <code>getActiveUserNames</code>.</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text completing the chain, e.g. <code>.filter(u => u.active).map(u => u.name)</code>.</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>ts_08 — Param type annotations <span class="pill off-cursor">off-cursor</span></h3>
|
||
<pre><code>function double(x: number): number {
|
||
return x * 2;
|
||
}
|
||
|
||
function add(a, b) {
|
||
return a + b;
|
||
}
|
||
|
||
function negate(x: number): number {
|
||
return -x;
|
||
}
|
||
|
||
function main(): void {
|
||
console.log(double(3));
|
||
console.log(add(2, 4));
|
||
console.log(negate(7));
|
||
}
|
||
|
||
main();</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>End of file (after <code>main();</code>).</p>
|
||
<div class="label">Expected</div>
|
||
<p>Decoration on the <code>add(a, b)</code> signature proposing the typed version.</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>ts_09 — React event handler <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>declare const React: {
|
||
useState: <T>(initial: T) => [T, (next: T) => void];
|
||
};
|
||
|
||
function Counter(): JSX.Element {
|
||
const [count, setCount] = React.useState<number>(0);
|
||
|
||
function handleClick() {
|
||
|
||
}
|
||
|
||
return (
|
||
<div>
|
||
<p>Count: {count}</p>
|
||
<button onClick={handleClick}>Increment</button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default Counter;</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>Empty indented line inside <code>handleClick</code> (column 4).</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text incrementing <code>count</code> via <code>setCount</code>.</p>
|
||
</div>
|
||
|
||
<h3>Go</h3>
|
||
|
||
<div class="card">
|
||
<h3>go_07 — Error handling block <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>package main
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
)
|
||
|
||
func loadConfig(path string) ([]byte, error) {
|
||
data, err := os.ReadFile(path)
|
||
|
||
return data, nil
|
||
}
|
||
|
||
func main() {
|
||
cfg, err := loadConfig("config.json")
|
||
if err != nil {
|
||
fmt.Println("error:", err)
|
||
return
|
||
}
|
||
fmt.Println(string(cfg))
|
||
}</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>Empty line right after <code>data, err := os.ReadFile(path)</code>.</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text proposing the canonical <code>if err != nil { return nil, err }</code>.</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>go_08 — Struct method body <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>package main
|
||
|
||
import "fmt"
|
||
|
||
type Rectangle struct {
|
||
Width float64
|
||
Height float64
|
||
}
|
||
|
||
func (r Rectangle) Perimeter() float64 {
|
||
return 2 * (r.Width + r.Height)
|
||
}
|
||
|
||
func (r Rectangle) Area() float64 {
|
||
|
||
}
|
||
|
||
func main() {
|
||
r := Rectangle{Width: 3, Height: 4}
|
||
fmt.Println("perimeter:", r.Perimeter())
|
||
fmt.Println("area:", r.Area())
|
||
}</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>Empty indented line inside <code>Area()</code>.</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text computing area from <code>Width</code> and <code>Height</code>.</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>go_09 — Goroutine + channel <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>package main
|
||
|
||
import "fmt"
|
||
|
||
func main() {
|
||
ch := make(chan int)
|
||
|
||
go func() {
|
||
|
||
}()
|
||
|
||
for v := range ch {
|
||
fmt.Println("got:", v)
|
||
}
|
||
}</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>Empty indented line inside the goroutine.</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text producing values onto the channel and closing it.</p>
|
||
</div>
|
||
|
||
<h3>Rust</h3>
|
||
|
||
<div class="card">
|
||
<h3>rs_07 — Match-arm completion <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>enum Shape {
|
||
Circle(f64),
|
||
Square(f64),
|
||
Rectangle(f64, f64),
|
||
Triangle(f64, f64),
|
||
}
|
||
|
||
fn area(s: &Shape) -> f64 {
|
||
match s {
|
||
Shape::Circle(r) => std::f64::consts::PI * r * r,
|
||
Shape::Square(side) => side * side,
|
||
|
||
}
|
||
}
|
||
|
||
fn main() {
|
||
let shapes = vec![
|
||
Shape::Circle(1.0),
|
||
Shape::Rectangle(2.0, 3.0),
|
||
Shape::Triangle(4.0, 5.0),
|
||
];
|
||
for s in &shapes {
|
||
println!("area = {}", area(s));
|
||
}
|
||
}</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>Empty indented line inside the <code>match</code> body, after the <code>Square</code> arm.</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text adding the missing <code>Rectangle</code> and <code>Triangle</code> arms.</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>rs_08 — Result/Option chaining <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>fn parse_int(s: &str) -> Option<i32> {
|
||
let n = s.trim()
|
||
Some(n * 2)
|
||
}
|
||
|
||
fn main() {
|
||
let inputs = [" 21 ", "not-a-number", "10"];
|
||
for s in &inputs {
|
||
match parse_int(s) {
|
||
Some(v) => println!("{} -> {}", s, v),
|
||
None => println!("{} -> skipped", s),
|
||
}
|
||
}
|
||
}</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>End of <code>let n = s.trim()</code> (no semicolon yet).</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text continuing the chain into a parsed <code>i32</code>.</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>rs_09 — Lifetime annotations <span class="pill off-cursor">off-cursor</span></h3>
|
||
<pre><code>fn longest(a: &str, b: &str) -> &str {
|
||
if a.len() >= b.len() {
|
||
a
|
||
} else {
|
||
b
|
||
}
|
||
}
|
||
|
||
fn main() {
|
||
let s1 = String::from("hello world");
|
||
let s2 = String::from("hi");
|
||
let out = longest(&s1, &s2);
|
||
println!("longest = {}", out);
|
||
}</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>End of file.</p>
|
||
<div class="label">Expected</div>
|
||
<p>Decoration on the <code>fn longest</code> signature proposing lifetime annotations.</p>
|
||
</div>
|
||
|
||
<h3>JavaScript</h3>
|
||
|
||
<div class="card">
|
||
<h3>js_07 — Async/await fetch <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>async function fetchUser(id) {
|
||
try {
|
||
|
||
} catch (err) {
|
||
console.error("fetchUser failed", err);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
async function main() {
|
||
const user = await fetchUser(42);
|
||
console.log("user:", user);
|
||
}
|
||
|
||
main();</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>Empty indented line inside the <code>try {</code> block (column 8).</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text completing the fetch + json parse.</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>js_08 — Express GET handler <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>const app = {
|
||
get: (_path, _handler) => app,
|
||
post: (_path, _handler) => app,
|
||
listen: (_port, cb) => cb && cb(),
|
||
};
|
||
|
||
const users = [
|
||
{ id: 1, name: "ada" },
|
||
{ id: 2, name: "lin" },
|
||
];
|
||
|
||
app.get("/users/:id", (req, res) => {
|
||
|
||
});
|
||
|
||
app.post("/users", (req, res) => {
|
||
const user = { id: users.length + 1, name: req.body.name };
|
||
users.push(user);
|
||
res.status(201).json(user);
|
||
});
|
||
|
||
app.listen(3000, () => console.log("listening on :3000"));</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>Empty indented line inside the GET handler (column 4).</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text proposing a get-by-id (lookup, 404, json response).</p>
|
||
</div>
|
||
|
||
<h3>SQL</h3>
|
||
|
||
<div class="card">
|
||
<h3>sql_07 — Missing JOIN <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>SELECT
|
||
c.name,
|
||
SUM(o.total) AS total_spent
|
||
FROM orders o
|
||
|
||
WHERE o.created_at >= '2026-01-01'
|
||
GROUP BY c.name
|
||
ORDER BY total_spent DESC
|
||
LIMIT 10;</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>End of the line <code>FROM orders o</code>.</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text completing the JOIN against <code>customers</code>.</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>sql_08 — WHERE filter <span class="pill same-line">same-line</span></h3>
|
||
<pre><code>SELECT id, email
|
||
FROM users
|
||
WHERE
|
||
ORDER BY last_login_at DESC;</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>End of the bare <code>WHERE</code> line.</p>
|
||
<div class="label">Expected</div>
|
||
<p>Ghost text proposing a predicate.</p>
|
||
</div>
|
||
|
||
<h3>Markdown (negative case)</h3>
|
||
|
||
<div class="card">
|
||
<h3>md_07 — Prose should stay quiet <span class="pill suppressed">suppressed</span></h3>
|
||
<pre><code># Mercury Edit 2 — Quick Notes
|
||
|
||
Mercury Edit 2 is a small, fast model trained to predict the user's
|
||
next single edit given the current file, cursor position, and recent
|
||
edit history. It targets latency under 200 ms on typical files and
|
||
returns a unified-diff-like patch scoped to a window around the cursor.
|
||
|
||
Unlike chat-style completions, the model is biased toward minimal,
|
||
local changes — finishing a function body, fixing a typo, propagating
|
||
a rename — rather than generating new files from scratch.</code></pre>
|
||
<div class="label">Cursor</div>
|
||
<p>End of the last sentence.</p>
|
||
<div class="label">Expected</div>
|
||
<p>
|
||
<strong>Nothing</strong>. If Mercury does propose a prose continuation it counts as a soft fail — we
|
||
don't want a code model writing README content.
|
||
</p>
|
||
</div>
|
||
|
||
<h2 id="troubleshooting">Troubleshooting</h2>
|
||
<blockquote class="warn">
|
||
<p>
|
||
If nothing happens when you type, open <strong>View → Output → "Kilo Code · Next Edit"</strong> and watch the
|
||
log. The pipeline is verbose enough that 90% of issues are obvious from the first few lines.
|
||
</p>
|
||
</blockquote>
|
||
|
||
<table>
|
||
<tr>
|
||
<th>Symptom</th>
|
||
<th>Likely cause</th>
|
||
<th>Fix</th>
|
||
</tr>
|
||
<tr>
|
||
<td>No log lines at all</td>
|
||
<td>Wrong model selected, or Dev Host wasn't reloaded after rebuild</td>
|
||
<td>
|
||
<kbd>Cmd</kbd>+<kbd>R</kbd> in the Dev Host; confirm <code>model = Mercury Next Edit (Inception)</code>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>skip — no API key resolved</code></td>
|
||
<td>Setting not saved</td>
|
||
<td>Re-paste the key in <code>nextEdit.apiKey</code>, press Enter, reload</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code><- 401 Unauthorized</code></td>
|
||
<td>Wrong key or wrong tier</td>
|
||
<td>Verify the key at platform.inceptionlabs.ai</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code><- 400 Bad Request</code></td>
|
||
<td>Prompt-shape regression (we shouldn't ship this, but if it happens during dev)</td>
|
||
<td>Capture the response body from the channel and ping the integration owner</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Suggestion shown for a wrong-looking model</td>
|
||
<td>
|
||
Selecting "Mercury Edit 2" routes through the classic FIM provider, not NES — that's by design (the
|
||
old behavior is preserved)
|
||
</td>
|
||
<td>Switch to "Mercury Next Edit (Inception)" to use the new pipeline</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Inline ghost text never appears, but logs show <code>RENDER</code></td>
|
||
<td>Another extension (Copilot, Tabnine) is winning the inline-completion race</td>
|
||
<td>Temporarily disable conflicting extensions in the Dev Host</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<h2 id="feedback">Feedback we'd love</h2>
|
||
<ul>
|
||
<li>
|
||
<strong>Where the prediction was wrong but the UX was correct.</strong> Note the file + cursor position + what
|
||
Mercury proposed. Helps us tune the model.
|
||
</li>
|
||
<li>
|
||
<strong>Where the UX got in the way.</strong> Tab semantics, decoration appearance, chained-prediction timing,
|
||
anything that felt clumsy compared to other NES products you've used.
|
||
</li>
|
||
<li>
|
||
<strong>Performance regressions in classic FIM autocomplete.</strong> The PR is supposed to leave the classic
|
||
path untouched — if Codestral or Mercury Edit 2 (FIM) feel different in this build, that's a regression
|
||
we want to know about.
|
||
</li>
|
||
<li>
|
||
<strong>Things you tried that aren't in this doc.</strong> The 20 tests are a starting point, not a contract.
|
||
Real codebases will be different.
|
||
</li>
|
||
</ul>
|
||
|
||
<div class="footer">
|
||
<p>
|
||
Mercury Next Edit integration for Kilo Code — prepared by the Inception Labs team.<br />
|
||
Questions / bug reports: <a href="mailto:firas@inceptionlabs.ai">firas@inceptionlabs.ai</a>.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>
|