@maccesar/aiskills 1.12.0 → 1.15.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 (59) hide show
  1. package/README.md +42 -7
  2. package/lib/cleanup.js +29 -0
  3. package/lib/commands/skills.js +110 -9
  4. package/lib/config.js +17 -9
  5. package/lib/installer.js +5 -3
  6. package/lib/symlink.js +45 -3
  7. package/lib/utils.js +41 -0
  8. package/package.json +1 -1
  9. package/skills/audit-codebase/SKILL.md +70 -0
  10. package/skills/audit-codebase/agents/openai.yaml +4 -0
  11. package/skills/audit-codebase/references/comprehensive-audit.md +220 -0
  12. package/skills/audit-codebase/references/report-format.md +119 -0
  13. package/skills/humaniza/SKILL.md +55 -4
  14. package/skills/humaniza/references/ai-patterns-es.md +40 -0
  15. package/skills/humaniza/references/checklist.md +9 -0
  16. package/skills/humaniza/references/examples.md +16 -0
  17. package/skills/humaniza/references/lexicon-es-mx.md +18 -0
  18. package/skills/humaniza/references/structures-es.md +132 -0
  19. package/skills/humaniza/scripts/check_ai_patterns.py +216 -0
  20. package/skills/refactoring-ui/SKILL.md +65 -29
  21. package/skills/refactoring-ui/references/05-motion.md +124 -0
  22. package/skills/refactoring-ui/references/06-dark-mode.md +117 -0
  23. package/skills/refactoring-ui/references/07-component-patterns.md +181 -0
  24. package/skills/stitch-showcase/SKILL.md +24 -232
  25. package/skills/stitch-showcase/references/07-theme-system.md +12 -0
  26. package/skills/stitch-showcase/references/08-type-detection.md +9 -1
  27. package/skills/stitch-showcase/references/10-component-standardization.md +25 -0
  28. package/skills/stitch-showcase/references/12-video-embedding.md +113 -0
  29. package/skills/stitch-showcase/references/13-language-detection.md +82 -0
  30. package/skills/stitch-showcase/references/14-troubleshooting-known-issues.md +122 -0
  31. package/skills/stitch-showcase/references/15-build-flags.md +71 -0
  32. package/skills/stitch-showcase/references/16-design-md-format.md +107 -0
  33. package/skills/stitch-showcase/references/index.html +25 -19
  34. package/skills/stitch-showcase/references/viewer.html +24 -12
  35. package/skills/stitch-showcase/scripts/__pycache__/build_showcase.cpython-314.pyc +0 -0
  36. package/skills/stitch-showcase/scripts/__pycache__/component_utils.cpython-314.pyc +0 -0
  37. package/skills/stitch-showcase/scripts/__pycache__/detect_components.cpython-314.pyc +0 -0
  38. package/skills/stitch-showcase/scripts/__pycache__/extract_catalog.cpython-314.pyc +0 -0
  39. package/skills/stitch-showcase/scripts/__pycache__/extract_text.cpython-314.pyc +0 -0
  40. package/skills/stitch-showcase/scripts/__pycache__/extract_zips.cpython-314.pyc +0 -0
  41. package/skills/stitch-showcase/scripts/__pycache__/parse_design_md.cpython-314.pyc +0 -0
  42. package/skills/stitch-showcase/scripts/__pycache__/slug_demangle.cpython-314.pyc +0 -0
  43. package/skills/stitch-showcase/scripts/build_showcase.py +150 -10
  44. package/skills/stitch-showcase/scripts/parse_design_md.py +145 -12
  45. package/skills/stitch-showcase/scripts/slug_demangle.py +209 -0
  46. package/skills/vscode-extension-dev/SKILL.md +90 -41
  47. package/skills/vscode-extension-dev/references/api-additional.md +168 -0
  48. package/skills/vscode-extension-dev/references/api-progress.md +55 -0
  49. package/skills/vscode-extension-dev/references/api-quickpick.md +75 -0
  50. package/skills/vscode-extension-dev/references/api-secretstorage.md +57 -0
  51. package/skills/vscode-extension-dev/references/api-statusbar.md +38 -0
  52. package/skills/vscode-extension-dev/references/api-treeview.md +78 -0
  53. package/skills/vscode-extension-dev/references/api-webview.md +149 -0
  54. package/skills/vscode-extension-dev/references/architecture.md +67 -0
  55. package/skills/vscode-extension-dev/references/debugger.md +179 -0
  56. package/skills/vscode-extension-dev/references/lsp.md +175 -0
  57. package/skills/vscode-extension-dev/references/notebooks.md +208 -0
  58. package/skills/vscode-extension-dev/references/testing.md +208 -0
  59. package/skills/vscode-extension-dev/references/api-patterns.md +0 -625
