From 90663ec0a002f5854ddb7457998be391032b76ef Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Tue, 18 Aug 2026 08:57:21 +0300 Subject: [PATCH] Surface repo-bootstrap commands as a tree node, not just Command Palette MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit repo-bootstrap-snippets shipped three real, working commands but made them Command Palette-only by design decision — no tree/menu presence. In live use this was the same discoverability gap already fixed twice this session for other features (missing Customize-Template feedback, missing agent-picker README pointer): a feature nobody finds because nothing in the UI points at it. Adds a "Repository Setup" node to the Changes tree (right after "OpenSpec Configuration") whose three leaf items invoke the existing commands unchanged, including their project-type QuickPick prompts. Co-Authored-By: Claude Sonnet 5 --- README.md | 2 +- .../repo-bootstrap-tree-ui/proposal.md | 53 +++++++++++++++++ .../smoke-test-notes.md | 17 ++++++ .../specs/vscode-extension/spec.md | 33 +++++++++++ .../changes/repo-bootstrap-tree-ui/tasks.md | 42 +++++++++++++ packages/extension/CHANGELOG.md | 10 ++++ packages/extension/README.md | 14 +++-- packages/extension/package.json | 2 +- .../extension/src/tree/changes-tree.test.ts | 37 +++++++++--- packages/extension/src/tree/changes-tree.ts | 59 ++++++++++++++++++- 10 files changed, 252 insertions(+), 17 deletions(-) create mode 100644 openspec/changes/repo-bootstrap-tree-ui/proposal.md create mode 100644 openspec/changes/repo-bootstrap-tree-ui/smoke-test-notes.md create mode 100644 openspec/changes/repo-bootstrap-tree-ui/specs/vscode-extension/spec.md create mode 100644 openspec/changes/repo-bootstrap-tree-ui/tasks.md diff --git a/README.md b/README.md index 12c0ba2f..1b1bf33e 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ release artifact. Current release versions are: | Package | Version | Release role | | --- | ---: | --- | | `@openspec-ui/core` | 0.18.0 | Shared behavior and persistence contract | -| `openspec-ui-vscode` | 0.12.1 | VS Code delivery | +| `openspec-ui-vscode` | 0.13.0 | VS Code delivery | | `@openspec-ui/server` | 1.7.0 | Standalone server delivery | | `@openspec-ui/webui` | 1.8.0 | Shared browser UI | | `@openspec-ui/cli` | 0.1.0 | CI merge-gate delivery | diff --git a/openspec/changes/repo-bootstrap-tree-ui/proposal.md b/openspec/changes/repo-bootstrap-tree-ui/proposal.md new file mode 100644 index 00000000..4af9efda --- /dev/null +++ b/openspec/changes/repo-bootstrap-tree-ui/proposal.md @@ -0,0 +1,53 @@ +## Why + +`repo-bootstrap-snippets` shipped three real, working commands +("Generate Agent Instructions", "Configure Dependabot", "Generate +Path-Scoped Copilot Instructions"), but by design.md decision made them +Command Palette-only — no tree or menu presence. In live use, this +turned out to be the same discoverability gap already fixed twice this +session for other features (Customize Template's missing feedback, +fixed in `customize-template-open-manifest`; the CLI-agent picker's +missing README pointer, fixed in `discoverable-agent-picker`): a +feature that works but that nobody finds because nothing in the UI +points at it. Unlike those two prior fixes, a README pointer alone +wasn't judged enough here — these commands need an actual visible entry +point in the tree, not just better docs. + +## What Changes + +- Adds a "Repository Setup" node at the top of the **Changes** tree + (right after "OpenSpec Configuration"), always present regardless of + workspace state. Expanding it lists the three existing commands as + leaf items; clicking one runs that exact command — including its + existing project-type `QuickPick` prompt, unchanged. +- No new commands, no duplicated project-type-selection logic: the tree + items are thin pointers at the commands that already existed + (`openspec-ui.generateAgentInstructions`, + `openspec-ui.configureDependabot`, + `openspec-ui.generateSubtypeInstructions`), registered in + `repo-bootstrap-snippets`. +- Archive tree unaffected — repository setup isn't archive-scoped, so it + only appears in **Changes**, matching how "OpenSpec Configuration" + already only appears there today. + +## Capabilities + +### New Capabilities + +(none) + +### Modified Capabilities + +- `vscode-extension`: the Changes tree now surfaces the three + repo-bootstrap commands as a visible node, not Command Palette-only. + +## Impact + +- `packages/extension/src/tree/changes-tree.ts` + (`RepoBootstrapRootTreeItem`, `RepoBootstrapActionTreeItem`, + `getRepoBootstrapActions()`, wired into + `ChangesTreeProvider.getChildren`). +- `packages/extension/src/tree/changes-tree.test.ts`. +- No change to `commands.ts` or `package.json` — the three commands + already existed and are invoked unchanged via `TreeItem.command`, the + same mechanism `EmptyTreeItem`'s "Initialize OpenSpec" already uses. diff --git a/openspec/changes/repo-bootstrap-tree-ui/smoke-test-notes.md b/openspec/changes/repo-bootstrap-tree-ui/smoke-test-notes.md new file mode 100644 index 00000000..d1b2cbde --- /dev/null +++ b/openspec/changes/repo-bootstrap-tree-ui/smoke-test-notes.md @@ -0,0 +1,17 @@ +# Smoke test — repo-bootstrap-tree-ui + +- Real Extension Host run (`npm run test:integration --workspace + openspec-ui-vscode`): 6/6 passing, extension activates and registers + all contributed commands (including the three repo-bootstrap commands + the new tree node points at) against a real VS Code instance. +- Real unit-test coverage of the actual tree logic (not a mock of it): + `changes-tree.test.ts` calls the real `ChangesTreeProvider` against a + mocked `discoverOpenSpecWorkspace` result and asserts the root children + include the "Repository Setup" node right after "OpenSpec + Configuration", and that expanding it returns exactly the three + expected actions with the correct `command.command` ids and + contextValue. +- No desktop-UI automation tool is available in this environment to + click-drive the actual tree in a running VS Code window — same, + already-documented limitation as `tasks-tree-expand`'s own + `smoke-test-notes.md`. diff --git a/openspec/changes/repo-bootstrap-tree-ui/specs/vscode-extension/spec.md b/openspec/changes/repo-bootstrap-tree-ui/specs/vscode-extension/spec.md new file mode 100644 index 00000000..b9b57e9f --- /dev/null +++ b/openspec/changes/repo-bootstrap-tree-ui/specs/vscode-extension/spec.md @@ -0,0 +1,33 @@ +## ADDED Requirements + +### Requirement: Changes tree surfaces repository-setup actions + +The "Changes" tree view SHALL show a "Repository Setup" node, always +present regardless of workspace initialization state, positioned +immediately after "OpenSpec Configuration". Expanding it SHALL list the +three repository-bootstrap actions ("Generate Agent Instructions", +"Configure Dependabot", "Generate Path-Scoped Copilot Instructions") as +child items; selecting one SHALL run the corresponding existing command +(`openspec-ui.generateAgentInstructions`, +`openspec-ui.configureDependabot`, `openspec-ui.generateSubtypeInstructions`) +unchanged, including its project-type `QuickPick` prompt. The "Archive" +tree SHALL NOT show this node. + +#### Scenario: Repository Setup node is always visible + +- **WHEN** the user opens the "Changes" tree, regardless of whether any + changes exist +- **THEN** a "Repository Setup" node is shown immediately after "OpenSpec + Configuration" + +#### Scenario: Selecting a repository-setup action runs its command + +- **WHEN** the user expands "Repository Setup" and selects "Generate + Agent Instructions" +- **THEN** the `openspec-ui.generateAgentInstructions` command runs, + including its existing project-type prompt + +#### Scenario: Archive tree has no Repository Setup node + +- **WHEN** the user opens the "Archive" tree +- **THEN** no "Repository Setup" node is shown diff --git a/openspec/changes/repo-bootstrap-tree-ui/tasks.md b/openspec/changes/repo-bootstrap-tree-ui/tasks.md new file mode 100644 index 00000000..9ad8423c --- /dev/null +++ b/openspec/changes/repo-bootstrap-tree-ui/tasks.md @@ -0,0 +1,42 @@ +## 1. Extension: Repository Setup tree node + +- [x] 1.1 `changes-tree.ts`: `RepoBootstrapRootTreeItem` (collapsible, + contextValue `openspec-ui.repoBootstrapRoot`), `RepoBootstrapActionTreeItem` + (leaf, contextValue `openspec-ui.repoBootstrapAction`, `.command` set + to the existing command id), `getRepoBootstrapActions()` factory (fresh + instances per call, matching how every other tree node is constructed + here — no shared mutable state). +- [x] 1.2 `ChangesTreeProvider.getChildren`: pushes a + `RepoBootstrapRootTreeItem` right after "OpenSpec Configuration" at + root level; expanding it returns `getRepoBootstrapActions()`. No + changes to `ArchiveTreeProvider` — it has its own independent root + logic and was never touched. +- [x] 1.3 No `commands.ts` or `package.json` changes — the three commands + already existed (`repo-bootstrap-snippets`) and are invoked unchanged + via `TreeItem.command`, same mechanism already used by `EmptyTreeItem`'s + "Initialize OpenSpec". +- [x] 1.4 `changes-tree.test.ts`: root children now include the + Repository Setup node (index shifted, existing tests updated); new + test expands it and asserts the three commands and contextValue. 6/6 + passing. + +## 2. Verification, versioning, and smoke test + +- [x] 2.1 `npm run typecheck && npm run lint && npm run test` passes for + `packages/extension`. Re-run `npm run verify` after `git add`/commit of + all new files. +- [x] 2.2 Bump `package.json` version (minor — new visible tree surface, + no contract break) for `openspec-ui-vscode` (0.12.1 → 0.13.0). Added a + `packages/extension/CHANGELOG.md` entry and updated the Features list + in both READMEs. +- [x] 2.3 Manual smoke test: real Extension Host run via + `npm run test:integration`, 6/6 passing (activation + command + registration verified against a real VS Code instance). Tree logic + itself (`ChangesTreeProvider.getChildren` returning the new node and + its children) is covered by real unit-test assertions against the + actual class, not a hand-simulated double — no desktop-UI automation + tool is available in this environment to click-drive the tree, per the + same limitation noted in prior changes this session + (`tasks-tree-expand/smoke-test-notes.md`). +- [x] 2.4 `openspec change validate --strict repo-bootstrap-tree-ui` + passes. diff --git a/packages/extension/CHANGELOG.md b/packages/extension/CHANGELOG.md index 55894330..e0c5f360 100644 --- a/packages/extension/CHANGELOG.md +++ b/packages/extension/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.13.0 + +- Added a "Repository Setup" node to the Changes tree, right after + "OpenSpec Configuration": expanding it lists "Generate Agent + Instructions", "Configure Dependabot", and "Generate Path-Scoped + Copilot Instructions" as clickable items. These commands already + existed (0.12.0) but were Command Palette-only with no tree presence — + this makes them discoverable without knowing the exact command name to + search for. + ## 0.12.1 - "Delete Task" is no longer offered for a task marked done, even in an diff --git a/packages/extension/README.md b/packages/extension/README.md index cf164bdc..504f31f9 100644 --- a/packages/extension/README.md +++ b/packages/extension/README.md @@ -38,12 +38,14 @@ notifications while keeping lifecycle and security behavior in template into any active change, or delete a project-level template with confirmation — built-in templates ship as part of the extension and are never deletable through the UI. -- Bootstrap repository files from the Command Palette, for a picked - project type (Node.js/TypeScript or Python): agent instructions - (`CLAUDE.md`/`AGENTS.md`), `.github/dependabot.yml`, and path-scoped - `.github/instructions/.instructions.md`. Any of these commands - leaves an existing file alone (and tells you so) if it wasn't created - by this extension — never overwrites hand-written content. +- Bootstrap repository files for a picked project type (Node.js/ + TypeScript or Python): agent instructions (`CLAUDE.md`/`AGENTS.md`), + `.github/dependabot.yml`, and path-scoped `.github/instructions/ + .instructions.md`. Reachable either from the Command Palette + or from the "Repository Setup" node at the top of the **Changes** + tree. Any of these commands leaves an existing file alone (and tells + you so) if it wasn't created by this extension — never overwrites + hand-written content. ## Requirements diff --git a/packages/extension/package.json b/packages/extension/package.json index 038943c9..43ee7523 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -3,7 +3,7 @@ "displayName": "OpenSpec Workbench", "description": "Native OpenSpec workflow with lifecycle controls, VS Code Chat, persistent processes, checkpoints, and conflict-safe rollback.", "publisher": "openspec-ui", - "version": "0.12.1", + "version": "0.13.0", "icon": "media/icon.png", "license": "MIT", "repository": { diff --git a/packages/extension/src/tree/changes-tree.test.ts b/packages/extension/src/tree/changes-tree.test.ts index 6cfdf0c9..d12dee73 100644 --- a/packages/extension/src/tree/changes-tree.test.ts +++ b/packages/extension/src/tree/changes-tree.test.ts @@ -35,11 +35,32 @@ describe("ChangesTreeProvider", () => { const provider = new ChangesTreeProvider("/workspace/repo"); const items = await provider.getChildren(); - expect(items).toHaveLength(3); + expect(items).toHaveLength(4); expect(items[0]?.contextValue).toBe("openspec-ui.config"); - expect(items[1]?.label).toBe("execution-core"); - expect(items[1]?.description).toBe("implemented"); - expect(items[2]?.description).toBe("in-progress"); + expect(items[1]?.contextValue).toBe("openspec-ui.repoBootstrapRoot"); + expect(items[2]?.label).toBe("execution-core"); + expect(items[2]?.description).toBe("implemented"); + expect(items[3]?.description).toBe("in-progress"); + }); + + it("expands the Repository Setup node to the three bootstrap actions", async () => { + discoverOpenSpecWorkspaceMock.mockResolvedValue({ + configPath: "/workspace/repo/openspec/config.yaml", + configExists: true, + changes: [], + }); + + const provider = new ChangesTreeProvider("/workspace/repo"); + const roots = await provider.getChildren(); + const bootstrapRoot = roots[1]; + const actions = await provider.getChildren(bootstrapRoot); + + expect(actions.map((item) => item.command?.command)).toEqual([ + "openspec-ui.generateAgentInstructions", + "openspec-ui.configureDependabot", + "openspec-ui.generateSubtypeInstructions", + ]); + expect(actions.every((item) => item.contextValue === "openspec-ui.repoBootstrapAction")).toBe(true); }); it("shows standard and delta artifacts under a change", async () => { @@ -60,7 +81,7 @@ describe("ChangesTreeProvider", () => { const provider = new ChangesTreeProvider("/workspace/repo"); const roots = await provider.getChildren(); - const change = roots[1]; + const change = roots[2]; const artifacts = await provider.getChildren(change); expect(artifacts.map((item) => item.label)).toEqual(["Proposal", "Design", "Spec: x"]); @@ -81,7 +102,7 @@ describe("ChangesTreeProvider", () => { const provider = new ChangesTreeProvider("/workspace/repo"); const roots = await provider.getChildren(); - const children = await provider.getChildren(roots[1]); + const children = await provider.getChildren(roots[2]); expect(readTaskChecklistMock).toHaveBeenCalledWith("/workspace/repo", "shared-ui", false); expect(children.map((item) => item.label)).toEqual(["1.1 First task", "1.2 Second task"]); @@ -103,8 +124,8 @@ describe("ChangesTreeProvider", () => { const provider = new ChangesTreeProvider("/workspace/repo"); const items = await provider.getChildren(); - expect(items[1]?.label).toBe("Initialize OpenSpec"); - expect(items[1]?.command?.command).toBe("openspec-ui.initialize"); + expect(items[2]?.label).toBe("Initialize OpenSpec"); + expect(items[2]?.command?.command).toBe("openspec-ui.initialize"); }); it("refresh() fires onDidChangeTreeData", () => { diff --git a/packages/extension/src/tree/changes-tree.ts b/packages/extension/src/tree/changes-tree.ts index 7f971067..c2d88fec 100644 --- a/packages/extension/src/tree/changes-tree.ts +++ b/packages/extension/src/tree/changes-tree.ts @@ -87,7 +87,60 @@ export class TaskTreeItem extends vscode.TreeItem { } } -export type WorkbenchTreeItem = ChangeTreeItem | ArtifactTreeItem | EmptyTreeItem | TaskTreeItem; +/** Groups the three repo-bootstrap Command Palette actions + * (`repo-bootstrap-snippets`) under a visible tree node — those commands + * previously had no tree/menu presence at all, which review found made + * them effectively undiscoverable. See + * openspec/changes/repo-bootstrap-tree-ui/proposal.md. */ +export class RepoBootstrapRootTreeItem extends vscode.TreeItem { + constructor() { + super("Repository Setup", vscode.TreeItemCollapsibleState.Collapsed); + this.description = "CLAUDE.md, dependabot.yml, ..."; + this.contextValue = "openspec-ui.repoBootstrapRoot"; + this.iconPath = new vscode.ThemeIcon("tools"); + } +} + +export class RepoBootstrapActionTreeItem extends vscode.TreeItem { + constructor(label: string, description: string, command: string, icon: string) { + super(label, vscode.TreeItemCollapsibleState.None); + this.description = description; + this.contextValue = "openspec-ui.repoBootstrapAction"; + this.iconPath = new vscode.ThemeIcon(icon); + this.command = { command, title: label }; + } +} + +export function getRepoBootstrapActions(): RepoBootstrapActionTreeItem[] { + return [ + new RepoBootstrapActionTreeItem( + "Generate Agent Instructions", + "CLAUDE.md / AGENTS.md", + "openspec-ui.generateAgentInstructions", + "book", + ), + new RepoBootstrapActionTreeItem( + "Configure Dependabot", + ".github/dependabot.yml", + "openspec-ui.configureDependabot", + "shield", + ), + new RepoBootstrapActionTreeItem( + "Generate Path-Scoped Copilot Instructions", + ".github/instructions/.instructions.md", + "openspec-ui.generateSubtypeInstructions", + "file-code", + ), + ]; +} + +export type WorkbenchTreeItem = + | ChangeTreeItem + | ArtifactTreeItem + | EmptyTreeItem + | TaskTreeItem + | RepoBootstrapRootTreeItem + | RepoBootstrapActionTreeItem; /** Shared by `ChangesTreeProvider` and `ArchiveTreeProvider` — both trees * expand a `ChangeTreeItem` the same way (artifacts, then tasks.md's @@ -131,6 +184,9 @@ export class ChangesTreeProvider implements vscode.TreeDataProvider