✨ feat(vscode): 新增「了解一块代码」:AI 找功能代码占一栏,对话里接着改

主栏标题栏的放大镜问要了解哪一块,占一个栏位建一栏(和收藏共用 30 个栏位,视图改名 slot1..30,按 slotKindN 分收藏 / 功能挂标题栏按钮),同时开对话页让 Claude 只读地在全部工作区文件夹里找,按 --json-schema 交回环节 → 文件 + 符号 + 一句话。
对话复用预览顶部的聊天(CodeQa.askTop:会话续接、进行中的一轮、记录存 workspaceState);结构不合格算这一轮没答成、留着能再画出来。
条目每次画、点时按眼下的代码找:有语言插件的解析成代码块按「类名.方法名」找,没有的问 VS Code 文档符号;找不到画成灰的。
This commit is contained in:
cruldra committed 2026-10-12 15:48:54 +11:00
1 parent 16d27004d8
commit 880bf7fc57
38 files changed
+3386 -378

No files matched your search

+70 -17
View File
@@ -1,7 +1,9 @@
import { readFileSync } from 'node:fs'
import * as path from 'node:path'
import { describe, expect, it } from 'vitest'
import { FAVORITE_SLOTS, favoriteCommand, favoriteContext, favoriteViewId } from '../favorites/favoriteSlots'
import { favoriteCommand } from '../favorites/favoriteSlots'
import { FEATURE_MARKER, featureCommand } from '../features/features'
import { slotContext, SLOTS, slotViewId } from '../slots/slots'
import { UNSTAGED_MARKER } from '../uncommitted/uncommittedFiles'
import { COPY_COMMANDS } from './copyText'
import { entryContext, GROUP_CONTEXT } from './fileEntries'
@@ -95,8 +97,9 @@ function evaluate(when: string, context: Context): boolean {
const FILES = 'superpowers.codeReader.files'
const UNCOMMITTED = 'superpowers.codeReader.uncommitted'
const FAVORITE = favoriteViewId(7)
const READER_VIEWS = [FILES, UNCOMMITTED, ...FAVORITE_SLOTS.map(favoriteViewId)]
const FAVORITE = slotViewId(7)
const FEATURE = slotViewId(8)
const READER_VIEWS = [FILES, UNCOMMITTED, ...SLOTS.map(slotViewId)]
const REVIEWING = 'superpowers.codeReader.reviewing'
const COPY_MENU = 'superpowers.codeReader.copy'
@@ -277,6 +280,7 @@ describe('右键菜单的「在系统文件管理器中显示」', () => {
})
describe('主栏的审查工单', () => {
const FIND = 'superpowers.codeReader.findFeature'
const PICK = 'superpowers.codeReader.pickReviewIssue'
const HIDE_TESTS = 'superpowers.codeReader.hideReviewTests'
const SHOW_TESTS = 'superpowers.codeReader.showReviewTests'
@@ -284,10 +288,10 @@ describe('主栏的审查工单', () => {
const REFRESH = 'superpowers.codeReader.refresh'
const TESTS_HIDDEN = 'superpowers.codeReader.reviewTestsHidden'
it('标题栏:平时是选工单、刷新;审查时多出隐藏 / 显示测试文件(按开关换着显示)和「显示全部」', () => {
expect(titleBar(FILES)).toEqual([PICK, REFRESH])
expect(titleBar(FILES, { [REVIEWING]: true })).toEqual([PICK, HIDE_TESTS, SHOW_ALL, REFRESH])
expect(titleBar(FILES, { [REVIEWING]: true, [TESTS_HIDDEN]: true })).toEqual([PICK, SHOW_TESTS, SHOW_ALL, REFRESH])
it('标题栏:平时是了解一块代码、选工单、刷新;审查时多出隐藏 / 显示测试文件(按开关换着显示)和「显示全部」', () => {
expect(titleBar(FILES)).toEqual([FIND, PICK, REFRESH])
expect(titleBar(FILES, { [REVIEWING]: true })).toEqual([FIND, PICK, HIDE_TESTS, SHOW_ALL, REFRESH])
expect(titleBar(FILES, { [REVIEWING]: true, [TESTS_HIDDEN]: true })).toEqual([FIND, PICK, SHOW_TESTS, SHOW_ALL, REFRESH])
expect(menus['view/title'].filter(item => item.when.includes(FILES)).every(item => item.group?.startsWith('navigation'))).toBe(true)
})
@@ -347,22 +351,34 @@ describe('「未提交的更改」一栏', () => {
})
})
describe('收藏栏', () => {
it(`预先声明 ${FAVORITE_SLOTS.length} 个栏位,排在「未提交的更改」后面,各自有收藏占着时才显示`, () => {
expect(views['superpowers-reader'].slice(2)).toEqual(FAVORITE_SLOTS.map(slot => ({ id: favoriteViewId(slot), name: `收藏 ${slot}`, when: favoriteContext(slot) })))
describe('收藏、「了解一块代码」共用的栏位', () => {
it(`预先声明 ${SLOTS.length} 个栏位,排在「未提交的更改」后面,各自有收藏或功能占着时才显示`, () => {
expect(views['superpowers-reader'].slice(2)).toEqual(SLOTS.map(slot => ({ id: slotViewId(slot), name: `栏位 ${slot}`, when: slotContext(slot) })))
})
it('每一栏的标题栏:重命名、取消收藏,只对这一栏', () => {
for (const slot of FAVORITE_SLOTS)
expect(titleBar(favoriteViewId(slot))).toEqual([favoriteCommand('rename', slot), favoriteCommand('remove', slot)])
it('收藏占着的栏:标题栏是重命名、取消收藏,只对这一栏', () => {
for (const slot of SLOTS)
expect(titleBar(slotViewId(slot), { [slotContext(slot)]: 'favorite' })).toEqual([favoriteCommand('rename', slot), favoriteCommand('remove', slot)])
})
it('每个栏位的命令都登记了、字和图标一样,命令面板里藏起来', () => {
for (const slot of FAVORITE_SLOTS) {
it('功能占着的栏:标题栏是打开对话、重命名、删除,只对这一栏', () => {
for (const slot of SLOTS)
expect(titleBar(slotViewId(slot), { [slotContext(slot)]: 'feature' })).toEqual([featureCommand('openChat', slot), featureCommand('rename', slot), featureCommand('remove', slot)])
})
it('别的栏被占着不影响这一栏的按钮', () => {
expect(titleBar(slotViewId(3), { [slotContext(4)]: 'feature' })).toEqual([])
})
it('每个栏位的命令都登记了、字和图标,命令面板里藏起来', () => {
for (const slot of SLOTS) {
expect(titleOf(favoriteCommand('rename', slot))).toMatchObject({ title: '重命名', icon: '$(edit)' })
expect(titleOf(favoriteCommand('remove', slot))).toMatchObject({ title: '取消收藏', icon: '$(star-full)' })
expect(palette.get(favoriteCommand('rename', slot))).toBe('false')
expect(palette.get(favoriteCommand('remove', slot))).toBe('false')
expect(titleOf(featureCommand('openChat', slot))).toMatchObject({ title: '打开对话', icon: '$(comment-discussion)' })
expect(titleOf(featureCommand('rename', slot))).toMatchObject({ title: '重命名', icon: '$(edit)' })
expect(titleOf(featureCommand('remove', slot))).toMatchObject({ title: '删除', icon: '$(trash)' })
for (const id of [favoriteCommand('rename', slot), favoriteCommand('remove', slot), featureCommand('openChat', slot), featureCommand('rename', slot), featureCommand('remove', slot)])
expect(palette.get(id)).toBe('false')
}
})
@@ -378,6 +394,43 @@ describe('收藏栏', () => {
})
})
describe('了解一块代码', () => {
const FIND = 'superpowers.codeReader.findFeature'
const OPEN_ITEM = 'superpowers.codeReader.openFeatureItem'
/** 功能栏里找得到的条目行(符号、只有文件的都是它) */
const item = entryContext({ isDirectory: false, isRoot: false }, FEATURE_MARKER)
it('入口只在主栏标题栏:放大镜「了解一块代码」,排在最前;别的栏没有', () => {
expect(titleBar(FILES)[0]).toBe(FIND)
expect(titleBar(FILES, { [REVIEWING]: true })[0]).toBe(FIND)
for (const view of [UNCOMMITTED, FEATURE])
expect(titleBar(view, { [slotContext(8)]: 'feature' })).not.toContain(FIND)
expect(titleOf(FIND)).toMatchObject({ title: '了解一块代码', icon: '$(search)' })
})
it('条目行:「问 AI」「复制」、在系统文件管理器中显示照常有;注释、收藏、暂存不挂', () => {
expect(itemsFor('view/item/context', 'inline', item, FEATURE)).toEqual([ASK, COPY_MENU])
expect(itemsFor('view/item/context', 'copy', item, FEATURE)).toEqual(FILE_COPIES)
expect(itemsFor(COPY_MENU, 'copy', item, FEATURE)).toEqual(FILE_COPIES)
expect(itemsFor('view/item/context', 'fileManager', item, FEATURE)).toEqual(['superpowers.codeReader.revealInFinder'])
for (const group of ['notes', 'star'])
expect(itemsFor('view/item/context', group, item, FEATURE)).toEqual([])
})
it('分组行、找不到的条目行什么按钮、菜单都没有', () => {
for (const context of [GROUP_CONTEXT, `${FEATURE_MARKER} missing`]) {
for (const group of ['inline', 'copy', 'fileManager', 'notes', 'star'])
expect(itemsFor('view/item/context', group, context, FEATURE)).toEqual([])
}
})
it('点条目的命令只给侧栏行用,命令面板里藏起来;「了解一块代码」命令面板里能用', () => {
expect(titleOf(OPEN_ITEM)).toBeDefined()
expect(palette.get(OPEN_ITEM)).toBe('false')
expect(palette.has(FIND)).toBe(false)
})
})
describe('菜单里用到的', () => {
it('命令都登记了;旧的筛选按钮、上下文键不再出现', () => {
const ids = new Set(commands.map(item => item.command))