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
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
id: bugfix-838
title: duplicate-debug-looking-comman
protocol: bugfix
phase: pr
plan_phases: []
current_plan_phase: null
gates: {}
iteration: 1
build_complete: false
history: []
started_at: '2026-05-26T05:44:03.993Z'
updated_at: '2026-05-26T05:50:32.842Z'
42 changes: 42 additions & 0 deletions codev/state/bugfix-838_thread.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# bugfix-838 — duplicate / debug-looking command titles

## Investigation

Issue #838: three `contributes.commands` in `packages/vscode/package.json`
render badly on the Feature Contributions tab:

- `codev.openBuilderTerminal` (visible) and `codev.openBuilderById`
(palette-hidden via `when:false`) both had title
`"Codev: Open Builder Terminal"` — two indistinguishable lines.
- `codev.openBuilderRow` (palette-hidden) had
`"Codev: Open Builder Terminal (and expand row)"` — reads like a debug
note.

Feature Contributions tab lists every declared command regardless of
`commandPalette` `when:false`, so palette-hidden commands surface there too.

## Fix

Adopted the issue's suggested `Codev (internal): …` prefix for the two
palette-hidden commands so anyone reading the contributions list can
distinguish user-callable from internal at a glance:

- `codev.openBuilderById` → `Codev (internal): Open Builder Terminal by ID`
- `codev.openBuilderRow` → `Codev (internal): Open Builder Terminal and Expand Row`

`codev.openBuilderTerminal` (the user-callable one) keeps its existing title.

## Regression test

Added `packages/vscode/src/__tests__/contributes-commands.test.ts`:

1. No two `contributes.commands` share a title.
2. No title contains the `(and ...)` debug-note pattern.

Both fail against pre-fix package.json (verified via `git stash`); both
pass after the fix.

## Scope

Single-file behavioral change + one new test file. ~6 lines net in
package.json, ~30 lines test. Well under BUGFIX's 300 LOC ceiling.
4 changes: 2 additions & 2 deletions packages/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
},
{
"command": "codev.openBuilderTerminal",
"title": "Codev: Open Builder Terminal"
"title": "Codev: Open Builder Terminal..."
},
{
"command": "codev.newShell",
Expand Down Expand Up @@ -178,7 +178,7 @@
},
{
"command": "codev.openBuilderRow",
"title": "Codev: Open Builder Terminal (and expand row)"
"title": "Codev (internal): Open Builder Terminal and Expand Row"
},
{
"command": "codev.openWorktreeFolder",
Expand Down
57 changes: 57 additions & 0 deletions packages/vscode/src/__tests__/contributes-commands.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Regression: VS Code's Feature Contributions tab lists every declared
* command regardless of `commandPalette` `when:false`. So palette-hidden
* commands surface there too, and identical titles render as duplicate
* lines on the extension's detail page (issue #838).
*
* Additional invariant: a "(internal)" marker may only appear on commands
* that are not exposed in any user-visible menu surface (the right-click
* `view/item/context` menus). A command shown to users in a real menu
* must read naturally there — labeling it "internal" would be a UX
* regression in that menu.
*/

import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';

const PKG = JSON.parse(
readFileSync(resolve(__dirname, '../../package.json'), 'utf8'),
);

const commands: Array<{ command: string; title: string }> =
PKG.contributes.commands;

const titleByCommand = new Map(commands.map((c) => [c.command, c.title]));

const viewContextCommands: string[] = (
PKG.contributes.menus?.['view/item/context'] ?? []
).map((m: { command: string }) => m.command);

describe('package.json contributes.commands', () => {
it('has no two commands sharing the same title', () => {
const byTitle = new Map<string, string[]>();
for (const { command, title } of commands) {
const list = byTitle.get(title) ?? [];
list.push(command);
byTitle.set(title, list);
}
const dupes = [...byTitle.entries()].filter(([, ids]) => ids.length > 1);
expect(dupes, `duplicate titles: ${JSON.stringify(dupes)}`).toEqual([]);
});

it('has no debug-note-style parenthetical titles like "(and ...)"', () => {
const offenders = commands.filter(({ title }) => /\(and\b/i.test(title));
expect(offenders).toEqual([]);
});

it('does not label a command "(internal)" if it is exposed in view/item/context', () => {
const offenders = viewContextCommands
.filter((cmd) => /\(internal\)/i.test(titleByCommand.get(cmd) ?? ''))
.map((cmd) => ({ command: cmd, title: titleByCommand.get(cmd) }));
expect(
offenders,
`commands marked (internal) but shown in a view menu: ${JSON.stringify(offenders)}`,
).toEqual([]);
});
});
Loading