diff --git a/package.json b/package.json index 08ed715b..9313cf16 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "@tanstack/react-query": "^5.90.12", "@tanstack/react-router": "^1.140.0", "@tanstack/react-store": "^0.8.0", + "@types/yargs": "^17.0.35", "big.js": "^7.0.1", "bip39": "^3.1.0", "buffer": "^6.0.3", @@ -96,6 +97,7 @@ "react-inspector": "^9.0.0", "tailwind-merge": "^3.4.0", "tw-animate-css": "^1.4.0", + "yargs": "^18.0.0", "zod": "^4.1.13" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b37d2db7..3f03e2f9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -95,6 +95,9 @@ importers: '@tanstack/react-store': specifier: ^0.8.0 version: 0.8.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@types/yargs': + specifier: ^17.0.35 + version: 17.0.35 big.js: specifier: ^7.0.1 version: 7.0.1 @@ -143,6 +146,9 @@ importers: tw-animate-css: specifier: ^1.4.0 version: 1.4.0 + yargs: + specifier: ^18.0.0 + version: 18.0.0 zod: specifier: ^4.1.13 version: 4.2.1 @@ -2551,6 +2557,12 @@ packages: '@types/ws@8.5.3': resolution: {integrity: sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==} + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} @@ -3121,6 +3133,10 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -6137,6 +6153,10 @@ packages: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yargs@16.2.0: resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} engines: {node: '>=10'} @@ -6145,6 +6165,10 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yeast@0.1.2: resolution: {integrity: sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==} @@ -8704,6 +8728,12 @@ snapshots: dependencies: '@types/node': 24.10.4 + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + '@ungap/structured-clone@1.3.0': {} '@vanilla-extract/css@1.18.0': @@ -9391,6 +9421,12 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.1.2 + wrap-ansi: 9.0.2 + clsx@2.1.1: {} cluster-key-slot@1.1.2: {} @@ -12549,6 +12585,8 @@ snapshots: yargs-parser@21.1.1: {} + yargs-parser@22.0.0: {} + yargs@16.2.0: dependencies: cliui: 7.0.4 @@ -12569,6 +12607,15 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yargs@18.0.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 7.2.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + yeast@0.1.2: {} yoctocolors-cjs@2.1.3: {} diff --git a/scripts/agent/cli.ts b/scripts/agent/cli.ts index e1c06a3c..85b4b271 100644 --- a/scripts/agent/cli.ts +++ b/scripts/agent/cli.ts @@ -4,410 +4,53 @@ * * Usage: * pnpm agent readme # 输出索引(最佳实践 + 知识地图) + * pnpm agent --help # 查看帮助 * pnpm agent roadmap [version] # 查看任务列表 * pnpm agent claim # 领取任务 * pnpm agent done # 完成任务 * pnpm agent create # 创建任务 - * pnpm agent epic create <title> # 创建 Epic - * pnpm agent epic list # 查看 Epic 列表 - * pnpm agent epic view <#> # 查看 Epic 详情 - * pnpm agent epic sync <#> # 同步 Epic 子任务状态 - * pnpm agent epic add <epic#> <issue#> # 添加子任务到 Epic * pnpm agent stats # 进度统计 * pnpm agent toc # 白皮书目录 * pnpm agent chapter <name> # 查阅白皮书章节 - * pnpm agent worktree create <name> --branch <branch> [--base main] - * pnpm agent worktree delete <name> [--force] - * pnpm agent worktree list - * pnpm agent practice list|add|remove|update + * pnpm agent epic <action> # Epic 管理 + * pnpm agent worktree <action> # Worktree 管理 + * pnpm agent practice [action] # 最佳实践管理 */ -import { log } from './utils' -import { printIndex } from './readme' -import { - printRoadmap, - claimIssue, - completeIssue, - createIssue, - printStats, -} from './roadmap' -import { - createEpic, - listEpics, - viewEpic, - syncEpicStatus, - addSubIssueToEpic, -} from './epic' -import { printWhiteBookToc, printChapterContent } from './whitebook' -import { createWorktree, deleteWorktree, listWorktrees } from './worktree' -import { addPractice, listPractices, removePractice, updatePractice } from './practice' - -function printHelp(): void { - console.log(` -AI Agent CLI - -Usage: - pnpm agent readme # 输出索引(最佳实践 + 知识地图) - pnpm agent --help # 查看帮助 - pnpm agent roadmap [current|v1|v2|draft] # 查看任务列表 - pnpm agent claim <issue#> # 领取任务(分配给自己) - pnpm agent done <issue#> # 完成任务(关闭 Issue) - pnpm agent create <title> # 创建新任务 - [--body <描述>] [--roadmap v1|v2|draft] [--category feature|bug|refactor] - pnpm agent stats # 查看进度统计 - pnpm agent toc # 白皮书目录结构 - pnpm agent chapter <name> # 查阅白皮书章节 - -Epic 管理: - pnpm agent epic create <title> # 创建 Epic - [--desc <描述>] [--roadmap v1] [--issues 1,2,3] - pnpm agent epic list # 查看所有 Epic - pnpm agent epic view <#> # 查看 Epic 详情 - pnpm agent epic sync <#> # 同步子任务状态 - pnpm agent epic add <epic#> <issue#> # 添加子任务到 Epic - -Worktree 管理: - pnpm agent worktree create <name> --branch <branch> [--base main] - pnpm agent worktree delete <name> [--force] - pnpm agent worktree list - 分支前缀仅允许: feat/, fix/, docs/, test/, refactor/, chore/, ci/, openspec/, release/ - -最佳实践管理: - pnpm agent practice list - pnpm agent practice add "<内容>" - pnpm agent practice remove <序号|内容> - pnpm agent practice update <序号> "<内容>" - -Aliases: CURRENT -> V1, NEXT -> V2 - -Examples: - pnpm agent readme - pnpm agent roadmap current - pnpm agent claim 28 - pnpm agent create "修复某个问题" --category bug --roadmap v1 - pnpm agent epic create "大功能" --roadmap v1 --issues 44,45,46 - pnpm agent worktree create issue-28 --branch feat/issue-28 - pnpm agent practice list -`) -} - -function getFlagValue(args: string[], flag: string): string | undefined { - const index = args.indexOf(flag) - if (index === -1) return undefined - return args[index + 1] -} - -function hasFlag(args: string[], flag: string): boolean { - return args.includes(flag) -} - -function runLegacy(args: string[]): boolean { - const knownFlags = new Set(['--roadmap', '--claim', '--done', '--create', '--stats', '--toc', '--chapter', '--epic']) - if (!args.some(arg => knownFlags.has(arg))) return false - - log.warn('检测到旧版参数,建议使用子命令形式(例如:pnpm agent roadmap current)') - - const claimIndex = args.indexOf('--claim') - if (claimIndex !== -1 && args[claimIndex + 1]) { - claimIssue(args[claimIndex + 1]) - return true - } - - const doneIndex = args.indexOf('--done') - if (doneIndex !== -1 && args[doneIndex + 1]) { - completeIssue(args[doneIndex + 1]) - return true - } - - if (args.includes('--stats')) { - printStats() - return true - } - - const createIndex = args.indexOf('--create') - if (createIndex !== -1 && args[createIndex + 1]) { - const title = args[createIndex + 1] - const body = getFlagValue(args, '--body') - const roadmap = getFlagValue(args, '--roadmap') - const category = getFlagValue(args, '--category') - createIssue({ - title, - body, - roadmap: roadmap ?? 'DRAFT', - category: category ?? 'feature', - }) - return true - } - - const roadmapIndex = args.indexOf('--roadmap') - if (roadmapIndex !== -1) { - printRoadmap(args[roadmapIndex + 1]) - return true - } - - const epicIndex = args.indexOf('--epic') - if (epicIndex !== -1) { - handleEpic(args.slice(epicIndex + 1)) - return true - } - - if (args.includes('--toc')) { - printWhiteBookToc() - return true - } - - const chapterIndex = args.indexOf('--chapter') - if (chapterIndex !== -1 && args[chapterIndex + 1]) { - log.section(`章节: ${args[chapterIndex + 1]}`) - printChapterContent(args[chapterIndex + 1]) - return true - } - - return false -} - -function handleEpic(args: string[]): void { - const subCommand = args[0] - - switch (subCommand) { - case 'create': { - const title = args[1] - if (!title) { - log.error('请提供 Epic 标题') - process.exit(1) - } - const desc = getFlagValue(args, '--desc') - const roadmap = getFlagValue(args, '--roadmap') - const issuesRaw = getFlagValue(args, '--issues') - const subIssues = issuesRaw - ? issuesRaw.split(',').map(value => parseInt(value.trim(), 10)).filter(Boolean) - : [] - - createEpic({ - title, - description: desc, - roadmap: roadmap ?? 'V1', - subIssues, - }) - break - } - - case 'list': - listEpics() - break - - case 'view': { - const epicNum = parseInt(args[1] ?? '', 10) - if (!epicNum) { - log.error('请提供 Epic 编号') - process.exit(1) - } - viewEpic(epicNum) - break - } - - case 'sync': { - const epicNum = parseInt(args[1] ?? '', 10) - if (!epicNum) { - log.error('请提供 Epic 编号') - process.exit(1) - } - syncEpicStatus(epicNum) - break - } - - case 'add': { - const epicNum = parseInt(args[1] ?? '', 10) - const issueNum = parseInt(args[2] ?? '', 10) - if (!epicNum || !issueNum) { - log.error('请提供 Epic 编号和 Issue 编号') - process.exit(1) - } - addSubIssueToEpic(epicNum, issueNum) - break - } - - default: - log.error(`未知的 Epic 子命令: ${subCommand}`) - console.log('可用命令: create, list, view, sync, add') - process.exit(1) - } -} - -function handleWorktree(args: string[]): void { - const subCommand = args[0] - - switch (subCommand) { - case 'create': { - const name = args[1] - const branch = getFlagValue(args, '--branch') - const base = getFlagValue(args, '--base') - createWorktree({ name, branch, base }) - break - } - - case 'delete': { - const name = args[1] - const force = hasFlag(args, '--force') - deleteWorktree({ name, force }) - break - } - - case 'list': - listWorktrees() - break - - default: - log.error(`未知的 worktree 子命令: ${subCommand}`) - console.log('可用命令: create, delete, list') - process.exit(1) - } -} - -function handlePractice(args: string[]): void { - const subCommand = args[0] - - if (!subCommand) { - listPractices() - return - } - - if (subCommand === '--help' || subCommand === '-h') { - console.log('用法: pnpm agent practice list|add|remove|update') - return - } - - switch (subCommand) { - case 'list': - listPractices() - break - - case 'add': - addPractice(args.slice(1).join(' ')) - break - - case 'remove': - removePractice(args.slice(1).join(' ')) - break - - case 'update': { - const index = args[1] - const text = args.slice(2).join(' ') - updatePractice(index, text) - break - } - - default: - log.error(`未知的 practice 子命令: ${subCommand}`) - console.log('可用命令: list, add, remove, update') - process.exit(1) - } -} - -function main(): void { - const args = process.argv.slice(2) - - if (args.length === 0) { - printHelp() - return - } - - if (args.includes('--help') || args.includes('-h')) { - printHelp() - return - } - - if (runLegacy(args)) { - return - } - - const [command, ...rest] = args - - switch (command) { - case 'readme': - printIndex() - break - - case 'roadmap': - printRoadmap(rest[0]) - break - - case 'claim': - if (!rest[0]) { - log.error('请提供 Issue 编号') - process.exit(1) - } - claimIssue(rest[0]) - break - - case 'done': - if (!rest[0]) { - log.error('请提供 Issue 编号') - process.exit(1) - } - completeIssue(rest[0]) - break - - case 'create': { - const title = rest[0] - if (!title) { - log.error('请提供 Issue 标题') - process.exit(1) - } - const body = getFlagValue(rest, '--body') - const roadmap = getFlagValue(rest, '--roadmap') - const category = getFlagValue(rest, '--category') - createIssue({ - title, - body, - roadmap: roadmap ?? 'DRAFT', - category: category ?? 'feature', - }) - break - } - - case 'stats': - printStats() - break - - case 'toc': - printWhiteBookToc() - break - - case 'chapter': { - const chapterName = rest[0] - if (!chapterName) { - log.error('请提供章节路径') - process.exit(1) - } - log.section(`章节: ${chapterName}`) - printChapterContent(chapterName) - break - } - - case 'epic': - handleEpic(rest) - break - - case 'worktree': - handleWorktree(rest) - break - - case 'practice': - handlePractice(rest) - break - - case 'help': - log.error('请使用 --help 查看帮助') - printHelp() - process.exit(1) - break - - default: - log.error(`未知命令: ${command}`) - printHelp() - process.exit(1) - } -} - -main() +import yargs from 'yargs' +import { hideBin } from 'yargs/helpers' + +import readmeCommand from './commands/readme' +import roadmapCommand from './commands/roadmap' +import claimCommand from './commands/claim' +import doneCommand from './commands/done' +import createCommand from './commands/create' +import statsCommand from './commands/stats' +import tocCommand from './commands/toc' +import chapterCommand from './commands/chapter' +import epicCommand from './commands/epic' +import worktreeCommand from './commands/worktree' +import practiceCommand from './commands/practice' + +yargs(hideBin(process.argv)) + .scriptName('pnpm agent') + .usage('$0 <command> [options]') + .command(readmeCommand) + .command(roadmapCommand) + .command(claimCommand) + .command(doneCommand) + .command(createCommand) + .command(statsCommand) + .command(tocCommand) + .command(chapterCommand) + .command(epicCommand) + .command(worktreeCommand) + .command(practiceCommand) + .demandCommand(1, '请指定命令,使用 --help 查看可用命令') + .strict() + .help() + .alias('h', 'help') + .version(false) + .wrap(100) + .epilogue('Aliases: CURRENT -> V1, NEXT -> V2') + .parse() diff --git a/scripts/agent/commands/chapter.ts b/scripts/agent/commands/chapter.ts new file mode 100644 index 00000000..28f86790 --- /dev/null +++ b/scripts/agent/commands/chapter.ts @@ -0,0 +1,23 @@ +import type { ArgumentsCamelCase, CommandModule } from 'yargs' +import { printChapterContent } from '../handlers/whitebook' +import { log } from '../utils' + +interface ChapterArgs { + name: string +} + +export default { + command: 'chapter <name>', + describe: '查阅白皮书章节', + builder: { + name: { + type: 'string', + describe: '章节路径', + demandOption: true, + }, + }, + handler: (argv: ArgumentsCamelCase<ChapterArgs>) => { + log.section(`章节: ${argv.name}`) + printChapterContent(argv.name) + }, +} satisfies CommandModule<object, ChapterArgs> diff --git a/scripts/agent/commands/claim.ts b/scripts/agent/commands/claim.ts new file mode 100644 index 00000000..00c10209 --- /dev/null +++ b/scripts/agent/commands/claim.ts @@ -0,0 +1,21 @@ +import type { ArgumentsCamelCase, CommandModule } from 'yargs' +import { claimIssue } from '../handlers/roadmap' + +interface ClaimArgs { + issue: string +} + +export default { + command: 'claim <issue>', + describe: '领取任务(分配给自己)', + builder: { + issue: { + type: 'string', + describe: 'Issue 编号', + demandOption: true, + }, + }, + handler: (argv: ArgumentsCamelCase<ClaimArgs>) => { + claimIssue(argv.issue) + }, +} satisfies CommandModule<object, ClaimArgs> diff --git a/scripts/agent/commands/create.ts b/scripts/agent/commands/create.ts new file mode 100644 index 00000000..b58336d9 --- /dev/null +++ b/scripts/agent/commands/create.ts @@ -0,0 +1,43 @@ +import type { ArgumentsCamelCase, CommandModule } from 'yargs' +import { createIssue } from '../handlers/roadmap' + +interface CreateArgs { + title: string + body?: string + roadmap?: string + category?: string +} + +export default { + command: 'create <title>', + describe: '创建新任务', + builder: { + title: { + type: 'string', + describe: 'Issue 标题', + demandOption: true, + }, + body: { + type: 'string', + describe: '描述内容', + }, + roadmap: { + type: 'string', + describe: '版本 (v1|v2|draft)', + default: 'DRAFT', + }, + category: { + type: 'string', + describe: '分类 (feature|bug|refactor)', + default: 'feature', + }, + }, + handler: (argv: ArgumentsCamelCase<CreateArgs>) => { + createIssue({ + title: argv.title, + body: argv.body, + roadmap: argv.roadmap ?? 'DRAFT', + category: argv.category ?? 'feature', + }) + }, +} satisfies CommandModule<object, CreateArgs> diff --git a/scripts/agent/commands/done.ts b/scripts/agent/commands/done.ts new file mode 100644 index 00000000..38c03991 --- /dev/null +++ b/scripts/agent/commands/done.ts @@ -0,0 +1,21 @@ +import type { ArgumentsCamelCase, CommandModule } from 'yargs' +import { completeIssue } from '../handlers/roadmap' + +interface DoneArgs { + issue: string +} + +export default { + command: 'done <issue>', + describe: '完成任务(关闭 Issue)', + builder: { + issue: { + type: 'string', + describe: 'Issue 编号', + demandOption: true, + }, + }, + handler: (argv: ArgumentsCamelCase<DoneArgs>) => { + completeIssue(argv.issue) + }, +} satisfies CommandModule<object, DoneArgs> diff --git a/scripts/agent/commands/epic.ts b/scripts/agent/commands/epic.ts new file mode 100644 index 00000000..ce339f6e --- /dev/null +++ b/scripts/agent/commands/epic.ts @@ -0,0 +1,124 @@ +import type { ArgumentsCamelCase, CommandModule, Argv } from 'yargs' +import { + createEpic, + listEpics, + viewEpic, + syncEpicStatus, + addSubIssueToEpic, +} from '../handlers/epic' +import { log } from '../utils' + +interface EpicCreateArgs { + title: string + desc?: string + roadmap?: string + issues?: string +} + +interface EpicViewArgs { + number: number +} + +interface EpicSyncArgs { + number: number +} + +interface EpicAddArgs { + epic: number + issue: number +} + +export default { + command: 'epic <action>', + describe: 'Epic 管理', + builder: (yargs: Argv) => + yargs + .command<EpicCreateArgs>( + 'create <title>', + '创建 Epic', + (y) => + y + .positional('title', { + type: 'string', + describe: 'Epic 标题', + demandOption: true, + }) + .option('desc', { + type: 'string', + describe: '描述', + }) + .option('roadmap', { + type: 'string', + describe: '版本 (v1|v2)', + default: 'V1', + }) + .option('issues', { + type: 'string', + describe: '子任务编号,逗号分隔 (如: 1,2,3)', + }), + (argv) => { + const subIssues = argv.issues + ? argv.issues + .split(',') + .map((v) => parseInt(v.trim(), 10)) + .filter(Boolean) + : [] + createEpic({ + title: argv.title, + description: argv.desc, + roadmap: argv.roadmap ?? 'V1', + subIssues, + }) + } + ) + .command('list', '查看所有 Epic', () => {}, () => { + listEpics() + }) + .command<EpicViewArgs>( + 'view <number>', + '查看 Epic 详情', + (y) => + y.positional('number', { + type: 'number', + describe: 'Epic 编号', + demandOption: true, + }), + (argv) => { + viewEpic(argv.number) + } + ) + .command<EpicSyncArgs>( + 'sync <number>', + '同步子任务状态', + (y) => + y.positional('number', { + type: 'number', + describe: 'Epic 编号', + demandOption: true, + }), + (argv) => { + syncEpicStatus(argv.number) + } + ) + .command<EpicAddArgs>( + 'add <epic> <issue>', + '添加子任务到 Epic', + (y) => + y + .positional('epic', { + type: 'number', + describe: 'Epic 编号', + demandOption: true, + }) + .positional('issue', { + type: 'number', + describe: 'Issue 编号', + demandOption: true, + }), + (argv) => { + addSubIssueToEpic(argv.epic, argv.issue) + } + ) + .demandCommand(1, '请指定 epic 子命令'), + handler: () => {}, +} satisfies CommandModule diff --git a/scripts/agent/commands/practice.ts b/scripts/agent/commands/practice.ts new file mode 100644 index 00000000..c670adf0 --- /dev/null +++ b/scripts/agent/commands/practice.ts @@ -0,0 +1,80 @@ +import type { CommandModule, Argv } from 'yargs' +import { + listPractices, + addPractice, + removePractice, + updatePractice, +} from '../handlers/practice' + +interface PracticeAddArgs { + text: string +} + +interface PracticeRemoveArgs { + target: string +} + +interface PracticeUpdateArgs { + index: string + text: string +} + +export default { + command: 'practice [action]', + describe: '最佳实践管理', + builder: (yargs: Argv) => + yargs + .command('list', '列出所有最佳实践', () => {}, () => { + listPractices() + }) + .command<PracticeAddArgs>( + 'add <text>', + '添加最佳实践', + (y) => + y.positional('text', { + type: 'string', + describe: '最佳实践内容', + demandOption: true, + }), + (argv) => { + addPractice(argv.text) + } + ) + .command<PracticeRemoveArgs>( + 'remove <target>', + '删除最佳实践', + (y) => + y.positional('target', { + type: 'string', + describe: '序号或内容', + demandOption: true, + }), + (argv) => { + removePractice(argv.target) + } + ) + .command<PracticeUpdateArgs>( + 'update <index> <text>', + '更新最佳实践', + (y) => + y + .positional('index', { + type: 'string', + describe: '序号', + demandOption: true, + }) + .positional('text', { + type: 'string', + describe: '新内容', + demandOption: true, + }), + (argv) => { + updatePractice(argv.index, argv.text) + } + ), + handler: (argv) => { + if (!argv.action || argv.action === 'list') { + listPractices() + } + }, +} satisfies CommandModule diff --git a/scripts/agent/commands/readme.ts b/scripts/agent/commands/readme.ts new file mode 100644 index 00000000..7cb071ef --- /dev/null +++ b/scripts/agent/commands/readme.ts @@ -0,0 +1,10 @@ +import type { CommandModule } from 'yargs' +import { printIndex } from '../handlers/readme' + +export default { + command: 'readme', + describe: '输出索引(最佳实践 + 知识地图)', + handler: () => { + printIndex() + }, +} satisfies CommandModule diff --git a/scripts/agent/commands/roadmap.ts b/scripts/agent/commands/roadmap.ts new file mode 100644 index 00000000..c527635d --- /dev/null +++ b/scripts/agent/commands/roadmap.ts @@ -0,0 +1,20 @@ +import type { ArgumentsCamelCase, CommandModule } from 'yargs' +import { printRoadmap } from '../handlers/roadmap' + +interface RoadmapArgs { + version?: string +} + +export default { + command: 'roadmap [version]', + describe: '查看任务列表', + builder: { + version: { + type: 'string', + describe: '版本号 (current|v1|v2|draft)', + }, + }, + handler: (argv: ArgumentsCamelCase<RoadmapArgs>) => { + printRoadmap(argv.version) + }, +} satisfies CommandModule<object, RoadmapArgs> diff --git a/scripts/agent/commands/stats.ts b/scripts/agent/commands/stats.ts new file mode 100644 index 00000000..35f1f10e --- /dev/null +++ b/scripts/agent/commands/stats.ts @@ -0,0 +1,10 @@ +import type { CommandModule } from 'yargs' +import { printStats } from '../handlers/roadmap' + +export default { + command: 'stats', + describe: '查看进度统计', + handler: () => { + printStats() + }, +} satisfies CommandModule diff --git a/scripts/agent/commands/toc.ts b/scripts/agent/commands/toc.ts new file mode 100644 index 00000000..b8329e78 --- /dev/null +++ b/scripts/agent/commands/toc.ts @@ -0,0 +1,10 @@ +import type { CommandModule } from 'yargs' +import { printWhiteBookToc } from '../handlers/whitebook' + +export default { + command: 'toc', + describe: '白皮书目录结构', + handler: () => { + printWhiteBookToc() + }, +} satisfies CommandModule diff --git a/scripts/agent/commands/worktree.ts b/scripts/agent/commands/worktree.ts new file mode 100644 index 00000000..3b4d6d4b --- /dev/null +++ b/scripts/agent/commands/worktree.ts @@ -0,0 +1,80 @@ +import type { CommandModule, Argv } from 'yargs' +import { + createWorktree, + deleteWorktree, + listWorktrees, +} from '../handlers/worktree' + +interface WorktreeCreateArgs { + name: string + branch: string + base?: string +} + +interface WorktreeDeleteArgs { + name: string + force?: boolean +} + +export default { + command: 'worktree <action>', + describe: 'Worktree 管理', + builder: (yargs: Argv) => + yargs + .command<WorktreeCreateArgs>( + 'create <name>', + '创建 worktree', + (y) => + y + .positional('name', { + type: 'string', + describe: 'Worktree 名称', + demandOption: true, + }) + .option('branch', { + type: 'string', + describe: '分支名称', + demandOption: true, + }) + .option('base', { + type: 'string', + describe: '基础分支', + default: 'main', + }), + (argv) => { + createWorktree({ + name: argv.name, + branch: argv.branch, + base: argv.base, + }) + } + ) + .command<WorktreeDeleteArgs>( + 'delete <name>', + '删除 worktree', + (y) => + y + .positional('name', { + type: 'string', + describe: 'Worktree 名称', + demandOption: true, + }) + .option('force', { + type: 'boolean', + describe: '强制删除(跳过 PR 检查)', + default: false, + }), + (argv) => { + deleteWorktree({ + name: argv.name, + force: argv.force, + }) + } + ) + .command('list', '列出所有 worktree', () => {}, () => { + listWorktrees() + }) + .demandCommand(1, '请指定 worktree 子命令') + .epilogue('分支前缀仅允许: feat/, fix/, docs/, test/, refactor/, chore/, ci/, openspec/, release/'), + handler: () => {}, +} satisfies CommandModule diff --git a/scripts/agent/epic.ts b/scripts/agent/handlers/epic.ts similarity index 99% rename from scripts/agent/epic.ts rename to scripts/agent/handlers/epic.ts index 09b099df..a17ff3ec 100644 --- a/scripts/agent/epic.ts +++ b/scripts/agent/handlers/epic.ts @@ -3,7 +3,7 @@ */ import { execSync } from 'node:child_process' -import { ROOT, log } from './utils' +import { ROOT, log } from '../utils' import { createIssue, addIssueToProject, setIssueRelease, fetchRoadmap } from './roadmap' export interface EpicOptions { diff --git a/scripts/agent/practice.ts b/scripts/agent/handlers/practice.ts similarity index 98% rename from scripts/agent/practice.ts rename to scripts/agent/handlers/practice.ts index 05a6ca6f..5555e48d 100644 --- a/scripts/agent/practice.ts +++ b/scripts/agent/handlers/practice.ts @@ -3,7 +3,7 @@ */ import { readFileSync, writeFileSync, existsSync } from 'node:fs' -import { BEST_PRACTICES_FILE, log } from './utils' +import { BEST_PRACTICES_FILE, log } from '../utils' interface PracticeDocument { headerLines: string[] diff --git a/scripts/agent/readme.ts b/scripts/agent/handlers/readme.ts similarity index 98% rename from scripts/agent/readme.ts rename to scripts/agent/handlers/readme.ts index 92c044fe..b1972282 100644 --- a/scripts/agent/readme.ts +++ b/scripts/agent/handlers/readme.ts @@ -3,7 +3,7 @@ */ import { fetchRoadmap, printStats } from './roadmap' -import { resolveRelease } from './utils' +import { resolveRelease } from '../utils' import { printBestPracticesContent } from './practice' export function printBestPractices(): void { diff --git a/scripts/agent/roadmap.ts b/scripts/agent/handlers/roadmap.ts similarity index 99% rename from scripts/agent/roadmap.ts rename to scripts/agent/handlers/roadmap.ts index 0a129f64..9c4b49d3 100644 --- a/scripts/agent/roadmap.ts +++ b/scripts/agent/handlers/roadmap.ts @@ -12,7 +12,7 @@ import { RELEASE_OPTIONS, resolveRelease, log, -} from './utils' +} from '../utils' export interface RoadmapItem { id: string diff --git a/scripts/agent/whitebook.ts b/scripts/agent/handlers/whitebook.ts similarity index 97% rename from scripts/agent/whitebook.ts rename to scripts/agent/handlers/whitebook.ts index 3a5121ec..82125fb6 100644 --- a/scripts/agent/whitebook.ts +++ b/scripts/agent/handlers/whitebook.ts @@ -4,7 +4,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs' import { join, relative } from 'node:path' -import { WHITE_BOOK_DIR, colors, log } from './utils' +import { WHITE_BOOK_DIR, colors, log } from '../utils' export interface ChapterInfo { name: string diff --git a/scripts/agent/worktree.ts b/scripts/agent/handlers/worktree.ts similarity index 99% rename from scripts/agent/worktree.ts rename to scripts/agent/handlers/worktree.ts index 74f82d74..c216884d 100644 --- a/scripts/agent/worktree.ts +++ b/scripts/agent/handlers/worktree.ts @@ -5,7 +5,7 @@ import { execFileSync } from 'node:child_process' import { copyFileSync, existsSync, mkdirSync, statSync } from 'node:fs' import { basename, join, relative, resolve, sep } from 'node:path' -import { ROOT, WORKTREE_DIR, log } from './utils' +import { ROOT, WORKTREE_DIR, log } from '../utils' const ALLOWED_BRANCH_PREFIXES = [ 'feat/',