POC: F5 launch config, README, and implementation plan (Feature #2)
.vscode launch/tasks wire F5 -> Run Extension (preLaunchTask: npm build). README documents run/dev. Plan archived under docs/superpowers/plans/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,468 @@
|
||||
# POC: Runnable VS Code Extension on `@cline/sdk` — Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Scaffold a standalone, non-shippable VS Code extension that builds, launches in the Extension Development Host on F5, and runs one command that drives `@cline/sdk` and shows a real result — validating Approach A (standalone extension on the Cline SDK, no fork).
|
||||
|
||||
**Architecture:** A CommonJS VS Code extension bundled with esbuild (`vscode` and `@cline/sdk` both kept external). `@cline/sdk` is ESM-only (Node ≥22) and uses `createRequire(import.meta.url)`, so it **cannot** be bundled into a CJS extension — instead it is shipped in `node_modules` and loaded at runtime via dynamic `import('@cline/sdk')`. The SDK-driving logic lives in a vscode-free module (`src/cline.ts`) so it is unit-testable in Node; the vscode command wiring lives in `src/extension.ts`. The command calls the SDK's pure `getCoreBuiltinToolCatalog()` + `CORE_BUILD_VERSION` (no API keys, no agent run) and renders the result.
|
||||
|
||||
**Tech Stack:** TypeScript, VS Code Extension API (`engines.vscode`), esbuild (bundler), vitest (unit test), `@cline/sdk@0.0.46`.
|
||||
|
||||
**De-risking already done (empirical, see session 0002 transcript):**
|
||||
- `@cline/sdk@0.0.46` loads in Node 24; `CORE_BUILD_VERSION === "0.0.46"`; `getCoreBuiltinToolCatalog()` returns 9 entries with shape `{id, description, headlessToolNames, defaultEnabled}` (e.g. `read_files`, `search_codebase`, `run_commands`, `editor`, `fetch_web_content`).
|
||||
- Bundling the SDK **into** CJS fails (`import.meta.url` undefined → `createRequire` throws). Keeping it external + dynamic `import()` from a CJS bundle works and yields a ~2KB extension bundle.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| File | Responsibility |
|
||||
| --- | --- |
|
||||
| `package.json` | Extension manifest: `main`, `engines.vscode`, `contributes.commands`, `activationEvents`, scripts, deps. |
|
||||
| `tsconfig.json` | TypeScript config (typecheck only; esbuild does the emit). |
|
||||
| `esbuild.mjs` | Bundle `src/extension.ts` → `out/extension.cjs`, externalize `vscode` + `@cline/sdk`. |
|
||||
| `src/cline.ts` | vscode-free SDK driver: `fetchSdkSummary()` dynamic-imports `@cline/sdk`, returns `{version, tools}`. Unit-testable. |
|
||||
| `src/extension.ts` | `activate`/`deactivate`; registers the `cowriting.showClineSdkInfo` command; renders the summary via notification + output channel. |
|
||||
| `test/cline.test.ts` | vitest unit test asserting `fetchSdkSummary()` returns a semver version and a non-empty tool list including `read_files`. |
|
||||
| `.vscode/launch.json` | F5 → "Run Extension" launches the Extension Development Host with this extension. |
|
||||
| `.vscode/tasks.json` | The `npm: build` task the launch config depends on. |
|
||||
| `.gitignore` | Ignore `node_modules/`, `out/`. |
|
||||
| `vitest.config.ts` | vitest node environment config. |
|
||||
| `README.md` | What the POC is + how to run it (F5). |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Project manifest and tooling config
|
||||
|
||||
**Files:**
|
||||
- Create: `package.json`
|
||||
- Create: `tsconfig.json`
|
||||
- Create: `.gitignore`
|
||||
- Create: `vitest.config.ts`
|
||||
|
||||
- [ ] **Step 1: Write `package.json`**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "vscode-cowriting-plugin",
|
||||
"displayName": "Cowriting (Cline SDK POC)",
|
||||
"description": "Non-shippable POC: a standalone VS Code extension that drives @cline/sdk.",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"publisher": "benstull",
|
||||
"engines": {
|
||||
"vscode": "^1.90.0",
|
||||
"node": ">=22"
|
||||
},
|
||||
"categories": ["Other"],
|
||||
"main": "./out/extension.cjs",
|
||||
"activationEvents": [],
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "cowriting.showClineSdkInfo",
|
||||
"title": "Cowriting: Show Cline SDK Info",
|
||||
"category": "Cowriting"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node esbuild.mjs",
|
||||
"watch": "node esbuild.mjs --watch",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"vscode:prepublish": "node esbuild.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cline/sdk": "0.0.46"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/vscode": "^1.90.0",
|
||||
"esbuild": "^0.23.0",
|
||||
"typescript": "^5.5.0",
|
||||
"vitest": "^2.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: `contributes.commands` declares the command without a `when`, so it appears in the Command Palette as soon as the extension activates. `activationEvents` is empty because declaring a command auto-generates `onCommand:` activation in VS Code ≥1.74.
|
||||
|
||||
- [ ] **Step 2: Write `tsconfig.json`**
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"noEmit": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src", "test", "esbuild.mjs"]
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Write `.gitignore`**
|
||||
|
||||
```
|
||||
node_modules/
|
||||
out/
|
||||
*.vsix
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Write `vitest.config.ts`**
|
||||
|
||||
```ts
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: "node",
|
||||
include: ["test/**/*.test.ts"],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Install dependencies**
|
||||
|
||||
Run: `npm install`
|
||||
Expected: `node_modules/` populated, `@cline/sdk` present, no error exit.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add package.json tsconfig.json .gitignore vitest.config.ts package-lock.json
|
||||
git commit -m "POC scaffold: extension manifest + tooling config (Feature #2)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: SDK driver module (TDD)
|
||||
|
||||
**Files:**
|
||||
- Create: `src/cline.ts`
|
||||
- Test: `test/cline.test.ts`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
`test/cline.test.ts`:
|
||||
|
||||
```ts
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { fetchSdkSummary } from "../src/cline";
|
||||
|
||||
describe("fetchSdkSummary", () => {
|
||||
it("loads @cline/sdk and returns its version and builtin tool catalog", async () => {
|
||||
const summary = await fetchSdkSummary();
|
||||
expect(summary.version).toMatch(/^\d+\.\d+\.\d+/);
|
||||
expect(summary.tools.length).toBeGreaterThan(0);
|
||||
expect(summary.tools.map((t) => t.id)).toContain("read_files");
|
||||
for (const tool of summary.tools) {
|
||||
expect(typeof tool.id).toBe("string");
|
||||
expect(typeof tool.description).toBe("string");
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `npm test`
|
||||
Expected: FAIL — cannot resolve `../src/cline` (module not created yet).
|
||||
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
`src/cline.ts`:
|
||||
|
||||
```ts
|
||||
/**
|
||||
* vscode-free driver for @cline/sdk.
|
||||
*
|
||||
* @cline/sdk is ESM-only (Node >=22) and uses createRequire(import.meta.url),
|
||||
* so it must NOT be bundled into the CJS extension. We load it at runtime via
|
||||
* dynamic import(); the bundler keeps it external (see esbuild.mjs).
|
||||
*/
|
||||
|
||||
export interface ClineTool {
|
||||
id: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface SdkSummary {
|
||||
version: string;
|
||||
tools: ClineTool[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Drive @cline/sdk with pure, key-free calls and return a renderable summary:
|
||||
* the SDK build version and the agent's builtin tool catalog. Proves the SDK
|
||||
* is linked and callable from the extension host.
|
||||
*/
|
||||
export async function fetchSdkSummary(): Promise<SdkSummary> {
|
||||
const sdk = await import("@cline/sdk");
|
||||
const catalog = sdk.getCoreBuiltinToolCatalog();
|
||||
return {
|
||||
version: sdk.CORE_BUILD_VERSION,
|
||||
tools: catalog.map((entry) => ({
|
||||
id: entry.id,
|
||||
description: entry.description,
|
||||
})),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `npm test`
|
||||
Expected: PASS — version matches semver, tools include `read_files`.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add src/cline.ts test/cline.test.ts
|
||||
git commit -m "POC: vscode-free @cline/sdk driver with unit test (Feature #2)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Extension entry point and command wiring
|
||||
|
||||
**Files:**
|
||||
- Create: `src/extension.ts`
|
||||
|
||||
- [ ] **Step 1: Write `src/extension.ts`**
|
||||
|
||||
```ts
|
||||
import * as vscode from "vscode";
|
||||
import { fetchSdkSummary } from "./cline";
|
||||
|
||||
const CHANNEL_NAME = "Cowriting (Cline SDK)";
|
||||
|
||||
export function activate(context: vscode.ExtensionContext): void {
|
||||
const output = vscode.window.createOutputChannel(CHANNEL_NAME);
|
||||
context.subscriptions.push(output);
|
||||
|
||||
const command = vscode.commands.registerCommand(
|
||||
"cowriting.showClineSdkInfo",
|
||||
async () => {
|
||||
try {
|
||||
const summary = await fetchSdkSummary();
|
||||
output.clear();
|
||||
output.appendLine(`@cline/sdk version: ${summary.version}`);
|
||||
output.appendLine(`Builtin tools (${summary.tools.length}):`);
|
||||
for (const tool of summary.tools) {
|
||||
output.appendLine(` • ${tool.id} — ${tool.description}`);
|
||||
}
|
||||
output.show(true);
|
||||
await vscode.window.showInformationMessage(
|
||||
`Cline SDK ${summary.version} loaded — ${summary.tools.length} builtin tools. See the "${CHANNEL_NAME}" output channel.`
|
||||
);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
output.appendLine(`Failed to drive @cline/sdk: ${message}`);
|
||||
output.show(true);
|
||||
await vscode.window.showErrorMessage(
|
||||
`Cowriting: failed to load @cline/sdk — ${message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
context.subscriptions.push(command);
|
||||
}
|
||||
|
||||
export function deactivate(): void {
|
||||
// Nothing to clean up beyond the disposables registered on the context.
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Typecheck**
|
||||
|
||||
Run: `npm run typecheck`
|
||||
Expected: PASS — no type errors (requires `@types/vscode` and `@types/node`).
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/extension.ts
|
||||
git commit -m "POC: extension activate + showClineSdkInfo command (Feature #2)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: esbuild bundle
|
||||
|
||||
**Files:**
|
||||
- Create: `esbuild.mjs`
|
||||
|
||||
- [ ] **Step 1: Write `esbuild.mjs`**
|
||||
|
||||
```js
|
||||
import { build, context } from "esbuild";
|
||||
|
||||
const watch = process.argv.includes("--watch");
|
||||
|
||||
/** @type {import('esbuild').BuildOptions} */
|
||||
const options = {
|
||||
entryPoints: ["src/extension.ts"],
|
||||
outfile: "out/extension.cjs",
|
||||
bundle: true,
|
||||
platform: "node",
|
||||
format: "cjs",
|
||||
target: "node20",
|
||||
sourcemap: true,
|
||||
// vscode is provided by the host; @cline/sdk is ESM-only and is loaded at
|
||||
// runtime via dynamic import() from node_modules, so keep both external.
|
||||
external: ["vscode", "@cline/sdk"],
|
||||
logLevel: "info",
|
||||
};
|
||||
|
||||
if (watch) {
|
||||
const ctx = await context(options);
|
||||
await ctx.watch();
|
||||
console.log("esbuild: watching…");
|
||||
} else {
|
||||
await build(options);
|
||||
console.log("esbuild: build complete → out/extension.cjs");
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Build**
|
||||
|
||||
Run: `npm run build`
|
||||
Expected: `out/extension.cjs` created; log "build complete". Bundle is small (~few KB) because the SDK is external.
|
||||
|
||||
- [ ] **Step 3: Verify the bundle keeps the SDK external**
|
||||
|
||||
Run: `node -e "const s=require('fs').readFileSync('out/extension.cjs','utf8'); console.log('has dynamic import of sdk:', s.includes('@cline/sdk')); console.log('bytes:', s.length)"`
|
||||
Expected: `has dynamic import of sdk: true`; byte count in the low thousands (SDK not inlined).
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add esbuild.mjs
|
||||
git commit -m "POC: esbuild bundle (vscode + @cline/sdk external) (Feature #2)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: F5 launch configuration
|
||||
|
||||
**Files:**
|
||||
- Create: `.vscode/launch.json`
|
||||
- Create: `.vscode/tasks.json`
|
||||
|
||||
- [ ] **Step 1: Write `.vscode/tasks.json`**
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "build",
|
||||
"group": "build",
|
||||
"problemMatcher": ["$esbuild"],
|
||||
"label": "npm: build",
|
||||
"detail": "Bundle the extension with esbuild"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Write `.vscode/launch.json`**
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Run Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
|
||||
"outFiles": ["${workspaceFolder}/out/**/*.cjs"],
|
||||
"preLaunchTask": "npm: build"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add .vscode/launch.json .vscode/tasks.json
|
||||
git commit -m "POC: F5 Run Extension launch config (Feature #2)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: README and verification
|
||||
|
||||
**Files:**
|
||||
- Create: `README.md`
|
||||
|
||||
- [ ] **Step 1: Write `README.md`**
|
||||
|
||||
```markdown
|
||||
# vscode-cowriting-plugin
|
||||
|
||||
Non-shippable **proof-of-concept** (Feature #2 of Epic #1): a standalone VS Code
|
||||
extension that drives **[`@cline/sdk`](https://www.npmjs.com/package/@cline/sdk)**
|
||||
— validating Approach A (own coauthoring extension on the Cline SDK, no fork).
|
||||
|
||||
## What it does
|
||||
|
||||
Registers one command, **`Cowriting: Show Cline SDK Info`**, which loads
|
||||
`@cline/sdk` and shows the SDK build version plus the agent's builtin tool
|
||||
catalog (a pure, key-free SDK call) in a notification and the
|
||||
"Cowriting (Cline SDK)" output channel.
|
||||
|
||||
## Architecture
|
||||
|
||||
- CommonJS extension bundled with esbuild (`src/extension.ts` → `out/extension.cjs`).
|
||||
- `@cline/sdk` is ESM-only (Node ≥22) and uses `createRequire(import.meta.url)`,
|
||||
so it is **not** bundled — it is shipped in `node_modules` and loaded at
|
||||
runtime via dynamic `import()` from the vscode-free `src/cline.ts`.
|
||||
|
||||
## Run it (F5)
|
||||
|
||||
1. `npm install`
|
||||
2. `npm run build`
|
||||
3. Press **F5** (or Run → "Run Extension") to launch the Extension Development Host.
|
||||
4. In the new window: **Cmd/Ctrl+Shift+P** → **"Cowriting: Show Cline SDK Info"**.
|
||||
|
||||
## Develop
|
||||
|
||||
- `npm run watch` — rebuild on change.
|
||||
- `npm test` — run the unit test for the SDK driver.
|
||||
- `npm run typecheck` — type-check without emit.
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Full verification sweep**
|
||||
|
||||
Run: `npm install && npm run typecheck && npm test && npm run build`
|
||||
Expected: install OK; typecheck OK; 1 test passes; build writes `out/extension.cjs`.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add README.md
|
||||
git commit -m "POC: README with run/dev instructions (Feature #2)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Manual / operator verification (cannot be automated in-session)
|
||||
|
||||
- [ ] Press **F5** in VS Code → Extension Development Host opens with no activation error.
|
||||
- [ ] Run **"Cowriting: Show Cline SDK Info"** → notification shows `Cline SDK 0.0.46 loaded — 9 builtin tools`, and the output channel lists the 9 tool IDs with descriptions.
|
||||
|
||||
This is the POC's acceptance: repo builds, F5 launches the host with the
|
||||
extension active, and one command drives `@cline/sdk` and shows a real result.
|
||||
Reference in New Issue
Block a user