fix: remove agent-based review code reintroduced from main merge

Revert PR #43 agent-based code that was brought in during the main merge:
- Remove generateBuiltinAgents and all agent-related logic from generate script
- Delete src/costrict/review/agent/ directory
- Delete src/commands/security-review.ts (handled by bundled skill)
- Restore PR #64 extension.ts with getClaudeConfigHomeDir approach
- Remove REVIEW_AGENTS from builtInAgents.ts
- Keep review command (needed for MCP entry), but use CommandLocale instead of agent

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
kingboung 2026-05-12 15:32:07 +08:00
parent a645f34ee5
commit 4c56bbe268
7 changed files with 62 additions and 314 deletions

View File

@ -2,7 +2,7 @@ import { feature } from 'bun:bundle'
import { getIsNonInteractiveSession } from 'src/bootstrap/state.js'
import { getFeatureValue_CACHED_MAY_BE_STALE } from 'src/services/analytics/growthbook.js'
import { isEnvTruthy } from 'src/utils/envUtils.js'
import { REVIEW_AGENTS } from 'src/costrict/review/agent/builtin.js'
import { DESIGN_AGENT } from 'src/costrict/agents/designAgent.js'
import { QUICK_EXPLORE_AGENT } from 'src/costrict/agents/quickExplore.js'
import { REQUIREMENT_AGENT } from 'src/costrict/agents/requirement.js'
@ -112,8 +112,5 @@ export function getBuiltInAgents(): AgentDefinition[] {
agents.push(VERIFICATION_AGENT)
}
// Review agents (CoStrictReviewer, CoStrictValidator)
agents.push(...REVIEW_AGENTS)
return agents
}

View File