@@ -0,0 +1,175 @@
1
+ # Language Server Protocol (LSP)
2
+
3
+ Build a language extension by running an out-of-process **language server** and connecting it to VS Code via the `vscode-languageclient` npm package. The server can be written in any language; only the client lives inside the extension.
4
+
5
+ Official guide: https://code.visualstudio.com/api/language-extensions/language-server-extension-guide
6
+ Protocol spec: https://microsoft.github.io/language-server-protocol/
7
+
8
+ ## When to Use LSP vs Direct API
9
+
10
+ | Need | Use |
11
+ |---|---|
12
+ | Diagnostics, completion, hover for ONE editor (VS Code only) | Direct API (`vscode.languages.register*Provider`) |
13
+ | Same language features in VS Code + other LSP clients (Neovim, Sublime, etc.) | LSP |
14
+ | Heavy parsing/analysis you want isolated from the extension host | LSP (server runs in its own process) |
15
+ | Sharing logic with a CLI or CI tool | LSP (server is reusable) |
16
+
17
+ **Rule of thumb**: write LSP if the features are non-trivial OR you want portability. Use direct API for a one-off completion provider in a single language.
18
+
19
+ ## Project Structure
20
+
21
+ ```
22
+ my-language-ext/
23
+ ├── client/
24
+ │ ├── src/extension.ts # The VS Code extension (the client)
25
+ │ └── tsconfig.json
26
+ ├── server/
27
+ │ ├── src/server.ts # Language server (Node-based example)
28
+ │ └── tsconfig.json
29
+ ├── package.json # Extension manifest (covers both)
30
+ └── tsconfig.json # Root, with project references
31
+ ```
32
+
33
+ The extension's `package.json` lists `vscode-languageclient` as a runtime dependency and uses `activationEvents: ["onLanguage:<langId>"]`.
34
+
35
+ ## Client (extension side)
36
+
37
+ ```typescript
38
+ import * as path from 'node:path';
39
+ import * as vscode from 'vscode';
40
+ import {
41
+ LanguageClient,
42
+ LanguageClientOptions,
43
+ ServerOptions,
44
+ TransportKind,
45
+ } from 'vscode-languageclient/node';
46
+
47
+ let client: LanguageClient | undefined;
48
+
49
+ export function activate(context: vscode.ExtensionContext) {
50
+ const serverModule = context.asAbsolutePath(
51
+ path.join('server', 'out', 'server.js'),
52
+ );
53
+
54
+ const serverOptions: ServerOptions = {
55
+ run: { module: serverModule, transport: TransportKind.ipc },
56
+ debug: {
57
+ module: serverModule,
58
+ transport: TransportKind.ipc,
59
+ options: { execArgv: ['--nolazy', '--inspect=6009'] },
60
+ },
61
+ };
62
+
63
+ const clientOptions: LanguageClientOptions = {
64
+ documentSelector: [{ scheme: 'file', language: 'myLang' }],
65
+ synchronize: {
66
+ fileEvents: vscode.workspace.createFileSystemWatcher('**/.myLangConfig'),
67
+ },
68
+ };
69
+
70
+ client = new LanguageClient(
71
+ 'myLangServer',
72
+ 'My Language Server',
73
+ serverOptions,
74
+ clientOptions,
75
+ );
76
+
77
+ client.start();
78
+ }
79
+
80
+ export async function deactivate(): Promise<void> {
81
+ if (client) {
82
+ await client.stop();
83
+ }
84
+ }
85
+ ```
86
+
87
+ `TransportKind.ipc` is the recommended transport for Node-based servers (process IPC). For non-Node servers, spawn with `TransportKind.stdio` or `TransportKind.socket`.
88
+
89
+ ## Server (minimal Node example)
90
+
91
+ ```typescript
92
+ import {
93
+ createConnection,
94
+ TextDocuments,
95
+ ProposedFeatures,
96
+ InitializeParams,
97
+ TextDocumentSyncKind,
98
+ CompletionItem,
99
+ CompletionItemKind,
100
+ } from 'vscode-languageserver/node';
101
+ import { TextDocument } from 'vscode-languageserver-textdocument';
102
+
103
+ const connection = createConnection(ProposedFeatures.all);
104
+ const documents = new TextDocuments(TextDocument);
105
+
106
+ connection.onInitialize((_params: InitializeParams) => ({
107
+ capabilities: {
108
+ textDocumentSync: TextDocumentSyncKind.Incremental,
109
+ completionProvider: { resolveProvider: false, triggerCharacters: ['.'] },
110
+ hoverProvider: true,
111
+ },
112
+ }));
113
+
114
+ connection.onCompletion((_params): CompletionItem[] => [
115
+ { label: 'hello', kind: CompletionItemKind.Keyword },
116
+ ]);
117
+
118
+ connection.onHover((params) => {
119
+ const doc = documents.get(params.textDocument.uri);
120
+ if (!doc) return null;
121
+ return { contents: { kind: 'markdown', value: 'Hover content' } };
122
+ });
123
+
124
+ documents.listen(connection);
125
+ connection.listen();
126
+ ```
127
+
128
+ ## Capabilities You Typically Register
129
+
130
+ | Capability | Server method | Client receives |
131
+ |---|---|---|
132
+ | Completion | `onCompletion` | items shown in IntelliSense |
133
+ | Hover | `onHover` | tooltip on hover |
134
+ | Diagnostics | `connection.sendDiagnostics({ uri, diagnostics })` | Problems panel + squiggles |
135
+ | Definition | `onDefinition` | "Go to Definition" target |
136
+ | References | `onReferences` | "Find All References" results |
137
+ | Document symbols | `onDocumentSymbol` | breadcrumbs + outline |
138
+ | Formatting | `onDocumentFormatting` | "Format Document" |
139
+ | Code actions | `onCodeAction` | quick fixes / refactors |
140
+ | Rename | `onRenameRequest` | symbol rename |
141
+
142
+ Each capability must be declared in the `InitializeResult.capabilities` AND have a matching server handler. Mismatch = silent failure.
143
+
144
+ ## Diagnostics (Push Model)
145
+
146
+ Send diagnostics whenever a document changes:
147
+
148
+ ```typescript
149
+ documents.onDidChangeContent((event) => {
150
+ const diagnostics = validate(event.document);
151
+ connection.sendDiagnostics({
152
+ uri: event.document.uri,
153
+ diagnostics,
154
+ });
155
+ });
156
+ ```
157
+
158
+ Diagnostic structure:
159
+
160
+ ```typescript
161
+ {
162
+ severity: DiagnosticSeverity.Error, // Error | Warning | Information | Hint
163
+ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 10 } },
164
+ message: 'Unexpected token',
165
+ source: 'my-lang',
166
+ }
167
+ ```
168
+
169
+ ## Anti-Patterns
170
+
171
+ - ❌ Re-parsing the whole document on every keystroke — use `TextDocumentSyncKind.Incremental` and process deltas
172
+ - ❌ Heavy work inside `onInitialize` — it blocks editor startup; do it lazily on first request
173
+ - ❌ Forgetting to `await client.stop()` in `deactivate()` — leaves the server process alive
174
+ - ❌ Hardcoding paths in `serverModule` — always go through `context.asAbsolutePath`
175
+ - ❌ Mixing client and server types — `vscode-languageclient/node` is for the client, `vscode-languageserver/node` is for the server
@@ -0,0 +1,208 @@
1
+ # Notebook Extensions
2
+
3
+ VS Code supports notebooks (Jupyter-style) through three independent extension points: **serializers** (read/write file format), **controllers** (execute cells), and **renderers** (display rich output). You can implement any combination.
4
+
5
+ Official guide: https://code.visualstudio.com/api/extension-guides/notebook
6
+
7
+ ## The Three Roles
8
+
9
+ | Role | What it does | API |
10
+ |---|---|---|
11
+ | Serializer | Converts file bytes ↔ in-memory `NotebookData` | `NotebookSerializer` |
12
+ | Controller | Runs a cell, produces outputs | `NotebookController` |
13
+ | Renderer | Webview that displays a specific output mime type | `package.json#contributes.notebookRenderer` |
14
+
15
+ A `.ipynb`-style extension typically provides serializer + controller. A renderer-only extension can extend ANY notebook (e.g., render a custom plot mime type).
16
+
17
+ ## Notebook Serializer
18
+
19
+ Used to load and save the notebook file format.
20
+
21
+ ```typescript
22
+ import * as vscode from 'vscode';
23
+
24
+ class MyNotebookSerializer implements vscode.NotebookSerializer {
25
+ async deserializeNotebook(
26
+ content: Uint8Array,
27
+ _token: vscode.CancellationToken,
28
+ ): Promise<vscode.NotebookData> {
29
+ const text = new TextDecoder().decode(content);
30
+ const parsed = JSON.parse(text);
31
+
32
+ const cells = parsed.cells.map((c: any) =>
33
+ new vscode.NotebookCellData(
34
+ c.kind === 'code'
35
+ ? vscode.NotebookCellKind.Code
36
+ : vscode.NotebookCellKind.Markup,
37
+ c.source,
38
+ c.language ?? 'plaintext',
39
+ ),
40
+ );
41
+
42
+ return new vscode.NotebookData(cells);
43
+ }
44
+
45
+ async serializeNotebook(
46
+ data: vscode.NotebookData,
47
+ _token: vscode.CancellationToken,
48
+ ): Promise<Uint8Array> {
49
+ const out = {
50
+ cells: data.cells.map((c) => ({
51
+ kind: c.kind === vscode.NotebookCellKind.Code ? 'code' : 'markdown',
52
+ language: c.languageId,
53
+ source: c.value,
54
+ })),
55
+ };
56
+ return new TextEncoder().encode(JSON.stringify(out, null, 2));
57
+ }
58
+ }
59
+
60
+ export function activate(context: vscode.ExtensionContext) {
61
+ context.subscriptions.push(
62
+ vscode.workspace.registerNotebookSerializer(
63
+ 'my-notebook', // notebook type id (see package.json)
64
+ new MyNotebookSerializer(),
65
+ { transientOutputs: false }, // false = persist outputs in file
66
+ ),
67
+ );
68
+ }
69
+ ```
70
+
71
+ `package.json`:
72
+
73
+ ```json
74
+ "contributes": {
75
+ "notebooks": [
76
+ {
77
+ "type": "my-notebook",
78
+ "displayName": "My Notebook",
79
+ "selector": [{ "filenamePattern": "*.mynb" }]
80
+ }
81
+ ]
82
+ }
83
+ ```
84
+
85
+ ## Notebook Controller
86
+
87
+ Runs cells and produces outputs.
88
+
89
+ ```typescript
90
+ class MyController {
91
+ readonly controllerId = 'my-controller';
92
+ readonly notebookType = 'my-notebook';
93
+ readonly label = 'My Kernel';
94
+ readonly supportedLanguages = ['python', 'plaintext'];
95
+
96
+ private readonly _controller: vscode.NotebookController;
97
+ private _executionOrder = 0;
98
+
99
+ constructor() {
100
+ this._controller = vscode.notebooks.createNotebookController(
101
+ this.controllerId,
102
+ this.notebookType,
103
+ this.label,
104
+ );
105
+ this._controller.supportedLanguages = this.supportedLanguages;
106
+ this._controller.supportsExecutionOrder = true;
107
+ this._controller.executeHandler = this._execute.bind(this);
108
+ }
109
+
110
+ private async _execute(
111
+ cells: vscode.NotebookCell[],
112
+ _notebook: vscode.NotebookDocument,
113
+ _controller: vscode.NotebookController,
114
+ ): Promise<void> {
115
+ for (const cell of cells) {
116
+ const exec = this._controller.createNotebookCellExecution(cell);
117
+ exec.executionOrder = ++this._executionOrder;
118
+ exec.start(Date.now());
119
+
120
+ try {
121
+ const result = await this._runCell(cell);
122
+ await exec.replaceOutput([
123
+ new vscode.NotebookCellOutput([
124
+ vscode.NotebookCellOutputItem.text(result, 'text/plain'),
125
+ ]),
126
+ ]);
127
+ exec.end(true, Date.now());
128
+ } catch (err) {
129
+ await exec.replaceOutput([
130
+ new vscode.NotebookCellOutput([
131
+ vscode.NotebookCellOutputItem.error(err as Error),
132
+ ]),
133
+ ]);
134
+ exec.end(false, Date.now());
135
+ }
136
+ }
137
+ }
138
+
139
+ private async _runCell(cell: vscode.NotebookCell): Promise<string> {
140
+ // Real kernels: send cell.document.getText() to a backend, await result
141
+ return `Echo: ${cell.document.getText()}`;
142
+ }
143
+
144
+ dispose(): void {
145
+ this._controller.dispose();
146
+ }
147
+ }
148
+ ```
149
+
150
+ Register the controller in `activate()` and push to `context.subscriptions`.
151
+
152
+ ## Output Mime Types
153
+
154
+ Common mime types VS Code knows how to render natively:
155
+
156
+ - `text/plain`
157
+ - `text/markdown`
158
+ - `text/html`
159
+ - `image/png`, `image/jpeg`, `image/svg+xml`
160
+ - `application/json`
161
+ - `application/x.notebook.error` (use `NotebookCellOutputItem.error(err)`)
162
+ - `application/vnd.code.notebook.stdout` / `.stderr` (use `.stdout(str)` / `.stderr(str)`)
163
+
164
+ For anything else, ship a notebook renderer (next section).
165
+
166
+ ## Notebook Renderer
167
+
168
+ A renderer is a webview that takes a typed output and produces DOM. Declared entirely in `package.json` + a script that exports `activate`:
169
+
170
+ ```json
171
+ "contributes": {
172
+ "notebookRenderer": [
173
+ {
174
+ "id": "my-plot-renderer",
175
+ "displayName": "My Plot",
176
+ "entrypoint": "./out/renderer.js",
177
+ "mimeTypes": ["application/x.my-plot+json"]
178
+ }
179
+ ]
180
+ }
181
+ ```
182
+
183
+ `renderer.ts` — build the DOM with safe APIs, never inject untrusted strings as HTML:
184
+
185
+ ```typescript
186
+ import type { ActivationFunction } from 'vscode-notebook-renderer';
187
+
188
+ export const activate: ActivationFunction = () => ({
189
+ renderOutputItem(outputItem, element) {
190
+ const data = outputItem.json();
191
+
192
+ const pre = document.createElement('pre');
193
+ pre.textContent = JSON.stringify(data, null, 2);
194
+ element.replaceChildren(pre);
195
+ },
196
+ });
197
+ ```
198
+
199
+ Renderers run in a separate iframe — they cannot call `vscode.*` directly. Communicate with the controller via the messaging API exposed in `renderer.activate`.
200
+
201
+ ## Anti-Patterns
202
+
203
+ - ❌ Writing to disk synchronously in the serializer — VS Code calls it on the main thread
204
+ - ❌ Forgetting `exec.end(success, endTime)` — the cell stays in "running" state forever
205
+ - ❌ Reusing `executionOrder` numbers — must monotonically increase per notebook session
206
+ - ❌ Heavy DOM building in the renderer without batching — laggy scroll on large notebooks
207
+ - ❌ Bundling a notebook renderer with `vscode` as a dependency — renderers run in an iframe, not the extension host; they have NO `vscode` import
208
+ - ❌ Setting `innerHTML` from untrusted output data — XSS in the notebook viewer. Build DOM with `createElement` + `textContent`
@@ -0,0 +1,208 @@
1
+ # Testing Extensions (Advanced)
2
+
3
+ Beyond the basic Mocha setup covered in `architecture.md`: configuring `.vscode-test.mjs` for multiple suites, isolating unit tests from the VS Code runtime, mocking the `vscode` module, running tests in CI, and measuring coverage.
4
+
5
+ Official guide: https://code.visualstudio.com/api/working-with-extensions/testing-extension
6
+
7
+ ## Two Test Layers
8
+
9
+ | Layer | Runs in | Imports `vscode`? | Speed |
10
+ |---|---|---|---|
11
+ | Unit | Plain Node (Mocha/Vitest) | No — only `services/`, pure logic | Fast |
12
+ | Integration | VS Code Extension Host (Electron) via `@vscode/test-electron` | Yes | Slow |
13
+
14
+ Keep `services/` free of `vscode` imports so unit tests don't need the Electron runtime. Integration tests cover wiring (commands registered, providers connected, activation works).
15
+
16
+ ## `.vscode-test.mjs` (Multi-Suite)
17
+
18
+ The default file in `architecture.md` runs one suite. For real extensions, split by concern:
19
+
20
+ ```javascript
21
+ import { defineConfig } from '@vscode/test-cli';
22
+
23
+ export default defineConfig([
24
+ {
25
+ label: 'integration',
26
+ files: 'out/test/integration/**/*.test.js',
27
+ workspaceFolder: './test/fixtures/workspace-basic',
28
+ mocha: { timeout: 20000, ui: 'tdd' },
29
+ },
30
+ {
31
+ label: 'multi-root',
32
+ files: 'out/test/multi-root/**/*.test.js',
33
+ workspaceFolder: './test/fixtures/workspace-multi.code-workspace',
34
+ mocha: { timeout: 30000, ui: 'tdd' },
35
+ },
36
+ {
37
+ label: 'insiders',
38
+ version: 'insiders',
39
+ files: 'out/test/integration/**/*.test.js',
40
+ workspaceFolder: './test/fixtures/workspace-basic',
41
+ },
42
+ ]);
43
+ ```
44
+
45
+ Run a specific suite: `vscode-test --label integration`.
46
+
47
+ ## Workspace Fixtures
48
+
49
+ Integration tests need a real folder on disk because the editor expects a workspace:
50
+
51
+ ```
52
+ test/fixtures/
53
+ ├── workspace-basic/
54
+ │ ├── .vscode/
55
+ │ │ └── settings.json
56
+ │ ├── src/
57
+ │ │ └── sample.txt
58
+ │ └── package.json
59
+ └── workspace-multi.code-workspace
60
+ ```
61
+
62
+ ```json
63
+ // workspace-multi.code-workspace
64
+ {
65
+ "folders": [
66
+ { "path": "workspace-basic" },
67
+ { "path": "workspace-secondary" }
68
+ ]
69
+ }
70
+ ```
71
+
72
+ Mutating fixtures in a test? Copy to a temp dir in `suiteSetup` and point the test there — leaving committed fixtures dirty between runs causes flakes.
73
+
74
+ ## Mocking the `vscode` Module (Unit Tests)
75
+
76
+ `vscode` is provided by the Electron host — it has no npm package. Unit tests outside the host can't `import 'vscode'` directly. Two ways around this:
77
+
78
+ **1. Dependency injection** (preferred): never import `vscode` in `services/`. Pass the bits you need as parameters.
79
+
80
+ ```typescript
81
+ // services/processor.ts — pure, no vscode import
82
+ export interface FileReader {
83
+ read(path: string): Promise<string>;
84
+ }
85
+
86
+ export async function process(reader: FileReader, path: string): Promise<number> {
87
+ const text = await reader.read(path);
88
+ return text.split('\n').length;
89
+ }
90
+ ```
91
+
92
+ Test it with a fake reader. The VS Code layer adapts `vscode.workspace.fs` to the `FileReader` interface.
93
+
94
+ **2. Module aliasing** (when DI isn't feasible): map `vscode` to a stub via the test runner.
95
+
96
+ ```javascript
97
+ // vitest.config.ts (or jest moduleNameMapper)
98
+ import { defineConfig } from 'vitest/config';
99
+
100
+ export default defineConfig({
101
+ test: {
102
+ alias: {
103
+ vscode: new URL('./test/stubs/vscode.ts', import.meta.url).pathname,
104
+ },
105
+ },
106
+ });
107
+ ```
108
+
109
+ ```typescript
110
+ // test/stubs/vscode.ts — just enough surface to compile
111
+ export const window = {
112
+ showInformationMessage: (..._args: unknown[]) => Promise.resolve(undefined),
113
+ showErrorMessage: (..._args: unknown[]) => Promise.resolve(undefined),
114
+ activeTextEditor: undefined,
115
+ };
116
+ export const workspace = {
117
+ workspaceFolders: undefined as readonly unknown[] | undefined,
118
+ getConfiguration: () => ({ get: () => undefined }),
119
+ };
120
+ export const Uri = { file: (p: string) => ({ fsPath: p }) };
121
+ // add more as needed
122
+ ```
123
+
124
+ Aliasing is brittle — keep the stub minimal and prefer DI for non-trivial logic.
125
+
126
+ ## CI: GitHub Actions
127
+
128
+ VS Code tests need a display. On Linux runners, wrap with `xvfb`:
129
+
130
+ ```yaml
131
+ name: CI
132
+ on: [push, pull_request]
133
+
134
+ jobs:
135
+ test:
136
+ strategy:
137
+ matrix:
138
+ os: [ubuntu-latest, macos-latest, windows-latest]
139
+ runs-on: ${{ matrix.os }}
140
+ steps:
141
+ - uses: actions/checkout@v4
142
+ - uses: actions/setup-node@v4
143
+ with:
144
+ node-version: 20
145
+ - run: npm ci
146
+ - run: npm run compile
147
+ - name: Run tests (Linux)
148
+ if: runner.os == 'Linux'
149
+ run: xvfb-run -a npm test
150
+ - name: Run tests (non-Linux)
151
+ if: runner.os != 'Linux'
152
+ run: npm test
153
+ ```
154
+
155
+ ## Coverage
156
+
157
+ `@vscode/test-electron` runs in a real Electron process, so traditional Istanbul instrumentation can't simply wrap `node`. Use `c8` against the compiled output:
158
+
159
+ ```json
160
+ // package.json
161
+ "scripts": {
162
+ "test:coverage": "c8 --reporter=text --reporter=lcov vscode-test"
163
+ }
164
+ ```
165
+
166
+ `c8` reads V8's native coverage and produces an `lcov.info` you can upload to Codecov/Coveralls. Coverage of integration-test execution paths only — unit tests should be measured separately and merged.
167
+
168
+ ## Stable Test Patterns
169
+
170
+ ```typescript
171
+ import * as assert from 'assert';
172
+ import * as vscode from 'vscode';
173
+
174
+ suite('Activation', () => {
175
+ suiteSetup(async () => {
176
+ const ext = vscode.extensions.getExtension('publisher.my-extension');
177
+ assert.ok(ext, 'Extension not found — check publisher and name in package.json');
178
+ await ext!.activate();
179
+ });
180
+
181
+ test('registers expected commands', async () => {
182
+ const commands = await vscode.commands.getCommands(true);
183
+ for (const id of ['myExt.run', 'myExt.refresh']) {
184
+ assert.ok(commands.includes(id), `Missing command: ${id}`);
185
+ }
186
+ });
187
+
188
+ test('respects user setting', async () => {
189
+ const config = vscode.workspace.getConfiguration('myExt');
190
+ await config.update('mode', 'strict', vscode.ConfigurationTarget.Workspace);
191
+
192
+ // ...exercise the feature
193
+
194
+ await config.update('mode', undefined, vscode.ConfigurationTarget.Workspace);
195
+ });
196
+ });
197
+ ```
198
+
199
+ Always restore mutated settings/state in `teardown` or `suiteTeardown` — workspace state persists across test files.
200
+
201
+ ## Anti-Patterns
202
+
203
+ - ❌ Putting business logic that doesn't touch the editor inside `extension.ts` — forces every test to spin up Electron
204
+ - ❌ Sharing state between tests via module-level variables — order-dependent failures
205
+ - ❌ `setTimeout`/`sleep` to "wait for activation" — use `await ext.activate()` and the `onDidStartDebugSession` (or similar) events
206
+ - ❌ Hardcoding absolute paths in fixtures — breaks on CI runners
207
+ - ❌ Running `xvfb-run` on macOS/Windows — only needed on Linux
208
+ - ❌ Trusting that a fresh test instance is clean — clear workspace state and SecretStorage explicitly when behavior depends on them