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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
53 changes: 53 additions & 0 deletions openspec/changes/repo-bootstrap-tree-ui/proposal.md
Original file line number Diff line number Diff line change
@@ -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.
17 changes: 17 additions & 0 deletions openspec/changes/repo-bootstrap-tree-ui/smoke-test-notes.md
Original file line number Diff line number Diff line change
@@ -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`.
Original file line number Diff line number Diff line change
@@ -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
42 changes: 42 additions & 0 deletions openspec/changes/repo-bootstrap-tree-ui/tasks.md
Original file line number Diff line number Diff line change
@@ -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.
10 changes: 10 additions & 0 deletions packages/extension/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
14 changes: 8 additions & 6 deletions packages/extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<subtype>.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/
<subtype>.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

Expand Down
2 changes: 1 addition & 1 deletion packages/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
37 changes: 29 additions & 8 deletions packages/extension/src/tree/changes-tree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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"]);
Expand All @@ -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"]);
Expand All @@ -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", () => {
Expand Down
59 changes: 58 additions & 1 deletion packages/extension/src/tree/changes-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/<subtype>.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
Expand Down Expand Up @@ -131,6 +184,9 @@ export class ChangesTreeProvider implements vscode.TreeDataProvider<WorkbenchTre
if (element instanceof ChangeTreeItem) {
return getChangeChildren(this.workspaceRoot, element);
}
if (element instanceof RepoBootstrapRootTreeItem) {
return getRepoBootstrapActions();
}

if (element) return [];
const workspace = await discoverOpenSpecWorkspace(this.workspaceRoot);
Expand All @@ -143,6 +199,7 @@ export class ChangesTreeProvider implements vscode.TreeDataProvider<WorkbenchTre
"openspec-ui.config",
),
);
items.push(new RepoBootstrapRootTreeItem());
for (const change of workspace.changes) {
items.push(new ChangeTreeItem(change.name, change.path, change.state, change.artifacts, false));
}
Expand Down
Loading