From 42e916f409792e4086a7fb63796ae48846fe81ec Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Mon, 17 Aug 2026 17:24:05 +0300 Subject: [PATCH 1/2] Archive three shipped OpenSpec changes (tasks-tree-expand, repo-bootstrap-snippets, template-catalog-v2) All three merged with passing CI and every tasks.md checkbox already checked, but were never run through `openspec archive`, so their requirements never landed in the canonical specs. Doing this now so upcoming changes can write valid deltas against the specs they modify. Co-Authored-By: Claude Sonnet 5 --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../smoke-test-notes.md | 0 .../specs/repo-bootstrap-snippets/spec.md | 0 .../tasks.md | 0 .../.openspec.yaml | 0 .../2026-08-17-tasks-tree-expand}/design.md | 0 .../2026-08-17-tasks-tree-expand}/proposal.md | 0 .../smoke-test-notes.md | 0 .../specs/vscode-extension/spec.md | 0 .../2026-08-17-tasks-tree-expand}/tasks.md | 0 .../.openspec.yaml | 0 .../2026-08-17-template-catalog-v2}/design.md | 0 .../proposal.md | 0 .../smoke-test-notes.md | 0 .../specs/template-catalog/spec.md | 0 .../2026-08-17-template-catalog-v2}/tasks.md | 0 .../specs/repo-bootstrap-snippets/spec.md | 76 +++++++++++++++++++ openspec/specs/template-catalog/spec.md | 28 +++++++ openspec/specs/vscode-extension/spec.md | 42 ++++++++++ 21 files changed, 146 insertions(+) rename openspec/changes/{repo-bootstrap-snippets => archive/2026-08-17-repo-bootstrap-snippets}/.openspec.yaml (100%) rename openspec/changes/{repo-bootstrap-snippets => archive/2026-08-17-repo-bootstrap-snippets}/design.md (100%) rename openspec/changes/{repo-bootstrap-snippets => archive/2026-08-17-repo-bootstrap-snippets}/proposal.md (100%) rename openspec/changes/{repo-bootstrap-snippets => archive/2026-08-17-repo-bootstrap-snippets}/smoke-test-notes.md (100%) rename openspec/changes/{repo-bootstrap-snippets => archive/2026-08-17-repo-bootstrap-snippets}/specs/repo-bootstrap-snippets/spec.md (100%) rename openspec/changes/{repo-bootstrap-snippets => archive/2026-08-17-repo-bootstrap-snippets}/tasks.md (100%) rename openspec/changes/{tasks-tree-expand => archive/2026-08-17-tasks-tree-expand}/.openspec.yaml (100%) rename openspec/changes/{tasks-tree-expand => archive/2026-08-17-tasks-tree-expand}/design.md (100%) rename openspec/changes/{tasks-tree-expand => archive/2026-08-17-tasks-tree-expand}/proposal.md (100%) rename openspec/changes/{tasks-tree-expand => archive/2026-08-17-tasks-tree-expand}/smoke-test-notes.md (100%) rename openspec/changes/{tasks-tree-expand => archive/2026-08-17-tasks-tree-expand}/specs/vscode-extension/spec.md (100%) rename openspec/changes/{tasks-tree-expand => archive/2026-08-17-tasks-tree-expand}/tasks.md (100%) rename openspec/changes/{template-catalog-v2 => archive/2026-08-17-template-catalog-v2}/.openspec.yaml (100%) rename openspec/changes/{template-catalog-v2 => archive/2026-08-17-template-catalog-v2}/design.md (100%) rename openspec/changes/{template-catalog-v2 => archive/2026-08-17-template-catalog-v2}/proposal.md (100%) rename openspec/changes/{template-catalog-v2 => archive/2026-08-17-template-catalog-v2}/smoke-test-notes.md (100%) rename openspec/changes/{template-catalog-v2 => archive/2026-08-17-template-catalog-v2}/specs/template-catalog/spec.md (100%) rename openspec/changes/{template-catalog-v2 => archive/2026-08-17-template-catalog-v2}/tasks.md (100%) create mode 100644 openspec/specs/repo-bootstrap-snippets/spec.md diff --git a/openspec/changes/repo-bootstrap-snippets/.openspec.yaml b/openspec/changes/archive/2026-08-17-repo-bootstrap-snippets/.openspec.yaml similarity index 100% rename from openspec/changes/repo-bootstrap-snippets/.openspec.yaml rename to openspec/changes/archive/2026-08-17-repo-bootstrap-snippets/.openspec.yaml diff --git a/openspec/changes/repo-bootstrap-snippets/design.md b/openspec/changes/archive/2026-08-17-repo-bootstrap-snippets/design.md similarity index 100% rename from openspec/changes/repo-bootstrap-snippets/design.md rename to openspec/changes/archive/2026-08-17-repo-bootstrap-snippets/design.md diff --git a/openspec/changes/repo-bootstrap-snippets/proposal.md b/openspec/changes/archive/2026-08-17-repo-bootstrap-snippets/proposal.md similarity index 100% rename from openspec/changes/repo-bootstrap-snippets/proposal.md rename to openspec/changes/archive/2026-08-17-repo-bootstrap-snippets/proposal.md diff --git a/openspec/changes/repo-bootstrap-snippets/smoke-test-notes.md b/openspec/changes/archive/2026-08-17-repo-bootstrap-snippets/smoke-test-notes.md similarity index 100% rename from openspec/changes/repo-bootstrap-snippets/smoke-test-notes.md rename to openspec/changes/archive/2026-08-17-repo-bootstrap-snippets/smoke-test-notes.md diff --git a/openspec/changes/repo-bootstrap-snippets/specs/repo-bootstrap-snippets/spec.md b/openspec/changes/archive/2026-08-17-repo-bootstrap-snippets/specs/repo-bootstrap-snippets/spec.md similarity index 100% rename from openspec/changes/repo-bootstrap-snippets/specs/repo-bootstrap-snippets/spec.md rename to openspec/changes/archive/2026-08-17-repo-bootstrap-snippets/specs/repo-bootstrap-snippets/spec.md diff --git a/openspec/changes/repo-bootstrap-snippets/tasks.md b/openspec/changes/archive/2026-08-17-repo-bootstrap-snippets/tasks.md similarity index 100% rename from openspec/changes/repo-bootstrap-snippets/tasks.md rename to openspec/changes/archive/2026-08-17-repo-bootstrap-snippets/tasks.md diff --git a/openspec/changes/tasks-tree-expand/.openspec.yaml b/openspec/changes/archive/2026-08-17-tasks-tree-expand/.openspec.yaml similarity index 100% rename from openspec/changes/tasks-tree-expand/.openspec.yaml rename to openspec/changes/archive/2026-08-17-tasks-tree-expand/.openspec.yaml diff --git a/openspec/changes/tasks-tree-expand/design.md b/openspec/changes/archive/2026-08-17-tasks-tree-expand/design.md similarity index 100% rename from openspec/changes/tasks-tree-expand/design.md rename to openspec/changes/archive/2026-08-17-tasks-tree-expand/design.md diff --git a/openspec/changes/tasks-tree-expand/proposal.md b/openspec/changes/archive/2026-08-17-tasks-tree-expand/proposal.md similarity index 100% rename from openspec/changes/tasks-tree-expand/proposal.md rename to openspec/changes/archive/2026-08-17-tasks-tree-expand/proposal.md diff --git a/openspec/changes/tasks-tree-expand/smoke-test-notes.md b/openspec/changes/archive/2026-08-17-tasks-tree-expand/smoke-test-notes.md similarity index 100% rename from openspec/changes/tasks-tree-expand/smoke-test-notes.md rename to openspec/changes/archive/2026-08-17-tasks-tree-expand/smoke-test-notes.md diff --git a/openspec/changes/tasks-tree-expand/specs/vscode-extension/spec.md b/openspec/changes/archive/2026-08-17-tasks-tree-expand/specs/vscode-extension/spec.md similarity index 100% rename from openspec/changes/tasks-tree-expand/specs/vscode-extension/spec.md rename to openspec/changes/archive/2026-08-17-tasks-tree-expand/specs/vscode-extension/spec.md diff --git a/openspec/changes/tasks-tree-expand/tasks.md b/openspec/changes/archive/2026-08-17-tasks-tree-expand/tasks.md similarity index 100% rename from openspec/changes/tasks-tree-expand/tasks.md rename to openspec/changes/archive/2026-08-17-tasks-tree-expand/tasks.md diff --git a/openspec/changes/template-catalog-v2/.openspec.yaml b/openspec/changes/archive/2026-08-17-template-catalog-v2/.openspec.yaml similarity index 100% rename from openspec/changes/template-catalog-v2/.openspec.yaml rename to openspec/changes/archive/2026-08-17-template-catalog-v2/.openspec.yaml diff --git a/openspec/changes/template-catalog-v2/design.md b/openspec/changes/archive/2026-08-17-template-catalog-v2/design.md similarity index 100% rename from openspec/changes/template-catalog-v2/design.md rename to openspec/changes/archive/2026-08-17-template-catalog-v2/design.md diff --git a/openspec/changes/template-catalog-v2/proposal.md b/openspec/changes/archive/2026-08-17-template-catalog-v2/proposal.md similarity index 100% rename from openspec/changes/template-catalog-v2/proposal.md rename to openspec/changes/archive/2026-08-17-template-catalog-v2/proposal.md diff --git a/openspec/changes/template-catalog-v2/smoke-test-notes.md b/openspec/changes/archive/2026-08-17-template-catalog-v2/smoke-test-notes.md similarity index 100% rename from openspec/changes/template-catalog-v2/smoke-test-notes.md rename to openspec/changes/archive/2026-08-17-template-catalog-v2/smoke-test-notes.md diff --git a/openspec/changes/template-catalog-v2/specs/template-catalog/spec.md b/openspec/changes/archive/2026-08-17-template-catalog-v2/specs/template-catalog/spec.md similarity index 100% rename from openspec/changes/template-catalog-v2/specs/template-catalog/spec.md rename to openspec/changes/archive/2026-08-17-template-catalog-v2/specs/template-catalog/spec.md diff --git a/openspec/changes/template-catalog-v2/tasks.md b/openspec/changes/archive/2026-08-17-template-catalog-v2/tasks.md similarity index 100% rename from openspec/changes/template-catalog-v2/tasks.md rename to openspec/changes/archive/2026-08-17-template-catalog-v2/tasks.md diff --git a/openspec/specs/repo-bootstrap-snippets/spec.md b/openspec/specs/repo-bootstrap-snippets/spec.md new file mode 100644 index 00000000..ae00f353 --- /dev/null +++ b/openspec/specs/repo-bootstrap-snippets/spec.md @@ -0,0 +1,76 @@ +# repo-bootstrap-snippets Specification + +## Purpose +TBD - created by archiving change repo-bootstrap-snippets. Update Purpose after archive. +## Requirements +### Requirement: Agent instructions are generated identically into CLAUDE.md and AGENTS.md + +The system SHALL let the user generate a project-type-specific +instructions block, written identically into both `CLAUDE.md` and +`AGENTS.md` at the workspace root, delimited by managed-section markers. +If either file already exists without matching markers, the system +SHALL NOT modify that file and SHALL report it as not managed. + +#### Scenario: Neither file exists yet + +- **WHEN** the user generates agent instructions for a project type and + neither `CLAUDE.md` nor `AGENTS.md` exists +- **THEN** both files are created with identical managed-section content + +#### Scenario: A file already exists without the managed-section markers + +- **WHEN** `CLAUDE.md` already exists and does not contain the managed- + section markers +- **THEN** `CLAUDE.md` is left unmodified and the system reports it as + not managed, while `AGENTS.md` (if unmanaged or absent) is still + handled independently + +#### Scenario: Regenerating an already-managed file preserves content outside the markers + +- **WHEN** the user regenerates agent instructions for a file that + already has the managed-section markers, with user-authored content + after the closing marker +- **THEN** only the content between the markers is replaced; content + before the start marker and after the end marker is preserved verbatim + +### Requirement: Dependabot configuration accumulates ecosystems across invocations + +The system SHALL let the user generate or update `.github/dependabot.yml` +for one or more selected project types, using a first-line ownership +marker. Invoking it again for a different project type SHALL add that +ecosystem without removing ecosystems added by a prior invocation. A +`dependabot.yml` that exists without the ownership marker SHALL NOT be +modified. + +#### Scenario: First invocation creates the file + +- **WHEN** `.github/dependabot.yml` does not exist and the user selects + a project type +- **THEN** the file is created with that ecosystem's entry, the + ownership marker, and a `github-actions` entry + +#### Scenario: A later invocation adds another ecosystem + +- **WHEN** the file already exists (managed) with one ecosystem's entry + and the user selects a different project type +- **THEN** the regenerated file contains both ecosystems' entries + +#### Scenario: An unmanaged dependabot.yml is left alone + +- **WHEN** `.github/dependabot.yml` exists without the ownership marker +- **THEN** the system makes no change and reports the file as not + managed + +### Requirement: Path-scoped Copilot instructions are generated per subtype + +The system SHALL let the user generate +`.github/instructions/.instructions.md` for a selected project +type and subtype, with `applyTo` frontmatter and managed-section content, +using the same file-ownership rule as `CLAUDE.md`/`AGENTS.md`. + +#### Scenario: Generating instructions for a subtype + +- **WHEN** the user selects a project type and a subtype +- **THEN** `.github/instructions/.instructions.md` is created + with `applyTo` frontmatter and the subtype's managed content + diff --git a/openspec/specs/template-catalog/spec.md b/openspec/specs/template-catalog/spec.md index 71dbc77d..b78eb9c5 100644 --- a/openspec/specs/template-catalog/spec.md +++ b/openspec/specs/template-catalog/spec.md @@ -128,3 +128,31 @@ manifest without requiring a separate extension. violates the schema - **THEN** VS Code's native editor surfaces the validation error inline +### Requirement: Project-level templates can be deleted through the product + +The system SHALL let the user permanently delete a project-level +template (`openspec/templates//` and its contents) through both +delivery targets, after an explicit confirmation step. Built-in +templates SHALL NOT offer a delete action anywhere in either delivery +target's UI — they have no on-disk representation in the target +workspace to delete. + +#### Scenario: Deleting an existing project-level template + +- **WHEN** the user confirms deletion of project-level template `X` +- **THEN** `openspec/templates/X/` no longer exists, and `X` no longer + appears in the template list + +#### Scenario: Deleting an unknown project-level template id + +- **WHEN** the requested id has no matching `openspec/templates//` + directory +- **THEN** the system reports a clear "not found" error and makes no + filesystem change + +#### Scenario: Built-in tree/tab items never offer delete + +- **WHEN** the user views a built-in template in either delivery + target's UI +- **THEN** no delete action is available for it + diff --git a/openspec/specs/vscode-extension/spec.md b/openspec/specs/vscode-extension/spec.md index 669e6547..136f07f8 100644 --- a/openspec/specs/vscode-extension/spec.md +++ b/openspec/specs/vscode-extension/spec.md @@ -137,3 +137,45 @@ for the areas the embed does not show. - **THEN** no embed signal is relevant, since this mode does not load the standalone shell at all +### Requirement: Changes and Archive trees expand to individual tasks, with reveal and scoped delete + +Expanding a change node in either the "Changes" or "Archive" tree view +SHALL also list that change's individual `tasks.md` checklist items as +child tree items, alongside its existing artifact children. Selecting a +task item SHALL open (or reveal, if already open) `tasks.md` with the +cursor moved to that task's line, in both trees. A "Delete Task" action +SHALL be available only on task items belonging to an active (non- +archived) change; selecting it, after confirmation, SHALL remove exactly +that task's checklist line from the change's `tasks.md`. Task items +belonging to archived changes SHALL NOT offer a delete action. + +#### Scenario: Expanding an active change shows its tasks + +- **WHEN** the user expands a change node in the "Changes" tree +- **THEN** its individual `tasks.md` checklist items appear as child + items alongside the existing artifact children + +#### Scenario: Selecting a task reveals it in the editor + +- **WHEN** the user selects a task tree item (in either tree) +- **THEN** `tasks.md` opens (or is revealed, if already open) with the + cursor at that task's line + +#### Scenario: Deleting a task from an active change + +- **WHEN** the user confirms "Delete Task" on a task belonging to an + active change +- **THEN** that exact line is removed from the change's `tasks.md` + +#### Scenario: Archived tasks offer no delete action + +- **WHEN** the user views a task item under the "Archive" tree +- **THEN** no delete action is available for it + +#### Scenario: The underlying file changed since the tree was last refreshed + +- **WHEN** the user attempts to delete a task whose stored position no + longer matches the current content of `tasks.md` +- **THEN** the system reports that the task list has changed and makes no + filesystem change, rather than risking deletion of a different line + From 226de96dbb84eb6dbda0145342a517faa74eb800 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Mon, 17 Aug 2026 17:32:39 +0300 Subject: [PATCH 2/2] Withhold Delete Task for done tasks, even in active Changes A completed checklist line records that work happened; deleting it throws that record away with no equivalent safeguard to the one archived changes already get. Extends the existing archived/active contextValue gating (tasks-tree-expand) with a done-state distinction, both in the tree (menu withheld) and in the command itself (fail-closed guard, not just UI-gated). Co-Authored-By: Claude Sonnet 5 --- README.md | 2 +- openspec/changes/task-done-lock/proposal.md | 45 ++++++++++++++++ .../task-done-lock/smoke-test-notes.md | 16 ++++++ .../specs/vscode-extension/spec.md | 51 +++++++++++++++++++ openspec/changes/task-done-lock/tasks.md | 30 +++++++++++ packages/extension/CHANGELOG.md | 8 +++ packages/extension/README.md | 3 +- packages/extension/package.json | 2 +- packages/extension/src/commands.test.ts | 9 ++++ packages/extension/src/commands.ts | 2 +- .../extension/src/tree/changes-tree.test.ts | 3 +- packages/extension/src/tree/changes-tree.ts | 9 +++- 12 files changed, 174 insertions(+), 6 deletions(-) create mode 100644 openspec/changes/task-done-lock/proposal.md create mode 100644 openspec/changes/task-done-lock/smoke-test-notes.md create mode 100644 openspec/changes/task-done-lock/specs/vscode-extension/spec.md create mode 100644 openspec/changes/task-done-lock/tasks.md diff --git a/README.md b/README.md index 98057d0b..12c0ba2f 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.0 | VS Code delivery | +| `openspec-ui-vscode` | 0.12.1 | 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/task-done-lock/proposal.md b/openspec/changes/task-done-lock/proposal.md new file mode 100644 index 00000000..d3b859be --- /dev/null +++ b/openspec/changes/task-done-lock/proposal.md @@ -0,0 +1,45 @@ +## Why + +`tasks-tree-expand` added a "Delete Task" action on active-change task +items, gated only on archived state (`openspec-ui.activeTask` vs +`openspec-ui.archivedTask`). Raised in review: a task marked done still +offers Delete today, even though deleting a completed checklist line +throws away a record of finished work with no equivalent safeguard to +the one archived changes already get. There's no legitimate reason to +delete a done task's line — if it's wrong, the fix is unchecking it, not +removing the record that it happened. + +## What Changes + +- A task item whose checklist line is done (`- [x]`) no longer offers + "Delete Task" in its context menu, in **active** changes — matching + the existing behavior for archived changes, which already withhold it + regardless of done-state. +- The command itself refuses to delete a done task even if invoked + another way (not just menu-gated), the same fail-closed pattern + already used for the archived-item guard. +- Undone tasks in active changes are unaffected — still deletable exactly + as before. + +## Capabilities + +### New Capabilities + +(none) + +### Modified Capabilities + +- `vscode-extension`: "Delete Task" is now withheld for done tasks in + active changes, not just archived ones. + +## Impact + +- `packages/extension/src/tree/changes-tree.ts` (`TaskTreeItem`: new + `openspec-ui.activeTaskDone` contextValue, distinct from + `openspec-ui.activeTask`). +- `packages/extension/src/commands.ts` (`openspec-ui.deleteTask`: guard + extended to `item.done`). +- `packages/extension/package.json` (no change needed — the existing + `viewItem == openspec-ui.activeTask` menu `when`-clause already + excludes the new `openspec-ui.activeTaskDone` value). +- No change to the command/event protocol, no core or standalone impact. diff --git a/openspec/changes/task-done-lock/smoke-test-notes.md b/openspec/changes/task-done-lock/smoke-test-notes.md new file mode 100644 index 00000000..93ec466f --- /dev/null +++ b/openspec/changes/task-done-lock/smoke-test-notes.md @@ -0,0 +1,16 @@ +# Smoke test — task-done-lock + +- Real Extension Host run (`npm run test:integration --workspace + openspec-ui-vscode`): 6/6 passing, extension activates and registers + all contributed commands including `openspec-ui.deleteTask`. +- Unit tests exercise the actual new code paths, not mocks of them: + `changes-tree.test.ts` asserts a done active task gets + `openspec-ui.activeTaskDone` (not `openspec-ui.activeTask`), which is + what the unchanged `package.json` `"when": "viewItem == + openspec-ui.activeTask"` clause relies on to withhold the menu item. + `commands.test.ts` asserts `openspec-ui.deleteTask` no-ops for a done + item without even prompting, mirroring the existing archived-item test. +- Live fixture check (via `tsx`, against a real temp `tasks.md`, deleted + after): `readTaskChecklist` correctly reports `done: true`/`false` for + a real `- [x]`/`- [ ]` line, confirming the data the tree consumes to + pick the contextValue is correct end to end, not just in mocks. diff --git a/openspec/changes/task-done-lock/specs/vscode-extension/spec.md b/openspec/changes/task-done-lock/specs/vscode-extension/spec.md new file mode 100644 index 00000000..a24bd7ab --- /dev/null +++ b/openspec/changes/task-done-lock/specs/vscode-extension/spec.md @@ -0,0 +1,51 @@ +## MODIFIED Requirements + +### Requirement: Changes and Archive trees expand to individual tasks, with reveal and scoped delete + +Expanding a change node in either the "Changes" or "Archive" tree view +SHALL also list that change's individual `tasks.md` checklist items as +child tree items, alongside its existing artifact children. Selecting a +task item SHALL open (or reveal, if already open) `tasks.md` with the +cursor moved to that task's line, in both trees. A "Delete Task" action +SHALL be available only on task items belonging to an active (non- +archived) change that are not marked done; selecting it, after +confirmation, SHALL remove exactly that task's checklist line from the +change's `tasks.md`. Task items belonging to archived changes, and done +task items in active changes, SHALL NOT offer a delete action. + +#### Scenario: Expanding an active change shows its tasks + +- **WHEN** the user expands a change node in the "Changes" tree +- **THEN** its individual `tasks.md` checklist items appear as child + items alongside the existing artifact children + +#### Scenario: Selecting a task reveals it in the editor + +- **WHEN** the user selects a task tree item (in either tree) +- **THEN** `tasks.md` opens (or is revealed, if already open) with the + cursor at that task's line + +#### Scenario: Deleting a task from an active change + +- **WHEN** the user confirms "Delete Task" on a task belonging to an + active change that is not marked done +- **THEN** that exact line is removed from the change's `tasks.md` + +#### Scenario: Archived tasks offer no delete action + +- **WHEN** the user views a task item under the "Archive" tree +- **THEN** no delete action is available for it + +#### Scenario: Done tasks offer no delete action, even in active changes + +- **WHEN** the user views a task item marked done (`- [x]`) under an + active change in the "Changes" tree +- **THEN** no delete action is available for it, and invoking the delete + command directly with that item makes no change to `tasks.md` + +#### Scenario: The underlying file changed since the tree was last refreshed + +- **WHEN** the user attempts to delete a task whose stored position no + longer matches the current content of `tasks.md` +- **THEN** the system reports that the task list has changed and makes no + filesystem change, rather than risking deletion of a different line diff --git a/openspec/changes/task-done-lock/tasks.md b/openspec/changes/task-done-lock/tasks.md new file mode 100644 index 00000000..88ba9d4c --- /dev/null +++ b/openspec/changes/task-done-lock/tasks.md @@ -0,0 +1,30 @@ +## 1. Extension: withhold delete for done tasks + +- [x] 1.1 `changes-tree.ts`: `TaskTreeItem` — new `openspec-ui.activeTaskDone` + contextValue for a done task in an active change (distinct from + `openspec-ui.activeTask`); tooltip explains why delete is withheld. + `openspec-ui.archivedTask` unchanged (archived always wins over + done-state). +- [x] 1.2 `commands.ts`: `openspec-ui.deleteTask` — guard extended to + `item.done`, same fail-closed pattern as the existing `item.archived` + no-op. +- [x] 1.3 `package.json`: no change needed — the existing + `"when": "viewItem == openspec-ui.activeTask"` menu entry already + excludes the new `openspec-ui.activeTaskDone` contextValue. +- [x] 1.4 `changes-tree.test.ts`: done active task gets + `openspec-ui.activeTaskDone`, not `openspec-ui.activeTask`. +- [x] 1.5 `commands.test.ts`: `deleteTask` no-ops for a done task without + prompting, same as the existing archived-task test. + +## 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 (patch) for `openspec-ui-vscode`. + Add a `packages/extension/CHANGELOG.md` entry and a README line. +- [x] 2.3 Manual smoke test: real Extension Host run via + `npm run test:integration` (6/6 passing), plus a real `readTaskChecklist` + run against a temp `tasks.md` confirming done-state is read correctly. + Full detail in `smoke-test-notes.md`. +- [x] 2.4 `openspec change validate --strict task-done-lock` passes. diff --git a/packages/extension/CHANGELOG.md b/packages/extension/CHANGELOG.md index 35d9816e..55894330 100644 --- a/packages/extension/CHANGELOG.md +++ b/packages/extension/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.12.1 + +- "Delete Task" is no longer offered for a task marked done, even in an + active (non-archived) change — matching the guard already in place for + archived changes. A completed checklist line records that the work + happened; the fix for a wrongly-checked task is unchecking it, not + deleting the record. + ## 0.12.0 - Added three Command Palette commands to bootstrap repository files diff --git a/packages/extension/README.md b/packages/extension/README.md index b57a543d..cf164bdc 100644 --- a/packages/extension/README.md +++ b/packages/extension/README.md @@ -12,7 +12,8 @@ notifications while keeping lifecycle and security behavior in - Create, validate, archive, unarchive, and delete changes from contextual actions with confirmation for destructive operations. Expand a change to see its individual `tasks.md` checklist items; select one to jump to it - in the editor, or delete it (active changes only) with confirmation. + in the editor, or delete it (active changes only, and only if not yet + done) with confirmation. - See queued, running, completed, failed, cancelled, interrupted, and rolled-back operations in the Processes view. Read-only work can run in parallel; workspace mutations are serialized to keep checkpoints isolated. diff --git a/packages/extension/package.json b/packages/extension/package.json index cf1f47ee..038943c9 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.0", + "version": "0.12.1", "icon": "media/icon.png", "license": "MIT", "repository": { diff --git a/packages/extension/src/commands.test.ts b/packages/extension/src/commands.test.ts index e1b36d19..ecb286e9 100644 --- a/packages/extension/src/commands.test.ts +++ b/packages/extension/src/commands.test.ts @@ -665,6 +665,15 @@ describe("registerCommands", () => { expect(deleteTaskLineMock).not.toHaveBeenCalled(); }); + it("does nothing for a done task, even in an active change, without even prompting", async () => { + registerCommands(makeContext() as unknown as import("vscode").ExtensionContext, makeDeps()); + + await vscodeMock._registeredCommands.get("openspec-ui.deleteTask")?.({ ...activeItem, done: true }); + + expect(vscodeMock.window.showWarningMessage).not.toHaveBeenCalled(); + expect(deleteTaskLineMock).not.toHaveBeenCalled(); + }); + it("reports a stale task list as a warning, not an error", async () => { vscodeMock.window.showWarningMessage.mockResolvedValueOnce("Delete"); deleteTaskLineMock.mockRejectedValue(new TaskListChangedError("task list changed")); diff --git a/packages/extension/src/commands.ts b/packages/extension/src/commands.ts index aacc6ef3..d40c726a 100644 --- a/packages/extension/src/commands.ts +++ b/packages/extension/src/commands.ts @@ -580,7 +580,7 @@ export function registerCommands(context: vscode.ExtensionContext, deps: Command }), vscode.commands.registerCommand("openspec-ui.deleteTask", async (item?: TaskTreeItem) => { const workspaceRoot = deps.getWorkspaceRoot(); - if (!workspaceRoot || !item || item.archived) return; + if (!workspaceRoot || !item || item.archived || item.done) return; const answer = await vscode.window.showWarningMessage( `Permanently delete task "${item.text}" from ${item.changeName}'s tasks.md?`, { modal: true }, diff --git a/packages/extension/src/tree/changes-tree.test.ts b/packages/extension/src/tree/changes-tree.test.ts index 39f7be7d..6cfdf0c9 100644 --- a/packages/extension/src/tree/changes-tree.test.ts +++ b/packages/extension/src/tree/changes-tree.test.ts @@ -87,7 +87,8 @@ describe("ChangesTreeProvider", () => { expect(children.map((item) => item.label)).toEqual(["1.1 First task", "1.2 Second task"]); expect(children[0]?.description).toBe("done"); expect(children[1]?.description).toBeUndefined(); - expect(children[0]?.contextValue).toBe("openspec-ui.activeTask"); + expect(children[0]?.contextValue).toBe("openspec-ui.activeTaskDone"); + expect(children[1]?.contextValue).toBe("openspec-ui.activeTask"); expect(children[0]?.command?.command).toBe("openspec-ui.revealTask"); }); diff --git a/packages/extension/src/tree/changes-tree.ts b/packages/extension/src/tree/changes-tree.ts index a4d770f0..7f971067 100644 --- a/packages/extension/src/tree/changes-tree.ts +++ b/packages/extension/src/tree/changes-tree.ts @@ -74,7 +74,14 @@ export class TaskTreeItem extends vscode.TreeItem { ) { super(text, vscode.TreeItemCollapsibleState.None); this.description = done ? "done" : undefined; - this.contextValue = archived ? "openspec-ui.archivedTask" : "openspec-ui.activeTask"; + this.contextValue = archived + ? "openspec-ui.archivedTask" + : done + ? "openspec-ui.activeTaskDone" + : "openspec-ui.activeTask"; + if (!archived && done) { + this.tooltip = `${text}\n\nDone tasks can't be deleted.`; + } this.iconPath = new vscode.ThemeIcon(done ? "check" : "circle-large-outline"); this.command = { command: "openspec-ui.revealTask", title: "Reveal Task", arguments: [this] }; }