fix: restore verified commit 89c9e87c files after main merge
Restore files to match the verified commit state: - generate-review-builtin.ts: skill-only generation, SSH clone, optimize/agent-prompts branch - extension.ts: listBuiltinSkillNames, getClaudeConfigHomeDir approach - lang.ts: re-extract review skills on language switch - locales: use Skill tool prompt format Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
4c56bbe268
commit
33bdcb9b74
|
|
@ -14,6 +14,7 @@ import fs from 'fs/promises'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
import { spawnSync } from 'child_process'
|
import { spawnSync } from 'child_process'
|
||||||
|
import matter from 'gray-matter'
|
||||||
import { mkdir } from 'fs/promises'
|
import { mkdir } from 'fs/promises'
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
|
@ -27,11 +28,11 @@ type IndexJson = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const REPO = 'zgsm-ai/costrict-review'
|
const REPO = 'zgsm-ai/costrict-review'
|
||||||
const BRANCH = 'main'
|
const BRANCH = 'optimize/agent-prompts'
|
||||||
const CLONE_URL = `git@github.com:${REPO}.git`
|
const CLONE_URL = `git@github.com:${REPO}.git`
|
||||||
|
|
||||||
function git(...args: string[]): { ok: boolean; stdout: string; stderr: string } {
|
function git(...args: string[]): { ok: boolean; stdout: string; stderr: string } {
|
||||||
const result = spawnSync('git', args, { encoding: 'utf-8' })
|
const result = spawnSync('git', args, { encoding: 'utf-8', env: process.env })
|
||||||
return {
|
return {
|
||||||
ok: result.status === 0,
|
ok: result.status === 0,
|
||||||
stdout: result.stdout?.trim() ?? '',
|
stdout: result.stdout?.trim() ?? '',
|
||||||
|
|
@ -89,6 +90,25 @@ function collectLocales(index: IndexJson): string[] {
|
||||||
return [...localeSet].sort()
|
return [...localeSet].sort()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract locale and skill directory from index.json path.
|
||||||
|
* New format: "skills/<locale>/<skillName>/SKILL.md"
|
||||||
|
* Old format: "<locale>/skills/<skillName>/SKILL.md" (for backwards compat)
|
||||||
|
*/
|
||||||
|
function parseSkillPath(skillMdPath: string): { locale: string; skillDir: string } | null {
|
||||||
|
// New format: skills/<locale>/<skillName>/SKILL.md
|
||||||
|
const newMatch = skillMdPath.match(/^skills\/([^/]+)\/(.+)$/)
|
||||||
|
if (newMatch) {
|
||||||
|
return { locale: newMatch[1], skillDir: newMatch[2].replace(/\/SKILL\.md$/, '') }
|
||||||
|
}
|
||||||
|
// Old format: <locale>/skills/<skillName>/SKILL.md
|
||||||
|
const oldMatch = skillMdPath.match(/^([^/]+)\/skills\/(.+)$/)
|
||||||
|
if (oldMatch) {
|
||||||
|
return { locale: oldMatch[1], skillDir: oldMatch[2].replace(/\/SKILL\.md$/, '') }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
async function cloneAndCopy(
|
async function cloneAndCopy(
|
||||||
cloneDir: string,
|
cloneDir: string,
|
||||||
index: IndexJson,
|
index: IndexJson,
|
||||||
|
|
@ -112,25 +132,21 @@ async function cloneAndCopy(
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|
||||||
for (const skillMdPath of skillPaths) {
|
for (const skillMdPath of skillPaths) {
|
||||||
|
const parsed = parseSkillPath(skillMdPath)
|
||||||
|
if (!parsed) {
|
||||||
|
console.warn(` ⚠ Skipping unparseable path: ${skillMdPath}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const { skillDir } = parsed
|
||||||
const srcDir = path.join(cloneDir, path.dirname(skillMdPath))
|
const srcDir = path.join(cloneDir, path.dirname(skillMdPath))
|
||||||
const relativeDir = skillMdPath.startsWith(`${locale}/`)
|
const outputDir = path.join(outputLocaleDir, skillDir)
|
||||||
? skillMdPath.slice(locale.length + 1).replace(/\/[^/]*$/, '')
|
|
||||||
: path.dirname(skillMdPath)
|
|
||||||
const outputDir = path.join(outputLocaleDir, relativeDir)
|
|
||||||
|
|
||||||
await fs.rm(outputDir, { recursive: true, force: true })
|
await fs.rm(outputDir, { recursive: true, force: true })
|
||||||
await fs.cp(srcDir, outputDir, { recursive: true })
|
await fs.cp(srcDir, outputDir, { recursive: true })
|
||||||
|
|
||||||
const skillMd = path.join(outputDir, 'SKILL.md')
|
|
||||||
try {
|
|
||||||
await fs.access(skillMd)
|
|
||||||
} catch {
|
|
||||||
throw new Error(`Skill (${locale}) missing SKILL.md at ${skillMdPath}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const skillName = path.basename(srcDir)
|
|
||||||
const fileCount = (await walk(outputDir)).length
|
const fileCount = (await walk(outputDir)).length
|
||||||
console.log(` ✓ ${locale}/skills/${skillName}: ${fileCount} files`)
|
console.log(` ✓ ${locale}/${skillDir}: ${fileCount} files`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,67 +164,99 @@ async function generateBuiltinSkills(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allSkillNames: string[] = []
|
|
||||||
|
|
||||||
// Discover skill names from first locale
|
// Discover skill names from first locale
|
||||||
|
const allSkillNames: string[] = []
|
||||||
for (const locale of locales) {
|
for (const locale of locales) {
|
||||||
const skillsDir = path.join(bundledReviewDir, locale, 'skills')
|
const entries = await fs.readdir(path.join(bundledReviewDir, locale)).catch(() => [] as string[])
|
||||||
const entries = await fs.readdir(skillsDir).catch(() => [] as string[])
|
|
||||||
for (const name of entries) {
|
for (const name of entries) {
|
||||||
if (!allSkillNames.includes(name)) allSkillNames.push(name)
|
const p = path.join(bundledReviewDir, locale, name)
|
||||||
|
if ((await fs.stat(p).catch(() => null))?.isDirectory()) {
|
||||||
|
if (!allSkillNames.includes(name)) allSkillNames.push(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
const imports: string[] = []
|
const skillFileEntries: string[] = []
|
||||||
const localeSkillEntries: string[] = []
|
const metadataEntries: string[] = []
|
||||||
let fileIdx = 0
|
let fileIdx = 0
|
||||||
|
|
||||||
for (const locale of [...locales].sort()) {
|
for (const locale of [...locales].sort()) {
|
||||||
const skillEntries: string[] = []
|
const fileEntries: string[] = []
|
||||||
|
const metaEntries: string[] = []
|
||||||
|
|
||||||
for (const skillName of allSkillNames) {
|
for (const skillName of allSkillNames) {
|
||||||
const skillDir = path.join(bundledReviewDir, locale, 'skills', skillName)
|
const skillDir = path.join(bundledReviewDir, locale, skillName)
|
||||||
const files = await walk(skillDir)
|
const files = await walk(skillDir)
|
||||||
const fileEntries: string[] = []
|
|
||||||
|
// Parse SKILL.md for metadata
|
||||||
|
let skillMeta = { name: skillName, description: '' }
|
||||||
|
const skillMdPath = path.join(skillDir, 'SKILL.md')
|
||||||
|
try {
|
||||||
|
const content = await fs.readFile(skillMdPath, 'utf-8')
|
||||||
|
const parsed = matter(content)
|
||||||
|
skillMeta = {
|
||||||
|
name: String(parsed.data.name ?? skillName),
|
||||||
|
description: String(parsed.data.description ?? ''),
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// SKILL.md not found, use defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileRecords: string[] = []
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const varName = `SKILL_FILE_${fileIdx++}`
|
const varName = `SKILL_FILE_${fileIdx++}`
|
||||||
const filePath = path.join(skillDir, file)
|
const filePath = path.join(skillDir, file)
|
||||||
const content = await fs.readFile(filePath, 'utf-8')
|
const content = await fs.readFile(filePath, 'utf-8')
|
||||||
const normalizedPath = file.replaceAll('\\', '/')
|
const normalizedPath = file.replaceAll('\\', '/')
|
||||||
imports.push(`const ${varName} = ${JSON.stringify(content)}`)
|
fileRecords.push(` "${normalizedPath}": ${JSON.stringify(content)}`)
|
||||||
fileEntries.push(` "${normalizedPath}": ${varName}`)
|
|
||||||
}
|
}
|
||||||
skillEntries.push(` "${skillName}": {\n${fileEntries.join(',\n')}\n }`)
|
|
||||||
|
fileEntries.push(` "${skillName}": {\n${fileRecords.join(',\n')}\n }`)
|
||||||
|
metaEntries.push(` "${skillName}": ${JSON.stringify(skillMeta)}`)
|
||||||
}
|
}
|
||||||
localeSkillEntries.push(` "${locale}": {\n${skillEntries.join(',\n')}\n }`)
|
|
||||||
|
skillFileEntries.push(` "${locale}": {\n${fileEntries.join(',\n')}\n }`)
|
||||||
|
metadataEntries.push(` "${locale}": {\n${metaEntries.join(',\n')}\n }`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = `// This file is auto-generated by scripts/generate-review-builtin.ts
|
const content = `// This file is auto-generated by scripts/generate-review-builtin.ts
|
||||||
// Do not edit manually
|
// Do not edit manually
|
||||||
|
// Skills are downloaded from zgsm-ai/costrict-review repository
|
||||||
|
|
||||||
import { writeFile, mkdir } from "fs/promises"
|
// locale → skillName → filePath → content
|
||||||
import { join, dirname } from "path"
|
export const SKILL_FILES: Record<string, Record<string, Record<string, string>>> = {
|
||||||
|
${skillFileEntries.join(',\n')}
|
||||||
${imports.join('\n')}
|
|
||||||
|
|
||||||
const SKILL_FILES: Record<string, Record<string, Record<string, string>>> = {
|
|
||||||
${localeSkillEntries.join(',\n')}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SKILL_VERSIONS: Record<string, string> = {
|
// locale → skillName → { name, description }
|
||||||
|
export const SKILL_METADATA: Record<string, Record<string, { name: string; description: string }>> = {
|
||||||
|
${metadataEntries.join(',\n')}
|
||||||
|
}
|
||||||
|
|
||||||
|
// skillName → commit SHA
|
||||||
|
export const SKILL_VERSIONS: Record<string, string> = {
|
||||||
${allSkillNames.map(n => ` "${n}": "${commitSha}"`).join(',\n')}
|
${allSkillNames.map(n => ` "${n}": "${commitSha}"`).join(',\n')}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listBuiltinSkills(): string[] {
|
// List all skill names
|
||||||
|
export function listBuiltinSkillNames(): string[] {
|
||||||
return ${JSON.stringify(allSkillNames)}
|
return ${JSON.stringify(allSkillNames)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get version for a skill
|
||||||
export function getBuiltinSkillVersion(skillName: string): string | undefined {
|
export function getBuiltinSkillVersion(skillName: string): string | undefined {
|
||||||
return SKILL_VERSIONS[skillName]
|
return SKILL_VERSIONS[skillName]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listSkillFiles(skillName: string, locale: string): string[] {
|
// Get files for a skill in a specific locale
|
||||||
return Object.keys(SKILL_FILES[locale]?.[skillName] || {})
|
export function getSkillFiles(skillName: string, locale: string): Record<string, string> {
|
||||||
|
return SKILL_FILES[locale]?.[skillName] ?? {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get metadata for a skill in a specific locale
|
||||||
|
export function getSkillMetadata(skillName: string, locale: string): { name: string; description: string } | undefined {
|
||||||
|
return SKILL_METADATA[locale]?.[skillName]
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function extractBundledSkill(skillName: string, targetDir: string, locale: string): Promise<void> {
|
export async function extractBundledSkill(skillName: string, targetDir: string, locale: string): Promise<void> {
|
||||||
|
|
@ -222,10 +270,12 @@ export async function extractBundledSkill(skillName: string, targetDir: string,
|
||||||
throw new Error(\`Skill not found: \${skillName}\`)
|
throw new Error(\`Skill not found: \${skillName}\`)
|
||||||
}
|
}
|
||||||
|
|
||||||
await mkdir(targetDir, { recursive: true })
|
const { mkdir: mkdirSync, writeFile: writeFileSync } = await import('fs/promises')
|
||||||
for (const [relativePath, content] of Object.entries(skillFiles)) {
|
const { join: pathJoin, dirname: pathDirname } = await import('path')
|
||||||
await mkdir(join(targetDir, dirname(relativePath)), { recursive: true })
|
await mkdirSync(targetDir, { recursive: true })
|
||||||
await writeFile(join(targetDir, relativePath), content, "utf-8")
|
for (const [relativePath, fileContent] of Object.entries(skillFiles)) {
|
||||||
|
await mkdirSync(pathJoin(targetDir, pathDirname(relativePath)), { recursive: true })
|
||||||
|
await writeFileSync(pathJoin(targetDir, relativePath), fileContent, 'utf-8')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
@ -284,7 +334,7 @@ async function generateBuiltinReview() {
|
||||||
|
|
||||||
await generateBuiltinSkills(commitSha)
|
await generateBuiltinSkills(commitSha)
|
||||||
|
|
||||||
console.log('\n💡 Run `bun run build` to compile the extension\n')
|
console.log('\n💡 Run `bun run build` to compile\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
generateBuiltinReview().catch(console.error)
|
generateBuiltinReview().catch(console.error)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import type { ToolUseContext } from '../../Tool.js'
|
import type { ToolUseContext } from '../../Tool.js'
|
||||||
|
import { clearCommandsCache } from '../../commands.js'
|
||||||
import type {
|
import type {
|
||||||
LocalJSXCommandContext,
|
LocalJSXCommandContext,
|
||||||
LocalJSXCommandOnDone,
|
LocalJSXCommandOnDone,
|
||||||
|
|
@ -9,6 +10,7 @@ import {
|
||||||
getLanguageDisplayName,
|
getLanguageDisplayName,
|
||||||
getResolvedLanguage,
|
getResolvedLanguage,
|
||||||
} from '../../utils/language.js'
|
} from '../../utils/language.js'
|
||||||
|
import { Extension as ReviewExtension } from '../../costrict/review/index.js'
|
||||||
|
|
||||||
const VALID_LANGS: readonly PreferredLanguage[] = ['en', 'zh', 'auto']
|
const VALID_LANGS: readonly PreferredLanguage[] = ['en', 'zh', 'auto']
|
||||||
|
|
||||||
|
|
@ -40,6 +42,10 @@ export async function call(
|
||||||
const lang = arg as PreferredLanguage
|
const lang = arg as PreferredLanguage
|
||||||
saveGlobalConfig(current => ({ ...current, preferredLanguage: lang }))
|
saveGlobalConfig(current => ({ ...current, preferredLanguage: lang }))
|
||||||
|
|
||||||
|
// Re-extract review skills for the new locale and refresh skill caches
|
||||||
|
await ReviewExtension.initializeBuiltinSkills().catch(() => {})
|
||||||
|
clearCommandsCache()
|
||||||
|
|
||||||
const resolved = getResolvedLanguage()
|
const resolved = getResolvedLanguage()
|
||||||
const suffix = lang === 'auto' ? ` → ${getLanguageDisplayName(resolved)}` : ''
|
const suffix = lang === 'auto' ? ` → ${getLanguageDisplayName(resolved)}` : ''
|
||||||
onDone(`Language set to ${getLanguageDisplayName(lang)}${suffix}`, {
|
onDone(`Language set to ${getLanguageDisplayName(lang)}${suffix}`, {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# Code Review
|
# Code Review
|
||||||
|
|
||||||
Please perform a code review on: $ARGUMENTS
|
Please use the Skill tool to load the `review` skill to perform a code review on: $ARGUMENTS
|
||||||
|
|
||||||
Please respond and write all files in English throughout the entire process.
|
Please respond and write all files in English throughout the entire process.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# 代码审查
|
# 代码审查
|
||||||
|
|
||||||
请对以下内容执行代码审查:$ARGUMENTS
|
请使用 Skill 工具加载 `review` 技能来对以下内容执行代码审查:$ARGUMENTS
|
||||||
|
|
||||||
全程请使用中文进行回答与文件写入。
|
全程请使用中文进行回答与文件写入。
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { getResolvedLanguage } from 'src/utils/language.js'
|
||||||
import {
|
import {
|
||||||
extractBundledSkill,
|
extractBundledSkill,
|
||||||
getBuiltinSkillVersion,
|
getBuiltinSkillVersion,
|
||||||
listBuiltinSkills,
|
listBuiltinSkillNames,
|
||||||
} from './skill/builtin.js'
|
} from './skill/builtin.js'
|
||||||
|
|
||||||
const LOCALE_MAP: Record<string, string> = { zh: 'zh-CN', en: 'en' }
|
const LOCALE_MAP: Record<string, string> = { zh: 'zh-CN', en: 'en' }
|
||||||
|
|
@ -55,7 +55,7 @@ async function needsUpdate(
|
||||||
export async function initializeBuiltinSkills(): Promise<void> {
|
export async function initializeBuiltinSkills(): Promise<void> {
|
||||||
const locale = getLocale()
|
const locale = getLocale()
|
||||||
const skillsDir = getReviewSkillsDir()
|
const skillsDir = getReviewSkillsDir()
|
||||||
const skillNames = listBuiltinSkills()
|
const skillNames = listBuiltinSkillNames()
|
||||||
|
|
||||||
for (const skillName of skillNames) {
|
for (const skillName of skillNames) {
|
||||||
const skillDir = join(skillsDir, skillName)
|
const skillDir = join(skillsDir, skillName)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user