Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/publish-open-vsx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
tag:
description: Existing GitHub Release tag to publish
required: true
default: v0.2.9
default: v0.2.10
type: string

permissions:
Expand Down
151 changes: 128 additions & 23 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,51 +6,156 @@ These instructions apply to the entire repository.

## Project Overview

Copilot CLI Launcher is a small, privacy-first VS Code extension. It creates a fresh side terminal and starts a user-configured GitHub Copilot CLI command. The extension must remain unofficial, local-only, and free of telemetry, installers, or background network behavior.
Copilot CLI Launcher is a small, privacy-first VS Code extension published as a VSIX. It creates a
fresh side terminal and starts a user-configured GitHub Copilot CLI command. The extension must
remain unofficial, local-only, and free of telemetry, installers, or background network behavior.

The repository is **public** and MIT licensed. Treat everything committed here as world-readable:
no internal endpoints, customer data, credentials, or unreleased claims.

## Stack and Runtime

- TypeScript compiled by `tsc` (no bundler) to CommonJS-compatible `out/` via `module: NodeNext`,
`target: ES2022`, `strict: true`.
- Runtime: the VS Code extension host. Declared engine floor is `^1.103.0`; `@types/vscode` must
stay aligned with it.
- Tooling runtime: **Node.js 22** (the version CI uses).
- Package manager: **npm**, with the committed `package-lock.json`. Do not introduce a second
package manager, lockfile, or a `packageManager` field — a metadata test asserts its absence.
- npm install scripts are denied unless their exact package version appears in `allowScripts`.
Review the script before approving it with `npm approve-scripts`; never broaden an existing
approval or carry it to a new dependency version without re-reviewing that version.
- No runtime dependencies. Everything in `devDependencies` is build, test, or packaging tooling.

## Source Layout

- `src/extension.ts`: VS Code activation, commands, terminal lifecycle, prompts, and shell-integration wiring.
- `src/command-utils.ts`: pure command parsing, risk classification, output bounding, naming, and workspace resolution helpers.
- `test/`: Node unit and metadata tests plus the VS Code integration smoke test.
- `media/`: packaged product artwork.
- `.github/`: issue templates, pull request guidance, and CI.
- `src/extension.ts`: VS Code activation, commands, terminal lifecycle, prompts, and
shell-integration wiring. The only module allowed to import `vscode`.
- `src/command-utils.ts`: pure command parsing, risk classification, output bounding, naming,
disposable bookkeeping, and workspace resolution helpers. Must stay free of `vscode` imports so
it is unit-testable under plain Node.
- `test/command-utils.test.js`: `node:test` unit tests against the compiled `out/command-utils.js`.
- `test/metadata.test.js`: asserts package metadata, ignore rules, documentation, CI, and
source-level invariants. Version-sensitive — update it with every version bump.
- `test/integration/`: VS Code integration smoke test driven by `@vscode/test-electron`.
- `media/icon.png`: the single packaged product artwork, used for both the Marketplace listing and
the editor-title command icon.
- `docs/`: engineering notes and the release checklist.
- `.github/`: issue templates, pull request guidance, CI, and the Open VSX publish workflow.

## Required Commands

Use Node.js 22 and install the committed dependency graph:

```powershell
npm ci
npm run check
npm run audit
```

Build a release candidate only after those commands pass:
- `npm run check` = clean compile, unit + metadata tests, VS Code integration smoke test, and
`vsce ls` package-content validation. It is the single gate to run before any commit.
- Focused runs: `npm run test:unit`, `npm run test:integration`, `npm run test:metadata`,
`npm run test:command-utils`.
- Build a release candidate only after those pass:

```powershell
npm run package -- --out .vsce/review.vsix
npm run package -- --out .vsce/vscode-copilot-cli-launcher-<version>.vsix
```

Do not commit `node_modules/`, `out/`, `.vscode-test/`, `.vsce/`, logs, or generated `.vsix` files.
## Generated and Ignored Files

