Skip to content

feat(vscode): Phase 1 - skill bundle + Marketplace-ready extension shell#312

Open
PhillyUrbs wants to merge 4 commits into
pbakaus:mainfrom
PhillyUrbs:vscode-extension
Open

feat(vscode): Phase 1 - skill bundle + Marketplace-ready extension shell#312
PhillyUrbs wants to merge 4 commits into
pbakaus:mainfrom
PhillyUrbs:vscode-extension

Conversation

@PhillyUrbs

@PhillyUrbs PhillyUrbs commented Jun 26, 2026

Copy link
Copy Markdown

Phase 1 of the VS Code extension plan in #311: a new vscode provider that emits a Marketplace-packageable extension shell from the same transformer factory as the other harnesses.

Closes nothing yet - leaves #311 open for Phases 2-4.

What's in this PR

8 files, no behavior change for existing providers:

  • scripts/lib/transformers/providers.js - adds the vscode provider entry. Uses configDir .vscode-ext for the harness folder name; the literal .vscode is reserved for workspace settings.
  • scripts/lib/transformers/index.js - exports the new transformer.
  • scripts/build.js - wires the vscode provider through the existing factory; emits dist/vscode/ with package.json, extension.js, skills/impeccable/SKILL.md + reference/*.md, LICENSE, README.md, .vscodeignore.
  • scripts/lib/utils.js, scripts/test-suites.mjs - small wiring.
  • tests/vscode-extension.test.js - asserts the emitted extension shape (publisher, name, main, engines, bundled skill files).
  • package.json - adds build:extension:vscode script that just routes to scripts/build.js.
  • AGENTS.md - one-line addition of vscode to the Generated Provider Output Policy list.

Generated extension shape

dist/vscode/:

  • package.json - publisher pbakaus, name impeccable, displayName Impeccable, engines.vscode ^1.95.0, categories ["Chat", "AI"], license Apache-2.0, activationEvents ["onStartupFinished"], main ./extension.js.
  • extension.js - minimal CommonJS entrypoint. Registers the bundled SKILL.md so Copilot Chat picks it up; top-of-file comment documents the API choice for the targeted engines version.
  • skills/impeccable/SKILL.md + reference/*.md - same transform output the github provider produces.
  • LICENSE, README.md, .vscodeignore.

Verification

Build + test was run by the coding agent on the original PR in the PhillyUrbs fork (PhillyUrbs#1). After rebasing onto current main, the changes were re-pushed - the rebase was conflict-free and none of the 7 intervening upstream commits touch the Phase 1 files. I'll let this PR's CI re-run bun run build and bun run test as the live verification; happy to attach local logs if useful.

I also smoke-tested the built VSIX locally in VS Code Insiders - extension loads cleanly and /impeccable shows up in Copilot Chat.

Confirmed NOT touched

Per the issue's "no behavior fork" principle:

  • cli/engine/detect-antipatterns.mjs and the rest of cli/engine/
  • .github/hooks/
  • The Chrome extension at extension/
  • site/, functions/
  • Other provider configs
  • plugin/ and any tracked .claude/, .cursor/, .codex/ etc. harness folders

Deferred to later phases

Phases 2-4 from #311 are intentionally out of scope here:

  • Phase 2 - Detector as vscode.DiagnosticCollection provider, populated on save, honoring .impeccable/config.json and inline <!-- impeccable-disable --> comments.
  • Phase 3 - Save-time hook via workspace.onWillSaveTextDocument, matching the Cursor hook-before-edit.mjs consent model. Off by default.
  • Phase 4 - Detector as a Copilot Chat tool (vscode.lm.registerTool), command-palette wrappers for high-traffic sub-commands, and an Impeccable: Init command.

Notes for review

  • Publisher / extension id / display name - open questions from [Feature] VS Code extension (Self-Updating Copilot Chat integration) #311 (1, 3). Current shell uses pbakaus.impeccable + display name Impeccable; happy to change either before publish.
  • Engines field - targeting ^1.95.0 (Copilot Chat APIs stable). Lower if you want broader reach.
  • No marketplace icon yet - placeholder is fine for now; can pull from the Chrome extension's icons/ or generate a fresh one before publish.
  • Marketplace publish - not done in this PR. Once you're happy with the shape and the publisher question, the existing scripts/release.mjs pattern from Chrome can be extended for vsce publish.

Refs #311.


Note

Medium Risk
New generated extension and workspace file writes on install, but existing providers are gated by flags and covered by integration tests; no changes to detector/CLI runtime.

Overview
Adds a vscode provider and build step that emits a VSIX-ready package at dist/vscode/ (documented in AGENTS.md as separate from root harness folders).

buildVSCodeExtension writes the extension manifest, generated extension.js, Marketplace README/LICENSE, and copies transformed skills from staging .github/skills/ into skills/. The impeccable.installSkill command installs the full skill tree under .github/skills/impeccable/ and merges SKILL.md into a managed block in .github/copilot-instructions.md (preserving user content outside the markers).

The vscode provider uses configDir: '.github' so {{scripts_path}} resolves to workspace paths the install step creates; buildVSCodeExtension: true keeps it out of the universal zip and root harness sync (avoiding collision with the GitHub Copilot provider). package.json adds build:extension:vscode; core CI includes tests/vscode-extension.test.js for package layout, path baking, and install/merge idempotency.

Reviewed by Cursor Bugbot for commit ca56d83. Bugbot is set up for automated code reviews on this repo. Configure here.

@PhillyUrbs PhillyUrbs requested a review from pbakaus as a code owner June 26, 2026 01:57
Comment thread scripts/lib/transformers/providers.js
@greptile-apps

greptile-apps Bot commented Jun 26, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces Phase 1 of the VS Code extension: a new vscode provider that runs through the existing transformer factory and emits a Marketplace-ready extension shell at dist/vscode/. The generated extension.js exposes an Impeccable: Install Skill to Workspace command that copies the bundled skill tree into .github/skills/impeccable/ and merges SKILL.md into a managed block in .github/copilot-instructions.md (preserving existing user content). Filtering logic (buildVSCodeExtension flag) correctly excludes the vscode provider from the universal bundle and root-harness sync.

  • buildVSCodeExtension() assembles package.json, extension.js, skills/, LICENSE, README.md, and .vscodeignore into dist/vscode/ after the factory transform stages skill files under dist/vscode/.github/.
  • The generated extension.js uses fs.promises throughout (async I/O), and mergeInstructions correctly preserves pre-existing user content in copilot-instructions.md on every install.
  • A comprehensive test suite covers layout, path regression (.github/ vs the old .vscode-ext paths), idempotency, content-preservation, and mergeInstructions unit cases.

Confidence Score: 4/5

Safe to merge with one wiring fix: the vscode-specific placeholder entry in utils.js is bypassed, so {{model}} renders as 'the model' instead of 'Copilot' in all generated skill content.

The placeholder lookup for the vscode provider resolves to PROVIDER_PLACEHOLDERS['agents'] (model: 'the model') rather than the newly-added PROVIDER_PLACEHOLDERS['vscode'] (model: 'Copilot') because placeholderProvider: 'agents' takes precedence in factory.js. Every {{model}} occurrence in the generated vscode SKILL.md and reference files will produce a generic model reference rather than the Copilot-specific one. The rest of the build pipeline, filtering logic, async I/O, and test coverage are solid.

scripts/lib/utils.js and scripts/lib/transformers/providers.js — the PROVIDER_PLACEHOLDERS['vscode'] entry and the placeholderProvider value are inconsistent with each other.

Important Files Changed

Filename Overview
scripts/build.js Adds buildVSCodeExtension() and wires it into the main build() call; correctly filters the vscode provider from assembleUniversal and root-sync via the buildVSCodeExtension flag. Generated extension.js uses fs.promises throughout (async I/O). Previous silent-overwrite concern is resolved: mergeInstructions preserves existing user content.
scripts/lib/utils.js Adds PROVIDER_PLACEHOLDERS['vscode'] and PROVIDER_BLOCK_TAGS entry for 'vscode'. The PROVIDER_BLOCK_TAGS addition is correct (registers the tag so future <vscode> source blocks are recognized). However, the PROVIDER_PLACEHOLDERS['vscode'] entry is unreachable: the vscode provider sets placeholderProvider: 'agents', so the factory always resolves to PROVIDER_PLACEHOLDERS['agents'] instead.
scripts/lib/transformers/providers.js Adds vscode provider config with configDir: '.github' (correct for matching install paths), buildVSCodeExtension: true flag for filtering, and placeholderProvider: 'agents' — but the latter causes the vscode-specific PROVIDER_PLACEHOLDERS['vscode'] to be bypassed.
tests/vscode-extension.test.js New integration test suite covering layout assertions, path regression check for .github/ vs .vscode-ext, idempotency of installSkill, content-preservation, stale-file cleanup, and mergeInstructions unit tests. Well-structured with proper tmpdir teardown and a lazy require (called inside test callbacks, after beforeAll).
scripts/lib/transformers/index.js Adds transformVSCode named export for test spying, consistent with existing provider exports.
scripts/test-suites.mjs Adds vscode-extension to the test file regex pattern and to the test:core suite commands list.
package.json Adds build:extension:vscode script that routes to scripts/build.js --skip-root-sync.
AGENTS.md One-line addition documenting that the VS Code extension distributes as dist/vscode/ with no root harness folder.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[bun run build] --> B[readSourceFiles: skill/]
    B --> C{For each PROVIDER}
    C --> D[createTransformer config]
    D --> E[Transform skills → dist/provider/]
    E --> C
    C -->|vscode provider| F[dist/vscode/.github/skills/impeccable/]
    C -->|done| G[buildVSCodeExtension]
    F --> G
    G --> H[Copy skills/ from .github/skills/]
    G --> I[Write package.json]
    G --> J[Write extension.js]
    G --> K[Copy LICENSE + README.md]
    G --> L[Write .vscodeignore]
    H & I & J & K & L --> M[dist/vscode/ — VSIX-ready]
    C -->|all except buildVSCodeExtension flag| N[assembleUniversal]
    N --> O[dist/universal/]
    O --> P[universal.zip]

    subgraph UserInstall [User runs: Impeccable: Install Skill to Workspace]
      Q[installSkill called] --> R[Copy dist/vscode/skills/impeccable → .github/skills/impeccable/]
      R --> S[Read SKILL.md from installed location]
      S --> T[Read existing .github/copilot-instructions.md]
      T --> U{Has IMPECCABLE block?}
      U -->|Yes| V[Replace managed block only]
      U -->|No / empty| W[Append or create managed block]
      V & W --> X[Write merged copilot-instructions.md]
    end

    M --> Q
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[bun run build] --> B[readSourceFiles: skill/]
    B --> C{For each PROVIDER}
    C --> D[createTransformer config]
    D --> E[Transform skills → dist/provider/]
    E --> C
    C -->|vscode provider| F[dist/vscode/.github/skills/impeccable/]
    C -->|done| G[buildVSCodeExtension]
    F --> G
    G --> H[Copy skills/ from .github/skills/]
    G --> I[Write package.json]
    G --> J[Write extension.js]
    G --> K[Copy LICENSE + README.md]
    G --> L[Write .vscodeignore]
    H & I & J & K & L --> M[dist/vscode/ — VSIX-ready]
    C -->|all except buildVSCodeExtension flag| N[assembleUniversal]
    N --> O[dist/universal/]
    O --> P[universal.zip]

    subgraph UserInstall [User runs: Impeccable: Install Skill to Workspace]
      Q[installSkill called] --> R[Copy dist/vscode/skills/impeccable → .github/skills/impeccable/]
      R --> S[Read SKILL.md from installed location]
      S --> T[Read existing .github/copilot-instructions.md]
      T --> U{Has IMPECCABLE block?}
      U -->|Yes| V[Replace managed block only]
      U -->|No / empty| W[Append or create managed block]
      V & W --> X[Write merged copilot-instructions.md]
    end

    M --> Q
Loading

Reviews (4): Last reviewed commit: "fix(vscode): preserve user instructions,..." | Re-trigger Greptile

Comment thread scripts/build.js
Comment thread scripts/build.js Outdated
Comment thread tests/vscode-extension.test.js Outdated
PhillyUrbs added a commit to PhillyUrbs/impeccable that referenced this pull request Jun 26, 2026
Bugbot finding on PR pbakaus#312: SKILL.md and reference/*.md baked .vscode-ext/skills/impeccable/scripts/... paths into setup steps, but the install command only wrote SKILL.md to the workspace and never copied the scripts or reference files. Setup steps that run 'node .vscode-ext/skills/...' or link reference/*.md siblings failed with ENOENT.

- Provider configDir: .vscode-ext -> .github, so {{scripts_path}} bakes in as .github/skills/impeccable/scripts, matching the layout the install command materializes in the workspace.

- buildVSCodeExtension copies from .github/skills/ instead of .vscode-ext/skills/; .vscodeignore updated to exclude .github/ staging.

- extension.js install command copies the entire skills/impeccable/ tree (SKILL.md + reference/ + scripts/) into .github/skills/impeccable/ then writes .github/copilot-instructions.md mirroring SKILL.md so Copilot Chat auto-loads it. require('vscode') is lazy inside activate() so installSkill is unit-testable.

- Release-mode sync filter (.codex/.vscode-ext exclusion) switched to filter by the buildVSCodeExtension flag; the configDir-name filter would no longer exclude vscode now that its configDir is .github, which would overwrite the github provider's tracked harness output.

- Tests: assert installed paths match what SKILL.md references, simulate installSkill against a tmp workspace, assert SKILL.md + reference/ + scripts/ + copilot-instructions.md all land in .github/, assert prior installs are replaced not merged, assert no .vscode-ext/ strings remain in bundled markdown.
@PhillyUrbs

Copy link
Copy Markdown
Author

Addressed the Cursor Bugbot finding ("Bundled skill scripts path mismatch") in 68edc70.

Root cause: the install command only wrote SKILL.md into the workspace's .github/copilot-instructions.md, but SKILL.md and every reference/*.md baked in absolute references to .vscode-ext/skills/impeccable/scripts/... (from configDir: '.vscode-ext'). The workspace had no .vscode-ext/ directory, and the referenced reference/script siblings were never installed - so every setup step in the skill failed with ENOENT once Copilot tried to run it.

Fix (kept inside Phase 1 scope):

  • Provider configDir: .vscode-ext -> .github, so {{scripts_path}} resolves to .github/skills/impeccable/scripts, matching the layout the install command now creates.
  • extension.js install command materializes the full skills/impeccable/ tree (SKILL.md + reference/ + scripts/) into the workspace at .github/skills/impeccable/, then writes .github/copilot-instructions.md with the SKILL.md content so Copilot Chat still auto-loads it. require('vscode') is lazy inside activate(), which makes the new installSkill helper directly unit-testable.
  • buildVSCodeExtension copies from .github/skills/ instead of .vscode-ext/skills/; .vscodeignore excludes the new staging dir.
  • Important release-mode fix found while making the change: the existing release-sync filter excluded the vscode provider by configDir name (!== '.vscode-ext'). Now that configDir is .github, that filter would no longer match and the release sync would overwrite the github provider's tracked .github/skills/ harness output. Switched both the universal-bundle and release-sync filters to use the existing buildVSCodeExtension flag instead.

Test coverage added: simulates installSkill against a tmp workspace, asserts every script path SKILL.md references actually exists in the materialized tree, asserts .github/copilot-instructions.md mirrors the installed SKILL.md, asserts prior installs are replaced rather than merged, and asserts no .vscode-ext strings remain in any bundled markdown. The original PR's tests only checked the dist/ shape - they would not have caught this.

I don't have Bun on this machine so the local build + test gate didn't run; relying on CI here. Happy to add a screenshot of the install command running against a real workspace if useful.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 68edc70. Configure here.

Comment thread tests/vscode-extension.test.js
Comment thread scripts/build.js Outdated
PhillyUrbs added a commit to PhillyUrbs/impeccable that referenced this pull request Jun 26, 2026
… tests

Addresses the remaining Cursor Bugbot + Greptile review findings on PR pbakaus#312:

- Silent overwrite (Bugbot Medium + Greptile P1): installSkill no longer clobbers .github/copilot-instructions.md. The skill is written into an IMPECCABLE:BEGIN/END managed block; existing user content is preserved (appended above the block on first install, block replaced in place on re-install). New mergeInstructions() helper is pure + exported for unit testing.

- Core tests require missing build (Bugbot High): tests/vscode-extension.test.js now builds dist/vscode in a guarded beforeAll when extension.js is absent, so the core suite no longer assumes the Build step ran first. No CI YAML change needed.

- Synchronous file I/O (Greptile P2): installSkill and its helpers use fs.promises throughout; activate() awaits the command. Extension host event loop is no longer blocked.

- engines regex (Greptile P2): VSCODE_ENGINES_MIN_1_95 now also accepts 2.x+ (^2.0.0) while still rejecting <1.95.

Tests added: preserves pre-existing instructions, idempotent re-install (single managed block), mergeInstructions unit matrix (empty/blockless/prior-block).
@PhillyUrbs

Copy link
Copy Markdown
Author

Worked through the rest of the Bugbot + Greptile review in c6d49c5. Summary of each finding and how it's addressed:

1. Path mismatch (Bugbot High, original e8a77b8f finding) - already resolved in 68edc70f (configDir .vscode-ext -> .github). No further action; the re-anchored comment is stale.

2. Core tests require missing build (Bugbot High) - real: CI runs bun run test:core before the Build step, and vscode-extension.test.js (in the core suite) asserted dist/vscode/ exists, so a clean checkout would fail. Fixed by building dist/vscode in a guarded beforeAll (only when extension.js is absent, so a local run after bun run build is a no-op). The suite is now order-independent without a CI YAML change.

3. Silent overwrite of .github/copilot-instructions.md (Bugbot Medium + Greptile P1) - installSkill no longer clobbers the file. The skill content goes into an IMPECCABLE:BEGIN/END managed block: user content is preserved (kept above the block on first install; only the block is replaced on re-install). Extracted a pure, exported mergeInstructions() for direct unit testing.

4. Synchronous file I/O blocks the extension host (Greptile P2) - installSkill and its helpers now use fs.promises throughout, and activate() awaits the command. No more blocking calls on the extension-host event loop.

5. engines regex rejects ^2.0.0 (Greptile P2) - applied the suggested pattern; it now accepts ^1.95+ and ^2.x+ while still rejecting <1.95.

New test coverage: preserves pre-existing instructions, idempotent re-install (asserts a single managed block after two installs), and a mergeInstructions matrix (empty / blockless / prior-block inputs).

I verified the generated extension.js template parses and that mergeInstructions behaves correctly by extracting it from the build output and exercising it directly (Bun isn't on this machine, so CI is the authoritative build+test gate). The order-independence fix specifically targets making that CI run green on a clean checkout.

Bugbot finding on PR pbakaus#312: SKILL.md and reference/*.md baked .vscode-ext/skills/impeccable/scripts/... paths into setup steps, but the install command only wrote SKILL.md to the workspace and never copied the scripts or reference files. Setup steps that run 'node .vscode-ext/skills/...' or link reference/*.md siblings failed with ENOENT.

- Provider configDir: .vscode-ext -> .github, so {{scripts_path}} bakes in as .github/skills/impeccable/scripts, matching the layout the install command materializes in the workspace.

- buildVSCodeExtension copies from .github/skills/ instead of .vscode-ext/skills/; .vscodeignore updated to exclude .github/ staging.

- extension.js install command copies the entire skills/impeccable/ tree (SKILL.md + reference/ + scripts/) into .github/skills/impeccable/ then writes .github/copilot-instructions.md mirroring SKILL.md so Copilot Chat auto-loads it. require('vscode') is lazy inside activate() so installSkill is unit-testable.

- Release-mode sync filter (.codex/.vscode-ext exclusion) switched to filter by the buildVSCodeExtension flag; the configDir-name filter would no longer exclude vscode now that its configDir is .github, which would overwrite the github provider's tracked harness output.

- Tests: assert installed paths match what SKILL.md references, simulate installSkill against a tmp workspace, assert SKILL.md + reference/ + scripts/ + copilot-instructions.md all land in .github/, assert prior installs are replaced not merged, assert no .vscode-ext/ strings remain in bundled markdown.
… tests

Addresses the remaining Cursor Bugbot + Greptile review findings on PR pbakaus#312:

- Silent overwrite (Bugbot Medium + Greptile P1): installSkill no longer clobbers .github/copilot-instructions.md. The skill is written into an IMPECCABLE:BEGIN/END managed block; existing user content is preserved (appended above the block on first install, block replaced in place on re-install). New mergeInstructions() helper is pure + exported for unit testing.

- Core tests require missing build (Bugbot High): tests/vscode-extension.test.js now builds dist/vscode in a guarded beforeAll when extension.js is absent, so the core suite no longer assumes the Build step ran first. No CI YAML change needed.

- Synchronous file I/O (Greptile P2): installSkill and its helpers use fs.promises throughout; activate() awaits the command. Extension host event loop is no longer blocked.

- engines regex (Greptile P2): VSCODE_ENGINES_MIN_1_95 now also accepts 2.x+ (^2.0.0) while still rejecting <1.95.

Tests added: preserves pre-existing instructions, idempotent re-install (single managed block), mergeInstructions unit matrix (empty/blockless/prior-block).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants