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:
kingboung 2026-05-12 15:41:21 +08:00
parent 4c56bbe268
commit 33bdcb9b74
5 changed files with 105 additions and 49 deletions

View File

@ -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)

View File

@ -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}`, {

View File

@ -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.

View File

@ -1,5 +1,5 @@
# 代码审查 # 代码审查
请对以下内容执行代码审查:$ARGUMENTS 使用 Skill 工具加载 `review` 技能来对以下内容执行代码审查:$ARGUMENTS
全程请使用中文进行回答与文件写入。 全程请使用中文进行回答与文件写入。

View File

@ -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)