Never commit or hand-edit: `node_modules/`, `out/`, `out/**/*.map`, `.vscode-test/`, `.vsce/`,
`*.vsix`, `*.tsbuildinfo`, logs. `out/` is wiped by `npm run clean` on every compile, so edits
there are lost. Edit `package-lock.json` only through npm.

## Engineering Guardrails

- Keep TypeScript strict and move reusable decision logic into pure functions in `command-utils.ts` with focused tests.
- Preserve Workspace Trust checks and resolve `copilotCliLauncher.cliCommand` from user or machine configuration only. A workspace must not be able to choose the command that is executed.
- Send the configured command exactly once. Shell-integration and fallback paths must remain mutually exclusive.
- Keep TypeScript strict and move reusable decision logic into pure functions in
`command-utils.ts` with focused tests.
- Preserve Workspace Trust checks and resolve `copilotCliLauncher.cliCommand` from user or machine
configuration only. A workspace must not be able to choose the command that is executed.
- Send the configured command exactly once. Shell-integration and fallback paths must remain
mutually exclusive.
- Register per-launch listeners and timers through the disposable registry in
`command-utils.ts`, never directly on `context.subscriptions`. VS Code drains `subscriptions`
only at deactivate, so pushing there on every launch grows that array without bound and pins each
launch's captured terminal output. A metadata test enforces a single `context.subscriptions.push`
call in `extension.ts`.
- Keep captured terminal output bounded and in memory only; never persist command output.
- Do not add telemetry, analytics, filesystem scanning, child-process installers, package-manager execution, or automatic downloads.
- Use official GitHub documentation for Copilot CLI installation guidance. Do not bundle GitHub or Microsoft trademarks or artwork without permission.
- Do not add telemetry, analytics, filesystem scanning, child-process installers, package-manager
execution, or automatic downloads. `extension.ts` must not import `node:child_process`,
`node:fs`, `node:os`, or `node:path` — a metadata test asserts this.
- Use official GitHub documentation for Copilot CLI installation guidance. Do not bundle GitHub or
Microsoft trademarks or artwork without permission.
- Update tests, `README.md`, and `CHANGELOG.md` whenever user-visible behavior changes.

## GitHub and CI
## Compatibility Rules

- Keep workflow permissions minimal and pin third-party actions to immutable commit SHAs.
- Do not weaken `main` branch protection, required checks, review requirements, secret scanning, or push protection.
- Never place publishing tokens in files, commands, logs, pull requests, or shell history. Use environment variables supplied through an approved secret store.
- Do not commit, push, tag, create a release, or publish to a registry unless the user explicitly requests that action.
- No breaking changes without an explicit, authorized major-version decision. Setting IDs
(`copilotCliLauncher.cliCommand`, `copilotCliLauncher.terminalName`), command IDs
(`copilotCliLauncher.openCli`, `copilotCliLauncher.openSettings`), the publisher `mikesoft`, and
the extension name `vscode-copilot-cli-launcher` are part of the public contract.
- Raise `engines.vscode` only when a newly used API actually requires it, and align
`@types/vscode` in the same change.
- The extension supports Windows, macOS, and Linux. Keep path and quoting handling
platform-neutral and covered by tests.

## Assets

## Releases
`media/icon.png` is the product artwork and must not be redesigned, recolored, renamed, or moved.
It may only be losslessly recompressed, preserving dimensions, format, and appearance. A metadata
test requires it to stay at least 256x256 and under 256 KiB.

Keep the version synchronized in `package.json`, `package-lock.json`, `CITATION.cff`, tests, and `CHANGELOG.md`. Follow [`docs/RELEASING.md`](docs/RELEASING.md), wait for required CI checks, and verify every target registry after publication.
## Environment Variables and Secrets

The extension itself reads no environment variables and stores no secrets. Publishing tokens
(`VSCE_PAT` for the Visual Studio Marketplace, `OVSX_PAT` for Open VSX) exist only as CI secrets or
an operator's local environment. Never place them in files, command arguments, logs, pull requests,
or shell history, and never echo their values — check only for presence.

