mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 13:46:32 +03:00
Add declarative VS Code skill extension packaging (#775)
* Add declarative VS Code skill extension packaging Stage a launcher-only Copilot skill bundle, validate relocation and VSIX packaging, and keep project-install hooks out of the extension. AI assistance: Codex, under maintainer direction. * Document verified Copilot extension smoke test Record the successful read-only VS Code 1.136.1 run and the reload required after initial workspace trust. Keep minimum-version and remote smoke gaps explicit. AI assistance: Codex, under maintainer direction. * Fix VS Code packaging test version source Derive the provider fixture version from the skill manifest and assert the packaged skill and extension versions agree. AI assistance: Codex, under maintainer direction.
This commit is contained in:
@@ -99,6 +99,10 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: bun run build
|
run: bun run build
|
||||||
|
|
||||||
|
- name: Validate VS Code package
|
||||||
|
run: bunx --package @vscode/vsce@3.9.2 vsce package --no-dependencies
|
||||||
|
working-directory: dist/vscode
|
||||||
|
|
||||||
# `bun run build:extension` runs `cargo xtask bundle`: the rule core
|
# `bun run build:extension` runs `cargo xtask bundle`: the rule core
|
||||||
# compiled to wasm plus the page JS in browser-bundle/.
|
# compiled to wasm plus the page JS in browser-bundle/.
|
||||||
- name: Install the pinned toolchain
|
- name: Install the pinned toolchain
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# VS Code skill distribution
|
||||||
|
|
||||||
|
The VS Code extension is a declarative delivery channel for the GitHub Copilot skill. `bun run build` stages `dist/vscode/` from the GitHub provider output; `bun run package:vscode` builds and packages a VSIX with pinned `@vscode/vsce` tooling. Nothing is published by either command.
|
||||||
|
|
||||||
|
The package version follows `.claude-plugin/plugin.json`. Do not independently bump it for feature work. The proposed Marketplace identifier is `pbakaus.impeccable`; publisher ownership and initial publication are separate maintainer steps.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Register `skills/impeccable/SKILL.md` through `contributes.chatSkills`.
|
||||||
|
- Include its references, inline role fallbacks, launchers, and pinned engine version. Do not bundle engine binaries.
|
||||||
|
- Resolve launcher paths from the loaded skill, not `.github/skills` in the workspace. Preserve the project's working directory.
|
||||||
|
- No extension entrypoint, activation events, automatic hooks, custom-agent registrations, settings mutations, or workspace copying.
|
||||||
|
- Marketplace updates own the bundled files. No separate extension update command.
|
||||||
|
|
||||||
|
VS Code 1.109 introduced the contribution point; 1.109.3 added skill slash commands. The manifest therefore requires `^1.109.3`. Register the **SKILL.md file**, not the containing folder; early release-note examples used the folder form. Supporting files remain adjacent and are referenced by relative Markdown links.
|
||||||
|
|
||||||
|
Sources: [release notes](https://code.visualstudio.com/updates/v1_109), [contribution-point reference](https://code.visualstudio.com/api/references/contribution-points#contributes.chatSkills), [supporting-file clarification](https://github.com/microsoft/vscode/issues/304721#issuecomment-4152956892).
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
`node --test tests/vscode-extension.test.mjs` checks the generated manifest, linked resources, launcher relocation (including spaces and project cwd), lack of repo-install sidecars, and deterministic rebuilding. The core suite includes these checks. CI also runs `vsce package` on the staged directory.
|
||||||
|
|
||||||
|
Before publishing, install the VSIX into a separate VS Code profile and extensions directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
code --user-data-dir /absolute/path/to/test-profile \
|
||||||
|
--extensions-dir /absolute/path/to/test-extensions \
|
||||||
|
--install-extension dist/vscode/impeccable-<version>.vsix
|
||||||
|
code --user-data-dir /absolute/path/to/test-profile \
|
||||||
|
--extensions-dir /absolute/path/to/test-extensions \
|
||||||
|
/absolute/path/to/synthetic-project
|
||||||
|
```
|
||||||
|
|
||||||
|
Use an otherwise skill-free fixture with PRODUCT.md, DESIGN.md, and a small HTML file. Disable discovery of user-level Impeccable copies in that test profile to avoid testing the wrong installation. Sign into Copilot without enabling Settings Sync.
|
||||||
|
|
||||||
|
Check that `/impeccable` is discoverable, the loaded SKILL.md is inside the installed extension, a command-specific reference resolves there, and the launcher runs with cwd at the fixture. Record any requested command approvals. Also check that installation itself created no `.github/` directory or instructions file in the fixture. A launcher subprocess test alone is not a Copilot behavior test.
|
||||||
|
|
||||||
|
Test the oldest supported editor as well as current stable before publication. Windows and remote workspaces need separate smoke checks; do not infer them from a macOS local run. Plain browser-only VS Code cannot run the native launcher.
|
||||||
|
|
||||||
|
Initial macOS packaging checks: the VSIX installs in VS Code 1.109.3 (which resolves Copilot Chat 0.37.9) and 1.136.1. The installed launcher loads the synthetic project's context correctly. The older editor has only been install-tested, not behavior-tested.
|
||||||
|
|
||||||
|
### Recorded Copilot smoke (September 7, 2026)
|
||||||
|
|
||||||
|
VS Code 1.136.1, Copilot Chat 0.64.1, Auto routed to GPT-5.6 Luna. A single read-only `/impeccable polish index.html` compatibility request passed:
|
||||||
|
|
||||||
|
- `/impeccable` appeared in the slash picker after reloading the window following initial folder trust. Before that reload, the extension was installed but absent from the skill list.
|
||||||
|
- Copilot invoked the quoted absolute `skills/impeccable/scripts/impeccable` path inside the installed extension, with `context --target index.html`. One command approval was granted; default permission settings remained unchanged.
|
||||||
|
- The loader resolved the synthetic project root and its PRODUCT.md and DESIGN.md. Copilot then read `reference/polish.md` from the same extension, followed by the three fixture files.
|
||||||
|
- The completed report correctly described the fixture. The project still contained only its original three files, with unchanged contents. No workspace skill copy, server, or image-generation call was created.
|
||||||
|
|
||||||
|
This is one packaging/path-resolution smoke, not an activation-reliability or design-quality evaluation. It does not establish Windows, remote-host, or minimum-version behavior.
|
||||||
@@ -40,6 +40,7 @@
|
|||||||
"build": "bun run build:skills && mkdir -p build/_data && rm -rf build/_data/dist && cp -R dist build/_data/dist",
|
"build": "bun run build:skills && mkdir -p build/_data && rm -rf build/_data/dist && cp -R dist build/_data/dist",
|
||||||
"build:release": "bun run build:skills:release && mkdir -p build/_data && rm -rf build/_data/dist && cp -R dist build/_data/dist",
|
"build:release": "bun run build:skills:release && mkdir -p build/_data && rm -rf build/_data/dist && cp -R dist build/_data/dist",
|
||||||
"build:extension": "node scripts/build-extension.js",
|
"build:extension": "node scripts/build-extension.js",
|
||||||
|
"package:vscode": "bun run build:skills && cd dist/vscode && bunx --package @vscode/vsce@3.9.2 vsce package --no-dependencies",
|
||||||
"fetch:engine": "node scripts/fetch-engine.mjs",
|
"fetch:engine": "node scripts/fetch-engine.mjs",
|
||||||
"clean": "rm -rf dist build",
|
"clean": "rm -rf dist build",
|
||||||
"rebuild": "bun run clean && bun run build",
|
"rebuild": "bun run clean && bun run build",
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
verifyPluginAgentRewrite,
|
verifyPluginAgentRewrite,
|
||||||
} from './lib/plugin-paths.js';
|
} from './lib/plugin-paths.js';
|
||||||
import { stageOpenAIPlugin } from './lib/openai-plugin.js';
|
import { stageOpenAIPlugin } from './lib/openai-plugin.js';
|
||||||
|
import { stageVSCodeExtension } from './lib/vscode-extension.js';
|
||||||
import { ENGINE_TARGETS, binaryName, main as fetchEngineMain, readEngineVersion } from './fetch-engine.mjs';
|
import { ENGINE_TARGETS, binaryName, main as fetchEngineMain, readEngineVersion } from './fetch-engine.mjs';
|
||||||
// Sub-page generation is now handled by Astro content collections.
|
// Sub-page generation is now handled by Astro content collections.
|
||||||
|
|
||||||
@@ -895,6 +896,10 @@ async function build() {
|
|||||||
const openAiPluginRoot = stageOpenAIPlugin(ROOT_DIR, DIST_DIR);
|
const openAiPluginRoot = stageOpenAIPlugin(ROOT_DIR, DIST_DIR);
|
||||||
await createProviderZip(openAiPluginRoot, DIST_DIR, 'openai-plugin');
|
await createProviderZip(openAiPluginRoot, DIST_DIR, 'openai-plugin');
|
||||||
|
|
||||||
|
// Declarative Marketplace skill bundle: no editor runtime or project hooks.
|
||||||
|
// Staged before optional engine bundling to keep the VSIX launcher-only.
|
||||||
|
stageVSCodeExtension(ROOT_DIR, DIST_DIR);
|
||||||
|
|
||||||
// Release zips ship launcher-only by default: the launcher downloads the
|
// Release zips ship launcher-only by default: the launcher downloads the
|
||||||
// pinned engine on first run. IMPECCABLE_BUNDLE_ENGINE=1 opts in to staging
|
// pinned engine on first run. IMPECCABLE_BUNDLE_ENGINE=1 opts in to staging
|
||||||
// every fetched target into every dist skill copy for offline installs.
|
// every fetched target into every dist skill copy for offline installs.
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import { rewritePluginMarkdownTree } from './plugin-paths.js';
|
||||||
|
|
||||||
|
const PROJECT_SCRIPTS = '.github/skills/impeccable/scripts';
|
||||||
|
const PROJECT_FALLBACK =
|
||||||
|
`That base directory resolves every \`${PROJECT_SCRIPTS}/impeccable <verb>\` command in this skill and its references, ` +
|
||||||
|
`and \`${PROJECT_SCRIPTS}\` is the fallback only when the runtime reports no base directory.`;
|
||||||
|
|
||||||
|
// VS Code provides the loaded skill's URI, not Claude's substitution variables.
|
||||||
|
// Relative Markdown links resolve beside that file; shell commands still run
|
||||||
|
// from the user's project. Never fall back to another installed skill copy.
|
||||||
|
export function rewriteVSCodeMarkdown(content, { isSkillEntrypoint = false } = {}) {
|
||||||
|
if (isSkillEntrypoint) {
|
||||||
|
if (!content.includes(PROJECT_FALLBACK)) {
|
||||||
|
throw new Error('VS Code skill path rewrite drift: Setup fallback changed.');
|
||||||
|
}
|
||||||
|
content = content.replace(PROJECT_FALLBACK,
|
||||||
|
'Resolve the installed directory from this skill’s [launcher](scripts/impeccable). ' +
|
||||||
|
'Replace `<skill-base-dir>` with that absolute directory before running commands; it is not a shell variable.');
|
||||||
|
}
|
||||||
|
return content.replaceAll(PROJECT_SCRIPTS, '<skill-base-dir>/scripts')
|
||||||
|
.replace(/(?<!["\w/])<skill-base-dir>\/scripts\/impeccable(\.cmd)?(?=[\s`])/g,
|
||||||
|
'"<skill-base-dir>/scripts/impeccable$1"');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stage a declarative skill extension, independently of repo-install sidecars. */
|
||||||
|
export function stageVSCodeExtension(rootDir, distDir) {
|
||||||
|
const source = path.join(distDir, 'github', '.github', 'skills', 'impeccable');
|
||||||
|
const version = JSON.parse(fs.readFileSync(path.join(rootDir, '.claude-plugin/plugin.json'), 'utf8')).version;
|
||||||
|
const extensionRoot = path.join(distDir, 'vscode');
|
||||||
|
// Only this generated artifact is replaced. Never touch the user's editor.
|
||||||
|
fs.rmSync(extensionRoot, { recursive: true, force: true });
|
||||||
|
fs.mkdirSync(extensionRoot, { recursive: true });
|
||||||
|
const skillDir = path.join(extensionRoot, 'skills', 'impeccable');
|
||||||
|
fs.cpSync(source, skillDir, { recursive: true });
|
||||||
|
rewritePluginMarkdownTree(skillDir, rewriteVSCodeMarkdown);
|
||||||
|
|
||||||
|
const manifest = {
|
||||||
|
name: 'impeccable',
|
||||||
|
displayName: 'Impeccable',
|
||||||
|
description: 'Design skills for GitHub Copilot: build, critique, audit, and refine interfaces.',
|
||||||
|
version,
|
||||||
|
publisher: 'pbakaus',
|
||||||
|
license: 'Apache-2.0',
|
||||||
|
homepage: 'https://impeccable.style',
|
||||||
|
repository: { type: 'git', url: 'https://github.com/pbakaus/impeccable.git' },
|
||||||
|
bugs: { url: 'https://github.com/pbakaus/impeccable/issues' },
|
||||||
|
icon: 'icon.png',
|
||||||
|
categories: ['AI'],
|
||||||
|
keywords: ['copilot', 'design', 'skills', 'frontend', 'accessibility'],
|
||||||
|
engines: { vscode: '^1.109.3' },
|
||||||
|
extensionKind: ['workspace'],
|
||||||
|
extensionDependencies: ['GitHub.copilot-chat'],
|
||||||
|
capabilities: { untrustedWorkspaces: { supported: false }, virtualWorkspaces: false },
|
||||||
|
contributes: { chatSkills: [{ path: './skills/impeccable/SKILL.md' }] },
|
||||||
|
};
|
||||||
|
fs.writeFileSync(path.join(extensionRoot, 'package.json'), `${JSON.stringify(manifest, null, 2)}\n`);
|
||||||
|
fs.copyFileSync(path.join(rootDir, 'scripts/lib/assets/plugin-icon.png'), path.join(extensionRoot, 'icon.png'));
|
||||||
|
fs.copyFileSync(path.join(rootDir, 'LICENSE'), path.join(extensionRoot, 'LICENSE'));
|
||||||
|
fs.copyFileSync(path.join(rootDir, 'vscode/README.md'), path.join(extensionRoot, 'README.md'));
|
||||||
|
fs.copyFileSync(path.join(rootDir, 'vscode/.vscodeignore'), path.join(extensionRoot, '.vscodeignore'));
|
||||||
|
return extensionRoot;
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ export const SUITES = {
|
|||||||
/^skill\/(SKILL\.src\.md|agents\/|reference\/|scripts\/)/,
|
/^skill\/(SKILL\.src\.md|agents\/|reference\/|scripts\/)/,
|
||||||
/^ENGINE_VERSION$/,
|
/^ENGINE_VERSION$/,
|
||||||
/^README(\.npm)?\.md$/,
|
/^README(\.npm)?\.md$/,
|
||||||
|
/^vscode\//,
|
||||||
/^\.github\/workflows\/release-engine\.yml$/,
|
/^\.github\/workflows\/release-engine\.yml$/,
|
||||||
/^cli\/bin\//,
|
/^cli\/bin\//,
|
||||||
],
|
],
|
||||||
@@ -72,6 +73,7 @@ export const SUITES = {
|
|||||||
'tests/github-sheriff.test.mjs',
|
'tests/github-sheriff.test.mjs',
|
||||||
'tests/hook-build.test.mjs',
|
'tests/hook-build.test.mjs',
|
||||||
'tests/openai-plugin.test.mjs',
|
'tests/openai-plugin.test.mjs',
|
||||||
|
'tests/vscode-extension.test.mjs',
|
||||||
'tests/process-group.test.mjs',
|
'tests/process-group.test.mjs',
|
||||||
'tests/release.test.mjs',
|
'tests/release.test.mjs',
|
||||||
'tests/bundle-signing.test.mjs',
|
'tests/bundle-signing.test.mjs',
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { after, before, describe, it } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { execFileSync } from 'node:child_process';
|
||||||
|
|
||||||
|
import { readSourceFiles } from '../scripts/lib/utils.js';
|
||||||
|
import { createTransformer, PROVIDERS } from '../scripts/lib/transformers/index.js';
|
||||||
|
import { stageVSCodeExtension, rewriteVSCodeMarkdown } from '../scripts/lib/vscode-extension.js';
|
||||||
|
|
||||||
|
const repo = fileURLToPath(new URL('..', import.meta.url));
|
||||||
|
|
||||||
|
describe('VS Code skill extension', () => {
|
||||||
|
let scratch;
|
||||||
|
let extension;
|
||||||
|
before(() => {
|
||||||
|
scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'impeccable-vscode-test-'));
|
||||||
|
const { skills } = readSourceFiles(repo);
|
||||||
|
const skillsVersion = JSON.parse(fs.readFileSync(path.join(repo, '.claude-plugin/plugin.json'), 'utf8')).version;
|
||||||
|
createTransformer(PROVIDERS.github)(skills, scratch, { skillsVersion });
|
||||||
|
extension = stageVSCodeExtension(repo, scratch);
|
||||||
|
});
|
||||||
|
after(() => fs.rmSync(scratch, { recursive: true, force: true }));
|
||||||
|
|
||||||
|
it('registers only the skill, without editor runtime or project-install sidecars', () => {
|
||||||
|
const manifest = JSON.parse(fs.readFileSync(path.join(extension, 'package.json'), 'utf8'));
|
||||||
|
assert.deepEqual(manifest.contributes, { chatSkills: [{ path: './skills/impeccable/SKILL.md' }] });
|
||||||
|
for (const key of ['main', 'browser', 'activationEvents', 'scripts']) assert.equal(key in manifest, false);
|
||||||
|
assert.equal(manifest.version, JSON.parse(fs.readFileSync(path.join(repo, '.claude-plugin/plugin.json'))).version);
|
||||||
|
assert.deepEqual(fs.readdirSync(extension).sort(), ['.vscodeignore', 'LICENSE', 'README.md', 'icon.png', 'package.json', 'skills']);
|
||||||
|
assert.ok(fs.existsSync(path.join(extension, manifest.contributes.chatSkills[0].path)));
|
||||||
|
const skill = fs.readFileSync(path.join(extension, manifest.contributes.chatSkills[0].path), 'utf8');
|
||||||
|
assert.equal(skill.match(/^version: (.+)$/m)?.[1], manifest.version);
|
||||||
|
assert.ok(fs.existsSync(path.join(extension, 'skills/impeccable/reference/degraded/asset-producer.md')));
|
||||||
|
assert.equal(fs.existsSync(path.join(extension, 'skills/impeccable/scripts/bin')), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves linked resources inside the packaged skill and removes project launcher paths', () => {
|
||||||
|
const skill = path.join(extension, 'skills/impeccable');
|
||||||
|
let links = 0;
|
||||||
|
for (const rel of fs.readdirSync(skill, { recursive: true }).filter(p => p.endsWith('.md'))) {
|
||||||
|
const filename = path.join(skill, rel);
|
||||||
|
const content = fs.readFileSync(filename, 'utf8');
|
||||||
|
assert.doesNotMatch(content, /\.github\/skills\/impeccable\/scripts|\$\{CLAUDE_(?:SKILL_DIR|PLUGIN_ROOT)\}|\{\{scripts_path\}\}/);
|
||||||
|
for (const match of content.matchAll(/\]\(([^)\s]+)\)/g)) {
|
||||||
|
const link = match[1];
|
||||||
|
if (!/^(?:reference\/|scripts\/)/.test(link)) continue;
|
||||||
|
assert.ok(fs.existsSync(path.resolve(path.dirname(filename), link.split('#')[0])), `${rel}: ${link}`);
|
||||||
|
links++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.ok(links > 20, 'entrypoint retains its playbook links');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps POSIX and Windows paths quoted, with arguments outside the quotes', () => {
|
||||||
|
const output = rewriteVSCodeMarkdown('Run `.github/skills/impeccable/scripts/impeccable context` or `<skill-base-dir>/scripts/impeccable.cmd context`.');
|
||||||
|
assert.equal(output, 'Run `"<skill-base-dir>/scripts/impeccable" context` or `"<skill-base-dir>/scripts/impeccable.cmd" context`.');
|
||||||
|
assert.equal(rewriteVSCodeMarkdown(output), output);
|
||||||
|
assert.throws(() => rewriteVSCodeMarkdown('changed Setup', { isSkillEntrypoint: true }), /rewrite drift/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs the relocated launcher with spaces without changing project cwd or skill root', { skip: process.platform === 'win32' }, () => {
|
||||||
|
const installed = path.join(scratch, 'extension cache with spaces', 'skills', 'impeccable');
|
||||||
|
fs.cpSync(path.join(extension, 'skills/impeccable'), installed, { recursive: true });
|
||||||
|
const project = path.join(scratch, 'user project');
|
||||||
|
fs.mkdirSync(project);
|
||||||
|
const probe = path.join(scratch, 'probe engine');
|
||||||
|
fs.writeFileSync(probe, '#!/bin/sh\nprintf "%s\\n" "$PWD" "$IMPECCABLE_SKILL_DIR" "$IMPECCABLE_SELF" "$@"\n', { mode: 0o755 });
|
||||||
|
const launcher = path.join(installed, 'scripts/impeccable');
|
||||||
|
const env = { ...process.env, IMPECCABLE_BIN: probe };
|
||||||
|
delete env.IMPECCABLE_SKILL_DIR;
|
||||||
|
delete env.IMPECCABLE_SELF;
|
||||||
|
const output = execFileSync(launcher, ['context', '--target', 'a file.html'], { cwd: project, env, encoding: 'utf8' }).trim().split('\n');
|
||||||
|
assert.equal(fs.realpathSync(output[0]), fs.realpathSync(project));
|
||||||
|
assert.equal(fs.realpathSync(output[1]), fs.realpathSync(installed));
|
||||||
|
assert.deepEqual(output.slice(2), [launcher, 'context', '--target', 'a file.html']);
|
||||||
|
assert.deepEqual(fs.readdirSync(project), []);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rebuilds deterministically and removes stale generated files', () => {
|
||||||
|
const before = fs.readFileSync(path.join(extension, 'skills/impeccable/SKILL.md'));
|
||||||
|
fs.writeFileSync(path.join(extension, 'old.js'), 'stale');
|
||||||
|
stageVSCodeExtension(repo, scratch);
|
||||||
|
assert.equal(fs.existsSync(path.join(extension, 'old.js')), false);
|
||||||
|
assert.deepEqual(fs.readFileSync(path.join(extension, 'skills/impeccable/SKILL.md')), before);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
**
|
||||||
|
!package.json
|
||||||
|
!README.md
|
||||||
|
!LICENSE
|
||||||
|
!icon.png
|
||||||
|
!skills/**
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Impeccable for GitHub Copilot
|
||||||
|
|
||||||
|
Design guidance and tools for building, critiquing, auditing, and refining interfaces with GitHub Copilot in VS Code.
|
||||||
|
|
||||||
|
## Use
|
||||||
|
|
||||||
|
Requires VS Code 1.109.3 or later, GitHub Copilot Chat access, and a trusted local checkout. Open Copilot Chat in Agent mode and try:
|
||||||
|
|
||||||
|
- `/impeccable polish` to refine the current interface.
|
||||||
|
- `/impeccable audit` to inspect accessibility and implementation quality.
|
||||||
|
- `/impeccable shape` to plan a new interface.
|
||||||
|
|
||||||
|
Copilot can also discover the skill automatically for relevant design requests. Supporting references load only when needed.
|
||||||
|
|
||||||
|
If `/impeccable` is missing immediately after trusting a folder, run **Developer: Reload Window**, then try again.
|
||||||
|
|
||||||
|
## What gets installed
|
||||||
|
|
||||||
|
This extension bundles the skill, references, and engine launchers. It has no extension runtime, activation events, telemetry code, update command, or automatic detector hooks. Installing it does not copy files into your project or edit `copilot-instructions.md`. Marketplace updates replace the bundled skill.
|
||||||
|
|
||||||
|
When Copilot invokes an engine command, the launcher uses an available engine or downloads its pinned version from Impeccable's GitHub releases into your user cache. Downloads are checksum-verified. The engine runs on your machine with Copilot's normal command approval controls; no Node.js installation is required. The first download needs network access. On Windows without a POSIX shell, use the `.cmd` launcher (in PowerShell, invoke a quoted path with `&`).
|
||||||
|
|
||||||
|
Commands you request may edit your project or launch local previews. This is not an editor diagnostics extension, and it does not block saves. Native custom-agent sidecars and automatic hooks from other installation methods are not registered by this skill-only package; the skill includes its inline role fallbacks.
|
||||||
|
|
||||||
|
Avoid installing another Impeccable skill copy in the same workspace or user profile: duplicate skill names can make selection ambiguous. Browser-only VS Code without a terminal is unsupported. Remote SSH, WSL, and container setups need the extension and engine on the workspace host and are not yet smoke-tested.
|
||||||
|
|
||||||
|
[Documentation](https://impeccable.style/docs) · [Source and issues](https://github.com/pbakaus/impeccable)
|
||||||
Reference in New Issue
Block a user