@ -1,10 +1,10 @@
#!/usr/bin/env bun
/**
* Downloads builtin review skills & agents from costrict-review repo and generates
* src/costrict/review/skill/builtin.ts and src/costrict/review/agent/builtin.ts
* Downloads builtin review skills from costrict-review repo and generates
* src/costrict/review/skill/builtin.ts
*
* Uses git SSH transport (git ls-remote + git clone).
* Reads index.json manifest to discover resources and their per-locale paths.
* Reads index.json manifest to discover skills and their per-locale paths.
* Compares remote commit SHA with cached version and skips download if unchanged.
*
* Usage: bun run scripts/generate-review-builtin.ts
@ -14,7 +14,6 @@ import fs from 'fs/promises'
import path from 'path'
import { fileURLToPath } from 'url'
import { spawnSync } from 'child_process'
import matter from 'gray-matter'
import { mkdir } from 'fs/promises'
const __filename = fileURLToPath(import.meta.url)
@ -22,35 +21,14 @@ const __dirname = path.dirname(__filename)
const bundledReviewDir = path.resolve(__dirname, '../packages/builtin-tools/bundled-review')
const builtinSkillsFile = path.resolve(__dirname, '../src/costrict/review/skill/builtin.ts')
const builtinAgentsFile = path.resolve(__dirname, '../src/costrict/review/agent/builtin.ts')
type IndexJson = {
agents: Array<{
name: string
path: Record<string, string>
opencode?: Record<string, unknown>
claudecode?: Record<string, unknown>
}>
skills: Array<{ name: string; path: Record<string, string> }>
}
const REPO = 'zgsm-ai/costrict-review'
const BRANCH = 'main'
const CLONE_URL = getCloneUrl()
function getCloneUrl(): string {
// Try gh CLI token first, then env vars, fall back to public HTTPS
const ghToken = (() => {
try {
return spawnSync('gh', ['auth', 'token'], { encoding: 'utf-8' }).stdout?.trim() || ''
} catch { return '' }
})()
const token = ghToken || process.env.GH_TOKEN || process.env.GITHUB_TOKEN || ''
if (token) {
return `https://x-access-token:${token}@github.com/${REPO}.git`
}
return `https://github.com/${REPO}.git`
}
const CLONE_URL = `git@github.com:${REPO}.git`
function git(...args: string[]): { ok: boolean; stdout: string; stderr: string } {
const result = spawnSync('git', args, { encoding: 'utf-8' })
@ -108,21 +86,9 @@ function collectLocales(index: IndexJson): string[] {
for (const skill of index.skills) {
for (const locale of Object.keys(skill.path)) localeSet.add(locale)
}
for (const agent of index.agents) {
for (const locale of Object.keys(agent.path)) localeSet.add(locale)
}
return [...localeSet].sort()
}
function mergeClaudecodeFrontmatter(
mdContent: string,
claudecodeFields: Record<string, unknown>,
): string {
const md = matter(mdContent)
const merged = { ...md.data, ...claudecodeFields }
return matter.stringify(md.content, merged)
}
async function cloneAndCopy(
cloneDir: string,
index: IndexJson,
@ -141,7 +107,6 @@ async function cloneAndCopy(
for (const locale of locales) {
const outputLocaleDir = path.join(bundledReviewDir, locale)
// Copy skill dirs for this locale
const skillPaths = index.skills
.map(s => s.path[locale])
.filter(Boolean)
@ -167,36 +132,6 @@ async function cloneAndCopy(
const fileCount = (await walk(outputDir)).length
console.log(`${locale}/skills/${skillName}: ${fileCount} files`)
}
// Copy agent files for this locale, merging claudecode frontmatter
const agentEntries = index.agents
.map(a => ({ name: a.name, filePath: a.path[locale], claudecode: a.claudecode }))
.filter(e => e.filePath)
for (const { name, filePath, claudecode } of agentEntries) {
const srcFile = path.join(cloneDir, filePath)
const outputDir = path.join(outputLocaleDir, 'agents')
await fs.mkdir(outputDir, { recursive: true })
const filename = path.basename(filePath)
const destFile = path.join(outputDir, filename)
if (claudecode) {
const rawContent = await fs.readFile(srcFile, 'utf-8')
const merged = mergeClaudecodeFrontmatter(rawContent, claudecode)
await fs.writeFile(destFile, merged, 'utf-8')
} else {
await fs.cp(srcFile, destFile)
}
try {
await fs.access(destFile)
} catch {
throw new Error(`Agent "${name}" (${locale}) missing at ${filePath}`)
}
console.log(`${locale}/agents/${filename}`)
}
}
await fs.rm(cloneDir, { recursive: true, force: true })
@ -300,155 +235,8 @@ export async function extractBundledSkill(skillName: string, targetDir: string,
console.log(`\n✓ Generated ${builtinSkillsFile}`)
}
async function generateBuiltinAgents(
commitSha: string,
): Promise<void> {
const localeEntries = await fs.readdir(bundledReviewDir).catch(() => [] as string[])
const locales: string[] = []
for (const l of localeEntries) {
if ((await fs.stat(path.join(bundledReviewDir, l)).catch(() => null))?.isDirectory()) {
locales.push(l)
}
}
// Discover agent names from first locale's agents dir
const allAgentNames: string[] = []
for (const locale of locales) {
const agentsDir = path.join(bundledReviewDir, locale, 'agents')
const entries = await fs.readdir(agentsDir).catch(() => [] as string[])
for (const name of entries) {
if (!allAgentNames.includes(name)) allAgentNames.push(name)
}
break
}
const imports: string[] = []
const agentCodeEntries: string[] = []
let fileIdx = 0
let primaryAgent = ''
let subAgent = ''
for (const agentName of allAgentNames) {
const agentKey = agentName.replace(/\.md$/, '')
const localeEntriesList: string[] = []
const promptVarNames: string[] = []
for (const locale of [...locales].sort()) {
const agentFile = path.join(bundledReviewDir, locale, 'agents', agentName)
try {
const content = await fs.readFile(agentFile, 'utf-8')
const varName = `REVIEW_AGENT_${fileIdx++}`
imports.push(`const ${varName} = ${JSON.stringify(content)}`)
localeEntriesList.push(`"${locale}": ${varName}`)
promptVarNames.push(varName)
if (content.includes('mode: primary')) primaryAgent = agentKey
if (content.includes('mode: subagent')) subAgent = agentKey
} catch {
// Agent file not found for this locale, skip
}
}
if (localeEntriesList.length > 0) {
const promptRecordName = `${agentKey.toUpperCase().replace(/-/g, '_')}_PROMPTS`
agentCodeEntries.push(`const ${promptRecordName}: Record<string, string> = {\n${localeEntriesList.join(',\n')}\n}`)
}
}
// Now generate the BuiltInAgentDefinition array
// Parse merged frontmatter from the zh-CN agent files to extract fields
const agentDefs: string[] = []
for (const agentName of allAgentNames) {
const agentKey = agentName.replace(/\.md$/, '')
const promptRecordName = `${agentKey.toUpperCase().replace(/-/g, '_')}_PROMPTS`
// Read zh-CN version to parse frontmatter for fields
const zhAgentFile = path.join(bundledReviewDir, 'zh-CN', 'agents', agentName)
let frontmatterData: Record<string, unknown> = {}
try {
const content = await fs.readFile(zhAgentFile, 'utf-8')
const parsed = matter(content)
frontmatterData = parsed.data as Record<string, unknown>
} catch {
// fallback
}
// Extract fields from frontmatter
const whenToUse = String(frontmatterData['whenToUse'] ?? frontmatterData['description'] ?? '')
const toolsStr = String(frontmatterData['tools'] ?? '')
const tools = toolsStr ? toolsStr.split(',').map((t: string) => t.trim()).filter(Boolean) : undefined
const permissionMode = String(frontmatterData['permissionMode'] ?? 'default')
const model = String(frontmatterData['model'] ?? 'inherit')
const mode = String(frontmatterData['mode'] ?? '')
// Determine disallowedTools based on tools availability
const toolNameSet = new Set(tools ?? [])
const disallowedTools: string[] = []
if (!toolNameSet.has('Agent')) disallowedTools.push('Agent')
if (!toolNameSet.has('FileEdit')) disallowedTools.push('FileEdit')
if (!toolNameSet.has('FileWrite')) disallowedTools.push('FileWrite')
if (!toolNameSet.has('NotebookEdit')) disallowedTools.push('NotebookEdit')
// Determine visibleTo based on mode
const visibleTo = mode === 'primary' ? undefined : ['CoStrictReviewer']
const toolsLiteral = tools ? JSON.stringify(tools) : 'undefined'
const disallowedLiteral = disallowedTools.length > 0
? `[${disallowedTools.map((t: string) => `'${t}'`).join(', ')}]`
: 'undefined'
const visibleToLiteral = visibleTo ? JSON.stringify(visibleTo) : 'undefined'
agentDefs.push(` {
agentType: '${agentKey}',
whenToUse: ${JSON.stringify(whenToUse)},
tools: ${toolsLiteral} as string[] | undefined,
disallowedTools: ${disallowedLiteral} as string[] | undefined,
permissionMode: '${permissionMode}',
model: '${model}',
source: 'built-in',
baseDir: 'built-in',
visibleTo: ${visibleToLiteral} as string[] | undefined,
getSystemPrompt: (_params) => {
const lang = getResolvedLanguage()
const locale = LOCALE_MAP[lang] ?? 'zh-CN'
return ${promptRecordName}[locale] ?? ${promptRecordName}['zh-CN']
},
}`)
}
const content = `// This file is auto-generated by scripts/generate-review-builtin.ts
// Do not edit manually
// Agents are downloaded from zgsm-ai/costrict-review repository
import type { BuiltInAgentDefinition } from '@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js'
import { getResolvedLanguage } from 'src/utils/language.js'
const LOCALE_MAP: Record<string, string> = { zh: 'zh-CN', en: 'en' }
${imports.join('\n')}
${agentCodeEntries.join('\n\n')}
export const REVIEW_AGENTS: BuiltInAgentDefinition[] = [
${agentDefs.join(',\n')}
]
export const AGENT_VERSIONS: Record<string, string> = {
${allAgentNames.map(n => ` "${n.replace(/\.md$/, '')}": "${commitSha}"`).join(',\n')}
}
export const PRIMARY_REVIEW_AGENT = ${JSON.stringify(primaryAgent)}
export const SUB_REVIEW_AGENT = ${JSON.stringify(subAgent)}
`
await mkdir(path.dirname(builtinAgentsFile), { recursive: true })
await fs.writeFile(builtinAgentsFile, content, 'utf-8')
console.log(`✓ Generated ${builtinAgentsFile}`)
}
async function generateBuiltinReview() {
console.log('\n🚀 CSC — Downloading Builtin Review Resources\n')
console.log('\n🚀 CSC — Downloading Builtin Review Skills\n')
await fs.mkdir(bundledReviewDir, { recursive: true })
@ -496,8 +284,6 @@ async function generateBuiltinReview() {
await generateBuiltinSkills(commitSha)
await generateBuiltinAgents(commitSha)
console.log('\n💡 Run `bun run build` to compile the extension\n')
}

View File

@ -3,12 +3,14 @@ import type { Command } from '../commands.js'
import type { ToolUseContext } from '../Tool.js'
import { isUltrareviewEnabled } from './review/ultrareviewEnabled.js'
import { CommandLocale } from 'src/costrict/command/locales/index.js'
import { PRIMARY_REVIEW_AGENT } from 'src/costrict/review/agent/builtin.js'
// Legal wants the explicit surface name plus a docs link visible before the
// user triggers, so the description carries "Claude Code on the web" + URL.
const CCR_TERMS_URL = 'https://costrict.ai/docs/en/claude-code-on-the-web'
// /review is registered as a bundled skill via extract-to-disk mechanism,
// discovered by the standard skill scanner. Here we provide a prompt-based
// command that routes to the Skill tool for non-skill contexts (e.g. MCP).
const review: Command = {
type: 'prompt',
name: 'review',
@ -16,12 +18,14 @@ const review: Command = {
progressMessage: 'reviewing code',
contentLength: 0,
source: 'builtin',
agent: PRIMARY_REVIEW_AGENT || 'CoStrictReviewer',
async getPromptForCommand(args, _context): Promise<ContentBlockParam[]> {
return [{ type: 'text', text: CommandLocale.get('review').replace('$ARGUMENTS', args) }]
},
}
// /ultrareview is the ONLY entry point to the remote bughunter path —
// /review stays purely local. local-jsx type renders the overage permission
// dialog when free reviews are exhausted.
const ultrareview: Command = {
type: 'local-jsx',
name: 'ultrareview',

View File

@ -1,18 +0,0 @@
import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages.js'
import type { Command } from '../commands.js'
import type { ToolUseContext } from '../Tool.js'
import { CommandLocale } from 'src/costrict/command/locales/index.js'
const securityReview: Command = {
type: 'prompt',
name: 'security-review',
description: 'Complete a security review of the pending changes on the current branch',
progressMessage: 'analyzing code changes for security risks',
contentLength: 0,
source: 'builtin',
async getPromptForCommand(args, _context): Promise<ContentBlockParam[]> {
return [{ type: 'text', text: CommandLocale.get('security-review').replace('$ARGUMENTS', args) }]
},
}
export default securityReview

View File

@ -1,87 +1,72 @@
/**
* CoStrict Skill Extension
*
* Initializes builtin review skills by extracting them from embedded
* SKILL_FILES to a cache directory on disk.
*
* Version tracking uses commit SHA + locale in a .version file.
* Skills are re-extracted when version or locale changes.
*/
import path from 'path'
import { writeFile, readFile, rm, mkdir, stat } from 'fs/promises'
import { mkdir, readFile, writeFile } from 'fs/promises'
import { join } from 'path'
import { getClaudeConfigHomeDir } from 'src/utils/envUtils.js'
import { getResolvedLanguage } from 'src/utils/language.js'
import * as Builtin from './skill/builtin.js'
import {
extractBundledSkill,
getBuiltinSkillVersion,
listBuiltinSkills,
} from './skill/builtin.js'
const LOCALE_MAP: Record<string, string> = { zh: 'zh-CN', en: 'en' }
function getSkillCacheDir(): string {
const home = process.env.HOME ?? process.env.USERPROFILE ?? ''
return path.join(home, '.claude', 'skills')
function getLocale(): string {
const lang = getResolvedLanguage()
return LOCALE_MAP[lang] ?? 'zh-CN'
}
function getReviewSkillsDir(): string {
return join(getClaudeConfigHomeDir(), 'skills')
}
function getVersionFilePath(skillDir: string): string {
return path.join(skillDir, '.version')
return join(skillDir, '.version')
}
async function getInstalledVersion(skillDir: string): Promise<string | null> {
try {
const content = await readFile(getVersionFilePath(skillDir), 'utf-8')
return content.trim()
return await readFile(getVersionFilePath(skillDir), 'utf-8')
} catch {
return null
}
}
async function needsUpdate(skillDir: string, skillName: string, locale: string): Promise<boolean> {
const builtinVersion = Builtin.getBuiltinSkillVersion(skillName)
if (!builtinVersion) return true
const installedVersion = await getInstalledVersion(skillDir)
const expectedVersion = `${builtinVersion}:${locale}`
return installedVersion !== expectedVersion
}
async function writeVersionFile(skillDir: string, skillName: string, locale: string): Promise<void> {
const builtinVersion = Builtin.getBuiltinSkillVersion(skillName)
async function writeVersionFile(
skillDir: string,
skillName: string,
locale: string,
): Promise<void> {
const builtinVersion = getBuiltinSkillVersion(skillName)
if (!builtinVersion) return
await mkdir(skillDir, { recursive: true })
await writeFile(getVersionFilePath(skillDir), `${builtinVersion}:${locale}`, 'utf-8')
}
async function needsUpdate(
skillDir: string,
skillName: string,
locale: string,
): Promise<boolean> {
const builtinVersion = getBuiltinSkillVersion(skillName)
if (!builtinVersion) return true
const installed = await getInstalledVersion(skillDir)
return installed !== `${builtinVersion}:${locale}`
}
export async function initializeBuiltinSkills(): Promise<void> {
const lang = getResolvedLanguage()
const locale = LOCALE_MAP[lang] ?? 'zh-CN'
const locale = getLocale()
const skillsDir = getReviewSkillsDir()
const skillNames = listBuiltinSkills()
const cacheDir = getSkillCacheDir()
const skillNames = Builtin.listBuiltinSkills()
for (const skillName of skillNames) {
const skillDir = join(skillsDir, skillName)
if (!(await needsUpdate(skillDir, skillName, locale))) continue
for (const name of skillNames) {
const skillDir = path.join(cacheDir, name)
const dirExists = await stat(skillDir).then(s => s.isDirectory()).catch(() => false)
if (dirExists) {
const updateNeeded = await needsUpdate(skillDir, name, locale)
if (!updateNeeded) continue
try {
await rm(skillDir, { recursive: true, force: true })
} catch {
// Continue with copy over existing files
}
}
await Builtin.extractBundledSkill(name, skillDir, locale)
await writeVersionFile(skillDir, name, locale)
const skillFiles = Builtin.listSkillFiles(name, locale)
const builtinVersion = Builtin.getBuiltinSkillVersion(name)
console.log(` [review] initialized skill "${name}" (${locale}, ${skillFiles.length} files, v${builtinVersion?.slice(0, 7)})`)
await mkdir(skillDir, { recursive: true })
await extractBundledSkill(skillName, skillDir, locale)
await writeVersionFile(skillDir, skillName, locale)
}
}
export function getBuiltinSkillsDir(): string {
return getSkillCacheDir()
return getReviewSkillsDir()
}

View File

@ -1,15 +1,9 @@
/**
* CoStrict Review Module
*
* Provides builtin review skills and agents that are embedded
* in the binary and extracted to cache on first run.
* Provides builtin review skills that are extracted to disk at runtime
* and discovered by the standard skill scanner.
*/
export * as Extension from './extension.js'
export * as SkillBuiltin from './skill/builtin.js'
export {
REVIEW_AGENTS,
AGENT_VERSIONS,
PRIMARY_REVIEW_AGENT,
SUB_REVIEW_AGENT,
} from './agent/builtin.js'
export * as Extension from './extension.js'

View File

@ -1,6 +1,6 @@
import { feature } from 'bun:bundle'
import { shouldAutoEnableClaudeInChrome } from 'src/utils/claudeInChrome/setup.js'
import { initializeBuiltinSkills } from 'src/costrict/review/extension.js'
import { Extension as ReviewExtension } from 'src/costrict/review/index.js'
import { registerBatchSkill } from './batch.js'
import { registerClaudeInChromeSkill } from './claudeInChrome.js'
import { registerDebugSkill } from './debug.js'
@ -30,8 +30,8 @@ import { registerTddSkill } from 'src/costrict/skills/tdd.js'
* 3. Import and call that function here
*/
export function initBundledSkills(): void {
// Initialize builtin review skills (extract to cache if needed)
initializeBuiltinSkills().catch(() => {})
// Extract review skills to disk for standard scanner discovery
ReviewExtension.initializeBuiltinSkills().catch(() => {})
registerUpdateConfigSkill()
registerProjectWikiSkill()