## GitHub and CI

- `main` is protected: required status checks `check (ubuntu-latest)` and `check (windows-latest)`,
one approving review, linear history, and required conversation resolution. Work on a dedicated
branch and open a pull request; never force-push, rewrite history, or bypass protection.
- Keep workflow permissions minimal and pin third-party actions to immutable commit SHAs.
- Do not weaken branch protection, required checks, review requirements, secret scanning, or push
protection.
- Do not commit, push, tag, create a release, or publish to a registry unless the user explicitly
requests that action.

## Versioning and Releases

Semantic versioning. Keep the version synchronized across `package.json`, `package-lock.json`
(both the root and `packages[""]` entries, via `npm version --no-git-tag-version`), `CITATION.cff`,
the version assertion in `test/metadata.test.js`, the default `tag` input of
`.github/workflows/publish-open-vsx.yml`, and `CHANGELOG.md` (Keep a Changelog format, with the
comparison links at the bottom).

Follow [`docs/RELEASING.md`](docs/RELEASING.md), wait for required CI checks, and verify every
target registry after publication:

- Visual Studio Marketplace: `mikesoft.vscode-copilot-cli-launcher`, published with `vsce`.
- Open VSX: `mikesoft/vscode-copilot-cli-launcher`, published by the **Publish Open VSX** workflow
from an existing GitHub Release asset, or with `ovsx` as an authorized local fallback.

## Mandatory Validation Before Handoff

1. `npm ci` succeeds from the committed lockfile.
2. `npm run check` passes end to end (compile, unit, metadata, integration, `vsce ls`).
3. `npm run audit` reports no high-severity advisories.
4. `npm run package` produces a VSIX whose file list contains only `out/*.js`, `media/icon.png`,
`package.json`, and the root documentation and licence files — no sources, tests, maps,
lockfile, or local tooling.
5. Version references are synchronized and `CHANGELOG.md` has a dated entry with real changes only.
6. `npm ci` emits no pending `allowScripts` warning; any newly requested install script is reviewed
and pinned to its exact package version before approval.

## Instructions for AI Agents

- Read this file, `README.md`, `CONTRIBUTING.md`, and `docs/RELEASING.md` before changing anything.
- Prefer small, reviewable diffs. Do not perform cosmetic refactors that enlarge the diff without
a concrete benefit.
- Never invent changelog entries, badges, statistics, links, or release results. Verify every
command and URL you document.
- Never delete user data, local `.vsce/` artwork sources, or untracked local files.
- Report failures with their real output instead of working around checks.
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

## [0.2.10] - 2026-08-01

### Fixed

- Stopped registering every launch's shell-integration listener, fallback timer, and shell-execution listener on the extension context, which VS Code releases only at deactivate. Repeated toolbar clicks appended entries that were never removed and kept each launch's captured terminal output reachable for the rest of the session; launch-scoped disposables now deregister themselves as soon as they are disposed.
- Closing the launcher terminal before its command is sent now cancels that launch's pending shell-integration wait, fallback timer, and missing-command listener instead of leaving them to fire against a terminal that no longer exists.

### Changed

- Extracted the launch disposable registry into `command-utils.ts` as a pure, unit-tested helper and named the shell-integration fallback delay constant.
- Documented the packaging, publishing, validation, and asset rules for the repository in `AGENTS.md`.
- Recorded the two reviewed release-tool install scripts with exact-version npm `allowScripts` entries and added a metadata regression test that keeps the manifest, lockfile, citation, changelog, and Open VSX workflow version synchronized.

### Added

- Unit tests covering the disposable registry and a metadata regression test asserting that per-launch listeners stay out of the never-drained extension subscriptions array.

## [0.2.9] - 2026-08-01

### Changed
Expand Down Expand Up @@ -186,5 +203,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Smart working directory resolution from the active editor.
- Windows, macOS, and Linux support.

