🐛 fix(vscode): 重写注释直接调用 /ecc:writing-code-comments,不再自己读技能文件
This commit is contained in:
1 parent
f2490b0505
commit
5f1352df86
3 files changed
+10
-72
No files matched your search
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { commentProse, commentRewritePrompt, commentSource, isModuleComment, loadCommentSkill, modelCommentBody, refillComment, skillBody } from './commentRewrite'
|
import { COMMENT_SKILL, commentProse, commentRewritePrompt, commentSource, isModuleComment, modelCommentBody, refillComment } from './commentRewrite'
|
||||||
import { commonIndent, replaceLines } from './editText'
|
import { commonIndent, replaceLines } from './editText'
|
||||||
|
|
||||||
/** 和预览写回同一条路:正文套回壳,再按原来的缩进替换这几行。 */
|
/** 和预览写回同一条路:正文套回壳,再按原来的缩进替换这几行。 */
|
||||||
@@ -116,15 +116,14 @@ describe('重写注释的提示', () => {
|
|||||||
it('文件顶上的三引号:上下文是去掉这段之后的文件,太长会截掉并说明', () => {
|
it('文件顶上的三引号:上下文是去掉这段之后的文件,太长会截掉并说明', () => {
|
||||||
const rest = Array.from({ length: 130 }, (_, index) => `line ${index}`).join('\n')
|
const rest = Array.from({ length: 130 }, (_, index) => `line ${index}`).join('\n')
|
||||||
const prompt = commentRewritePrompt({
|
const prompt = commentRewritePrompt({
|
||||||
skill: '技能正文',
|
|
||||||
comment: '"""旧"""',
|
comment: '"""旧"""',
|
||||||
source: `"""旧"""\n${rest}`,
|
source: `"""旧"""\n${rest}`,
|
||||||
startLine: 0,
|
startLine: 0,
|
||||||
endLine: 0,
|
endLine: 0,
|
||||||
earlierKinds: [],
|
earlierKinds: [],
|
||||||
})
|
})
|
||||||
expect(prompt).toContain('技能正文')
|
expect(prompt?.startsWith(`${COMMENT_SKILL}\n`)).toBe(true)
|
||||||
expect(prompt).toContain('只按上面的技能重写这一段注释')
|
expect(prompt).toContain('只按这个技能重写这一段注释')
|
||||||
expect(prompt).toContain('这段注释现在的正文:\n旧')
|
expect(prompt).toContain('这段注释现在的正文:\n旧')
|
||||||
expect(prompt).not.toContain('"""')
|
expect(prompt).not.toContain('"""')
|
||||||
expect(prompt).toContain('截断了')
|
expect(prompt).toContain('截断了')
|
||||||
@@ -134,7 +133,6 @@ describe('重写注释的提示', () => {
|
|||||||
|
|
||||||
it('普通注释只带后面紧挨着的那一块,不带整份文件', () => {
|
it('普通注释只带后面紧挨着的那一块,不带整份文件', () => {
|
||||||
const prompt = commentRewritePrompt({
|
const prompt = commentRewritePrompt({
|
||||||
skill: '技能正文',
|
|
||||||
comment: '# 旧',
|
comment: '# 旧',
|
||||||
source: '整份文件不该出现',
|
source: '整份文件不该出现',
|
||||||
startLine: 4,
|
startLine: 4,
|
||||||
@@ -149,19 +147,6 @@ describe('重写注释的提示', () => {
|
|||||||
expect(isModuleComment('"""旧"""', ['function'])).toBe(false)
|
expect(isModuleComment('"""旧"""', ['function'])).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('技能按给出的顺序找,用到第一份;都没有就是没有', async () => {
|
|
||||||
const read = async (path: string): Promise<string> => {
|
|
||||||
if (path.includes('/marketplaces/ecc/'))
|
|
||||||
throw Object.assign(new Error('no'), { code: 'ENOENT' })
|
|
||||||
return '---\nname: writing-code-comments\n---\n规范正文\n'
|
|
||||||
}
|
|
||||||
expect(await loadCommentSkill(read)).toBe('规范正文')
|
|
||||||
expect(await loadCommentSkill(async () => {
|
|
||||||
throw Object.assign(new Error('no'), { code: 'ENOENT' })
|
|
||||||
})).toBeUndefined()
|
|
||||||
expect(skillBody('没有头的正文')).toBe('没有头的正文')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('模型套了代码围栏时只留里面的正文', () => {
|
it('模型套了代码围栏时只留里面的正文', () => {
|
||||||
expect(modelCommentBody('```\n新说明\n```')).toBe('新说明')
|
expect(modelCommentBody('```\n新说明\n```')).toBe('新说明')
|
||||||
expect(modelCommentBody(' ')).toBeUndefined()
|
expect(modelCommentBody(' ')).toBeUndefined()
|
||||||
|
|||||||
@@ -3,19 +3,13 @@
|
|||||||
* 认 Python 的三引号、井号行注释,以及块注释和双斜杠行注释。认不出就不改。
|
* 认 Python 的三引号、井号行注释,以及块注释和双斜杠行注释。认不出就不改。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { readFile } from 'node:fs/promises'
|
|
||||||
import { homedir } from 'node:os'
|
|
||||||
import { join } from 'node:path'
|
|
||||||
import { commonIndent, splitLines } from './editText'
|
import { commonIndent, splitLines } from './editText'
|
||||||
|
|
||||||
/** 贴给模型的代码最多这么多行,再长就截断并在提示里说明。 */
|
/** 贴给模型的代码最多这么多行,再长就截断并在提示里说明。 */
|
||||||
export const COMMENT_CONTEXT_LINES = 120
|
export const COMMENT_CONTEXT_LINES = 120
|
||||||
|
|
||||||
/** 运行时按这个顺序找技能,用到的第一份。不把技能正文放进仓库。 */
|
/** 跟在对话框里输入这个命令一样,由 Claude Code 自己加载写注释的技能。 */
|
||||||
export const COMMENT_SKILL_PATHS = [
|
export const COMMENT_SKILL = '/ecc:writing-code-comments'
|
||||||
join(homedir(), '.claude/plugins/marketplaces/ecc/skills/writing-code-comments/SKILL.md'),
|
|
||||||
join(homedir(), '.claude/writing-code-comments/SKILL.md'),
|
|
||||||
] as const
|
|
||||||
|
|
||||||
const DOUBLE_DOC = '"""'
|
const DOUBLE_DOC = '"""'
|
||||||
const SINGLE_DOC = '\'' + '\'' + '\''
|
const SINGLE_DOC = '\'' + '\'' + '\''
|
||||||
@@ -28,31 +22,6 @@ export function commentSource(lines: readonly string[]): string {
|
|||||||
return lines.map(line => line.slice(Math.min(width, line.length))).join('\n')
|
return lines.map(line => line.slice(Math.min(width, line.length))).join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 去掉技能文件开头的 frontmatter,剩下的才是给模型的规范。 */
|
|
||||||
export function skillBody(markdown: string): string {
|
|
||||||
const text = markdown.charCodeAt(0) === 0xFEFF ? markdown.slice(1) : markdown
|
|
||||||
const fence = text.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/)
|
|
||||||
return (fence ? text.slice(fence[0].length) : text).trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
function missingFile(err: unknown): boolean {
|
|
||||||
return typeof err === 'object' && err !== null && 'code' in err && err.code === 'ENOENT'
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 读到的第一份技能正文;两处都没有就是 undefined。读失败(不是没有文件)照样抛。 */
|
|
||||||
export async function loadCommentSkill(read: (path: string) => Promise<string> = path => readFile(path, 'utf8')): Promise<string | undefined> {
|
|
||||||
for (const path of COMMENT_SKILL_PATHS) {
|
|
||||||
try {
|
|
||||||
return skillBody(await read(path))
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
if (!missingFile(err))
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DocShell {
|
interface DocShell {
|
||||||
prefix: string
|
prefix: string
|
||||||
quote: string
|
quote: string
|
||||||
@@ -307,8 +276,6 @@ function clip(text: string, what: string): { text: string, note: string } {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CommentRewriteInput {
|
export interface CommentRewriteInput {
|
||||||
/** 去掉 frontmatter 的技能正文;没有技能就不组提示 */
|
|
||||||
skill: string | undefined
|
|
||||||
/** 去掉共同缩进的注释原文 */
|
/** 去掉共同缩进的注释原文 */
|
||||||
comment: string
|
comment: string
|
||||||
/** 整份文件,用来在是模块说明时拿掉这段注释 */
|
/** 整份文件,用来在是模块说明时拿掉这段注释 */
|
||||||
@@ -320,10 +287,8 @@ export interface CommentRewriteInput {
|
|||||||
following?: string
|
following?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 发给模型的整段话。注释认不出,或没有技能,就不组。 */
|
/** 发给模型的整段话。开头是技能命令,Claude Code 自己加载技能。注释认不出就不组。 */
|
||||||
export function commentRewritePrompt(input: CommentRewriteInput): string | undefined {
|
export function commentRewritePrompt(input: CommentRewriteInput): string | undefined {
|
||||||
if (input.skill === undefined)
|
|
||||||
return undefined
|
|
||||||
const body = commentProse(input.comment)
|
const body = commentProse(input.comment)
|
||||||
if (body === undefined)
|
if (body === undefined)
|
||||||
return undefined
|
return undefined
|
||||||
@@ -334,8 +299,8 @@ export function commentRewritePrompt(input: CommentRewriteInput): string | undef
|
|||||||
? clip(input.following, '这块代码')
|
? clip(input.following, '这块代码')
|
||||||
: { text: '(后面没有紧挨着的代码)', note: '' }
|
: { text: '(后面没有紧挨着的代码)', note: '' }
|
||||||
return [
|
return [
|
||||||
input.skill,
|
COMMENT_SKILL,
|
||||||
'只按上面的技能重写这一段注释。用中文。不要改代码,不要加技能没要求的中文名记号。只输出新注释的正文,不要定界符,不要解释。',
|
'只按这个技能重写这一段注释。用中文。不要改代码,不要加技能没要求的中文名记号。只输出新注释的正文,不要定界符,不要解释。',
|
||||||
`这段注释现在的正文:\n${body}`,
|
`这段注释现在的正文:\n${body}`,
|
||||||
`它说明的代码:\n${context.text}${context.note}`,
|
`它说明的代码:\n${context.text}${context.note}`,
|
||||||
].join('\n\n')
|
].join('\n\n')
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { commands, Range, window, workspace, WorkspaceEdit } from 'vscode'
|
|||||||
import { logger } from '../../logging/logger'
|
import { logger } from '../../logging/logger'
|
||||||
import { webviewHtml, webviewUiRoot } from '../../webview/html'
|
import { webviewHtml, webviewUiRoot } from '../../webview/html'
|
||||||
import { DocumentCallees } from '../calleeKinds'
|
import { DocumentCallees } from '../calleeKinds'
|
||||||
import { commentRewritePrompt, commentSource, findPlaced, loadCommentSkill, modelCommentBody, refillComment } from '../commentRewrite'
|
import { commentRewritePrompt, commentSource, findPlaced, modelCommentBody, refillComment } from '../commentRewrite'
|
||||||
import { PreviewDiff } from '../diff/previewDiff'
|
import { PreviewDiff } from '../diff/previewDiff'
|
||||||
import { blockReplacement } from '../editText'
|
import { blockReplacement } from '../editText'
|
||||||
import { locate } from '../folderStore'
|
import { locate } from '../folderStore'
|
||||||
@@ -588,19 +588,7 @@ class Preview {
|
|||||||
const following = next && next.startLine >= 0 && next.endLine >= next.startLine && next.endLine < document.lineCount
|
const following = next && next.startLine >= 0 && next.endLine >= next.startLine && next.endLine < document.lineCount
|
||||||
? Array.from({ length: next.endLine - next.startLine + 1 }, (_, i) => document.lineAt(next.startLine + i).text).join('\n')
|
? Array.from({ length: next.endLine - next.startLine + 1 }, (_, i) => document.lineAt(next.startLine + i).text).join('\n')
|
||||||
: undefined
|
: undefined
|
||||||
let skill: string | undefined
|
|
||||||
try {
|
|
||||||
skill = await loadCommentSkill()
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
if (!this.rewriteAbort.signal.aborted)
|
|
||||||
this.failRewrite(`读注释技能失败:${err instanceof Error ? err.message : String(err)}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (this.rewriteAbort.signal.aborted)
|
|
||||||
return
|
|
||||||
const prompt = commentRewritePrompt({
|
const prompt = commentRewritePrompt({
|
||||||
skill,
|
|
||||||
comment,
|
comment,
|
||||||
source,
|
source,
|
||||||
startLine: block.startLine,
|
startLine: block.startLine,
|
||||||
@@ -609,7 +597,7 @@ class Preview {
|
|||||||
...(following !== undefined && { following }),
|
...(following !== undefined && { following }),
|
||||||
})
|
})
|
||||||
if (!prompt) {
|
if (!prompt) {
|
||||||
this.failRewrite(skill === undefined ? '找不到写注释的技能' : '这段注释的定界符认不出来,没有改文件。')
|
this.failRewrite('这段注释的定界符认不出来,没有改文件。')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const at = locate(document.uri)
|
const at = locate(document.uri)
|
||||||
|
|||||||
Reference in new issue
Block a user