[Unreleased]: https://github.com/TheStreamCode/github-copilot-cli-launcher/compare/v0.2.9...HEAD
[Unreleased]: https://github.com/TheStreamCode/github-copilot-cli-launcher/compare/v0.2.10...HEAD
[0.2.10]: https://github.com/TheStreamCode/github-copilot-cli-launcher/compare/v0.2.9...v0.2.10
[0.2.9]: https://github.com/TheStreamCode/github-copilot-cli-launcher/compare/v0.2.8...v0.2.9
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ authors:
- family-names: Gasperini
given-names: Michael
url: "https://github.com/TheStreamCode/github-copilot-cli-launcher"
version: "0.2.9"
version: "0.2.10"
license: MIT
6 changes: 3 additions & 3 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ Use this checklist for stable releases. Publishing is an external write and requ

Choose the next semantic version and keep it synchronized in:

- `package.json`
- `package-lock.json`
- `package.json` and `package-lock.json` (use `npm version <version> --no-git-tag-version`)
- `CITATION.cff`
- version-sensitive metadata tests
- the version assertion in `test/metadata.test.js`
- the default `tag` input of `.github/workflows/publish-open-vsx.yml`
- `CHANGELOG.md`

Move completed changelog entries from `Unreleased` into a dated release section and update the comparison links.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"displayName": "Copilot CLI Launcher — Run GitHub Copilot in a Side Terminal",
"description": "Launch the GitHub Copilot CLI coding agent in a side terminal from your editor toolbar — one click, fresh terminal. Unofficial; works in VS Code, Cursor & Windsurf on Windows, macOS & Linux.",
"publisher": "mikesoft",
"version": "0.2.9",
"version": "0.2.10",
"private": true,
"repository": {
"type": "git",
Expand Down Expand Up @@ -121,5 +121,9 @@
"@vscode/test-electron": "^3.0.0",
"@vscode/vsce": "^3.9.2",
"typescript": "^7.0.2"
},
"allowScripts": {
"@vscode/vsce-sign@2.0.9": true,
"keytar@7.9.0": true
}
}
57 changes: 57 additions & 0 deletions src/command-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,63 @@ export function shouldShowMissingCliGuidance(command: string, exitCode: number |
return buildCommandNotFoundPatterns(command).some((pattern) => pattern.test(output));
}

/** Minimal structural shape of a VS Code disposable, kept free of the `vscode` module. */
export type DisposableLike = { dispose(): void };

/** Tracks short-lived disposables and releases each entry as soon as it is disposed. */
export type DisposableRegistry = {
/** Registers `disposable` and returns a handle that disposes and deregisters it exactly once. */
track(disposable: DisposableLike): DisposableLike;
/** Disposes every entry that is still pending and empties the registry. */
disposeAll(): void;
/** Number of entries still pending. */
size(): number;
};

/**
* Creates a registry for disposables whose lifetime is shorter than the extension's.
*
* VS Code drains `ExtensionContext.subscriptions` only at deactivate, so pushing per-launch
* listeners and timers there grows that array on every launch and keeps their closures — including
* captured terminal output — alive for the rest of the session. Entries registered here deregister
* themselves the moment they are disposed, so the registry stays proportional to the work that is
* actually still in flight, while {@link DisposableRegistry.disposeAll} still guarantees cleanup at
* shutdown through a single `subscriptions` entry.
*/
export function createDisposableRegistry(): DisposableRegistry {
const pending = new Set<DisposableLike>();

return {
track(disposable: DisposableLike): DisposableLike {
const handle: DisposableLike = {
dispose: () => {
if (!pending.delete(handle)) {
return;
}

disposable.dispose();
},
};

pending.add(handle);

return handle;
},

disposeAll(): void {
for (const handle of [...pending]) {
handle.dispose();
}

pending.clear();
},

size(): number {
return pending.size;
},
};
}

/** Resolves the terminal cwd from the active editor or the first workspace folder. */
export function resolveTerminalCwd<T>(
activeEditor: ActiveEditorLike<T> | undefined,
Expand Down
Loading
Loading