mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
feat: 注册所有新命令到命令系统和工具注册表
- commands.ts: 注册所有新命令(memory-stores、vault、schedule 等), 移除 require() 动态加载,统一为 ESM import - tools.ts: 注册 LocalMemoryRecallTool、VaultHttpFetchTool - 补充命令测试(bridge-kick、commit、commit-push-pr、init-verifiers) - 补充工具测试(AgentTool、RemoteTrigger、SkillTool、WebFetch、WebSearch) - 集成测试:autonomy-lifecycle-user-flow 更新 - 探测脚本和功能文档 Co-Authored-By: glm-5-turbo <zai-org@claude-code-best.win>
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
|
||||
1. [Buddy 伴侣系统](#1-buddy-伴侣系统)
|
||||
2. [Remote Control 远程控制](#2-remote-control-远程控制)
|
||||
3. [定时任务 /schedule](#3-定时任务-schedule)
|
||||
3. [定时任务 /triggers](#3-定时任务-triggers)
|
||||
4. [Voice Mode 语音模式](#4-voice-mode-语音模式)
|
||||
5. [Chrome 浏览器控制](#5-chrome-浏览器控制)
|
||||
6. [Computer Use 屏幕操控](#6-computer-use-屏幕操控)
|
||||
@@ -72,19 +72,21 @@ CLAUDE_BRIDGE_BASE_URL=https://your-server.com CLAUDE_BRIDGE_OAUTH_TOKEN=your-to
|
||||
|
||||
---
|
||||
|
||||
## 3. 定时任务 /schedule
|
||||
## 3. 定时任务 /triggers
|
||||
|
||||
**PR**: #88 `feat: enable /schedule by adding AGENT_TRIGGERS_REMOTE`
|
||||
**Feature Flag**: `AGENT_TRIGGERS_REMOTE`
|
||||
|
||||
> 命令名已从 `/schedule` 改为 `/triggers`,避免与上游 bundled skill `schedule` 冲突。`/cron` 是别名。
|
||||
|
||||
### 说明
|
||||
创建定时执行的远程 agent 任务,支持 cron 表达式。
|
||||
|
||||
### 使用
|
||||
```
|
||||
/schedule create "每天检查依赖更新" --cron "0 9 * * *" --prompt "检查 package.json 中的过期依赖并创建更新 PR"
|
||||
/schedule list — 列出所有定时任务
|
||||
/schedule delete <id> — 删除指定任务
|
||||
/triggers create "每天检查依赖更新" --cron "0 9 * * *" --prompt "检查 package.json 中的过期依赖并创建更新 PR"
|
||||
/triggers list — 列出所有定时任务
|
||||
/triggers delete <id> — 删除指定任务
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
769
docs/features/autofix-pr.md
Normal file
769
docs/features/autofix-pr.md
Normal file
@@ -0,0 +1,769 @@
|
||||
# `/autofix-pr` 命令实现规格文档
|
||||
|
||||
> **状态**:规划阶段(2026-04-29),等待评审通过后进入实施。
|
||||
> **Worktree**:`E:\Source_code\Claude-code-bast-autofix-pr`,分支 `feat/autofix-pr`,基于 `origin/main` 4f1649e2。
|
||||
> **架构**:R(Remote-via-CCR),完整版(含 stop 子命令、单例锁、subscribePR、in-process teammate、skills 探测)。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
### 1.1 问题
|
||||
|
||||
本仓库(`Claude-code-bast`)是 Anthropic 官方 `@anthropic-ai/claude-code` 的反编译/重构版本。许多远程能力被 stub 化处理 —— `/autofix-pr` 是其中之一:
|
||||
|
||||
```js
|
||||
// src/commands/autofix-pr/index.js(当前 stub)
|
||||
export default { isEnabled: () => false, isHidden: true, name: 'stub' };
|
||||
```
|
||||
|
||||
三个字段共同导致命令在斜杠菜单中完全不可见、不可调起:
|
||||
|
||||
| 字段 | 值 | 效果 |
|
||||
|---|---|---|
|
||||
| `isEnabled` | `() => false` | 注册时被判定不可用 |
|
||||
| `isHidden` | `true` | 即使被列出也被过滤 |
|
||||
| `name` | `'stub'` | 实际注册名是 `'stub'`,输入 `/autofix-pr` 无法匹配 |
|
||||
|
||||
### 1.2 用户场景
|
||||
|
||||
用户在 fork 仓库(`feat/autonomy-lifecycle-upstream` 分支)尝试对上游 `claude-code-best/claude-code#386` 跑 `/autofix-pr 386`,多次报 `git_repository source setup error`。根因:官方派发的远程 session 落在被 MCP 拒绝访问的仓库(`amdosion/claude-code-bast`),权限/可见性问题。
|
||||
|
||||
### 1.3 目标
|
||||
|
||||
| ID | 需求 | 验收 |
|
||||
|---|---|---|
|
||||
| R1 | 命令在斜杠菜单可见可调起 | 输入 `/au` 出现补全 |
|
||||
| R2 | 跨仓库 PR:从本地 fork 触发对上游 PR 的修复 | `/autofix-pr 386` 不报 repo-not-allowed |
|
||||
| R3 | 远端真正完成修复并 push 回 PR 分支 | PR 出现来自远端的新 commit |
|
||||
| R4 | 不破坏现存其他 stub(如 `share`) | 只动 `autofix-pr` |
|
||||
| R5 | TypeScript 严格模式,`bun run typecheck` 零错误 | CI 绿 |
|
||||
| R6 | bridge 可触发(Remote Control 场景) | `bridgeSafe: true` 生效 |
|
||||
| R7 | 支持 stop/off 子命令 | `/autofix-pr stop` 能终止当前监控 |
|
||||
| R8 | 单例锁防止重复派发 | 已监控 PR 时拒绝新启动并提示 |
|
||||
|
||||
---
|
||||
|
||||
## 二、反编译调研结论(来源:`C:\Users\12180\.local\bin\claude.exe`)
|
||||
|
||||
`claude.exe` 是 242MB 的 Bun 原生编译产物(JS 源码 embed 在二进制内)。通过对该文件的字符串提取(`grep -aoE`)反推出完整调用链。
|
||||
|
||||
### 2.1 主入口函数结构
|
||||
|
||||
```js
|
||||
async function entry(input, q, ctx) {
|
||||
const isStop = input === "stop" || input === "off"
|
||||
const args = { freeformPrompt: input }
|
||||
return main(args, q, ctx)
|
||||
}
|
||||
|
||||
async function main(args, q, { signal, onProgress }) {
|
||||
// args 字段:{ prNumber, target, freeformPrompt, repoPath, skills }
|
||||
d("tengu_autofix_pr_started", {
|
||||
action: "start",
|
||||
has_pr_number: String(args.prNumber !== undefined),
|
||||
has_repo_path: String(args.repoPath !== undefined),
|
||||
})
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 `teleportToRemote` 调用签名(黄金证据)
|
||||
|
||||
```ts
|
||||
const session = await teleportToRemote({
|
||||
initialMessage: C, // 给远端的初始消息
|
||||
source: "autofix_pr", // ⚠️ 新字段,本仓库 teleport.tsx 没有
|
||||
branchName: N, // PR 头分支
|
||||
reuseOutcomeBranch: N, // 与 branchName 同 — 远端 push 回原分支
|
||||
title: `Autofix PR: ${owner}/${repo}#${prNumber} (${branch})`,
|
||||
useDefaultEnvironment: true, // ⚠️ 不用 synthetic env(与 ultrareview 不同)
|
||||
signal,
|
||||
githubPr: { owner, repo, number },
|
||||
cwd: repoPath,
|
||||
onBundleFail: (msg) => { /* ... */ },
|
||||
})
|
||||
```
|
||||
|
||||
**与 `ultrareview` 的关键差异**:
|
||||
|
||||
| 字段 | ultrareview | autofix-pr |
|
||||
|---|---|---|
|
||||
| `environmentId` | `env_011111111111111111111113`(synthetic) | 不传 |
|
||||
| `useDefaultEnvironment` | 不传 | `true` |
|
||||
| `useBundle` | 有(branch mode) | 不传(`skipBundle` 隐含于不传 bundle) |
|
||||
| `reuseOutcomeBranch` | 不传 | 传(远端 push 回原 PR 分支) |
|
||||
| `githubPr` | 不传 | 必传 |
|
||||
| `source` | 不传 | `"autofix_pr"` |
|
||||
| `environmentVariables` | `BUGHUNTER_*` 一堆 | 不传 |
|
||||
|
||||
### 2.3 `registerRemoteAgentTask` 调用
|
||||
|
||||
```ts
|
||||
registerRemoteAgentTask({
|
||||
remoteTaskType: "autofix-pr",
|
||||
session: { id: session.id, title: session.title },
|
||||
command,
|
||||
isLongRunning: true, // poll 不消费 result,靠通知周期驱动
|
||||
})
|
||||
```
|
||||
|
||||
### 2.4 子命令解析
|
||||
|
||||
```
|
||||
/autofix-pr <PR#> → 启动监控 + 派 CCR session
|
||||
/autofix-pr stop → 停止当前监控
|
||||
/autofix-pr off → 同 stop
|
||||
/autofix-pr <freeform-prompt> → 自由 prompt 模式(无 PR 号)
|
||||
/autofix-pr <owner>/<repo>#<n> → 跨仓库(覆盖 R2 验收)
|
||||
```
|
||||
|
||||
### 2.5 状态模型
|
||||
|
||||
- **单例锁**:同一时刻只能监控一个 PR。重复启动报:`already monitoring ${repo}#${prNumber}. Run /autofix-pr stop first.`(error_code: `rc_already_monitoring_other`)
|
||||
- **PR 订阅**:调 `kairos.subscribePR(owner, repo, taskId)` —— 依赖 `KAIROS_GITHUB_WEBHOOKS` feature flag(用户已订阅,可用)
|
||||
- **in-process teammate**:注册后台 agent
|
||||
```ts
|
||||
const teammate = {
|
||||
agentId,
|
||||
agentName: "autofix-pr",
|
||||
teamName: "_autofix",
|
||||
color: undefined,
|
||||
planModeRequired: false,
|
||||
parentSessionId,
|
||||
}
|
||||
```
|
||||
- **Skills 探测**:扫项目里 autofix-related skills(如 `.claude/skills/autofix-*` 或根目录 `AUTOFIX.md`),命中后拼到 prompt:`Run X and Y for custom instructions on how to autofix.`
|
||||
|
||||
### 2.6 Telemetry
|
||||
|
||||
| 事件 | 字段 |
|
||||
|---|---|
|
||||
| `tengu_autofix_pr_started` | `{ action, has_pr_number, has_repo_path }` |
|
||||
| `tengu_autofix_pr_result` | `{ result, error_code? }` |
|
||||
|
||||
`result` 取值:`success_rc` / `failed` / `cancelled`
|
||||
|
||||
`error_code` 取值:
|
||||
|
||||
| code | 含义 |
|
||||
|---|---|
|
||||
| `rc_already_monitoring_other` | 已在监控其他 PR |
|
||||
| `session_create_failed` | teleport 失败 |
|
||||
| `exception` | 未捕获异常 |
|
||||
|
||||
### 2.7 错误返回结构
|
||||
|
||||
```ts
|
||||
function errorResult(message: string, code: string) {
|
||||
d("tengu_autofix_pr_result", { result: "failed", error_code: code })
|
||||
return {
|
||||
kind: "error",
|
||||
message: `Autofix PR failed: ${message}`,
|
||||
code,
|
||||
}
|
||||
}
|
||||
|
||||
function cancelledResult() {
|
||||
d("tengu_autofix_pr_result", { result: "cancelled" })
|
||||
return { kind: "cancelled" }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、本仓库现有基础设施盘点
|
||||
|
||||
下表列出实现 `/autofix-pr` 时**直接复用**的现成能力(已确认完整可用):
|
||||
|
||||
| 能力 | 文件 | 角色 |
|
||||
|---|---|---|
|
||||
| `teleportToRemote` | `src/utils/teleport.tsx:947` | 派 CCR 远端 session(缺 `source` 字段,需补) |
|
||||
| `registerRemoteAgentTask` | `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx:526` | 注册 long-running 任务到 store |
|
||||
| `checkRemoteAgentEligibility` | `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx:185` | 前置鉴权检查 |
|
||||
| `getRemoteTaskSessionUrl` | `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx` | 生成 session 跟踪 URL |
|
||||
| `formatPreconditionError` | `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx` | 错误文案格式化 |
|
||||
| `REMOTE_TASK_TYPES` | `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx:103` | 已含 `'autofix-pr'` 类型 |
|
||||
| `AutofixPrRemoteTaskMetadata` | `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx:112` | `{ owner, repo, prNumber }` schema |
|
||||
| `RemoteSessionProgress` | `src/components/tasks/RemoteSessionProgress.tsx` | 进度面板 UI(已认 autofix-pr 类型) |
|
||||
| `detectCurrentRepositoryWithHost` | `src/utils/detectRepository.ts` | 解析 owner/repo |
|
||||
| `getDefaultBranch` / `gitExe` | `src/utils/git.ts` | git 工具 |
|
||||
| `feature('FLAG')` | `bun:bundle` | feature flag 系统(CLAUDE.md 红线:只能在 if/三元条件位置直接调用) |
|
||||
|
||||
### 模板答案文件
|
||||
|
||||
以下三个文件已确认完整工作,是本次实现的"参考答案":
|
||||
|
||||
- `src/commands/review/reviewRemote.ts`(317 行)—— **主模板**,照抄改造
|
||||
- `src/commands/ultraplan.tsx`(525 行)
|
||||
- `src/commands/review/ultrareviewCommand.tsx`(89 行)
|
||||
|
||||
---
|
||||
|
||||
## 四、命令对象规格
|
||||
|
||||
### 4.1 `Command` 类型选择
|
||||
|
||||
`Command` 类型定义在 `src/types/command.ts`,三态之一:`PromptCommand` / `LocalCommand` / `LocalJSXCommand`。
|
||||
|
||||
**选 `LocalJSXCommand`**,因为:
|
||||
- 需要 spawn 远端 session 并显示进度面板
|
||||
- 兄弟命令 `ultraplan` / `ultrareview` 都用 local-jsx
|
||||
- 接口签名:`call(onDone, context, args) => Promise<React.ReactNode>`
|
||||
|
||||
### 4.2 `index.ts` 完整形状
|
||||
|
||||
```ts
|
||||
import { feature } from 'bun:bundle'
|
||||
import type { Command } from '../../types/command.js'
|
||||
|
||||
const autofixPr: Command = {
|
||||
type: 'local-jsx',
|
||||
name: 'autofix-pr', // 关键:必须是 'autofix-pr' 不是 'stub'
|
||||
description: 'Auto-fix CI failures on a pull request',
|
||||
argumentHint: '<pr-number> | stop | <owner>/<repo>#<n>',
|
||||
isEnabled: () => feature('AUTOFIX_PR'),
|
||||
isHidden: false,
|
||||
bridgeSafe: true,
|
||||
getBridgeInvocationError: (args) => {
|
||||
const trimmed = args.trim()
|
||||
if (!trimmed) return 'PR number required, e.g. /autofix-pr 386'
|
||||
if (trimmed === 'stop' || trimmed === 'off') return undefined
|
||||
if (/^\d+$/.test(trimmed)) return undefined
|
||||
if (/^[\w.-]+\/[\w.-]+#\d+$/.test(trimmed)) return undefined
|
||||
return 'Invalid args. Use /autofix-pr <pr-number> | stop | <owner>/<repo>#<n>'
|
||||
},
|
||||
load: async () => {
|
||||
const m = await import('./launchAutofixPr.js')
|
||||
return { call: m.callAutofixPr }
|
||||
},
|
||||
}
|
||||
|
||||
export default autofixPr
|
||||
```
|
||||
|
||||
### 4.3 参数解析规则
|
||||
|
||||
```
|
||||
^stop$ | ^off$ → { action: 'stop' }
|
||||
^\d+$ → { action: 'start', prNumber, owner: <git>, repo: <git> }
|
||||
^([\w.-]+)/([\w.-]+)#(\d+)$ → { action: 'start', prNumber, owner, repo }
|
||||
其他 → { action: 'start', freeformPrompt: <input> }
|
||||
空字符串 → 错误
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、文件结构
|
||||
|
||||
```
|
||||
src/commands/autofix-pr/
|
||||
├── index.ts # 命令对象(替换 index.js)
|
||||
├── launchAutofixPr.ts # 主流程
|
||||
├── parseArgs.ts # 参数解析(独立便于测试)
|
||||
├── monitorState.ts # 单例锁
|
||||
├── inProcessAgent.ts # 后台 teammate
|
||||
├── skillDetect.ts # 项目 skills 探测
|
||||
└── __tests__/
|
||||
├── parseArgs.test.ts
|
||||
├── monitorState.test.ts
|
||||
├── launchAutofixPr.test.ts
|
||||
└── index.test.ts # bridge invocation error 测试
|
||||
```
|
||||
|
||||
**删除**:原 `index.js`、`index.d.ts`(合并进 `index.ts`)。
|
||||
|
||||
**修改**:
|
||||
- `scripts/defines.ts` —— 加 `AUTOFIX_PR` flag
|
||||
- `scripts/dev.ts` —— dev 默认开启
|
||||
- `src/utils/teleport.tsx` —— `teleportToRemote` 选项加 `source?: string` 字段并透传
|
||||
- `src/commands.ts` —— **不动**(import 路径 `'./commands/autofix-pr/index.js'` 在 ESM/Bun 下会自动解析到 `.ts`)
|
||||
|
||||
---
|
||||
|
||||
## 六、模块详细规格
|
||||
|
||||
### 6.1 `parseArgs.ts`
|
||||
|
||||
```ts
|
||||
export type ParsedArgs =
|
||||
| { action: 'stop' }
|
||||
| { action: 'start'; prNumber: number; owner?: string; repo?: string }
|
||||
| { action: 'freeform'; prompt: string }
|
||||
| { action: 'invalid'; reason: string }
|
||||
|
||||
export function parseAutofixArgs(raw: string): ParsedArgs {
|
||||
const trimmed = raw.trim()
|
||||
if (!trimmed) return { action: 'invalid', reason: 'empty' }
|
||||
if (trimmed === 'stop' || trimmed === 'off') return { action: 'stop' }
|
||||
if (/^\d+$/.test(trimmed)) {
|
||||
return { action: 'start', prNumber: parseInt(trimmed, 10) }
|
||||
}
|
||||
const cross = trimmed.match(/^([\w.-]+)\/([\w.-]+)#(\d+)$/)
|
||||
if (cross) {
|
||||
return {
|
||||
action: 'start',
|
||||
owner: cross[1],
|
||||
repo: cross[2],
|
||||
prNumber: parseInt(cross[3], 10),
|
||||
}
|
||||
}
|
||||
return { action: 'freeform', prompt: trimmed }
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 `monitorState.ts`
|
||||
|
||||
```ts
|
||||
import type { UUID } from 'crypto'
|
||||
|
||||
type MonitorState = {
|
||||
taskId: UUID
|
||||
owner: string
|
||||
repo: string
|
||||
prNumber: number
|
||||
abortController: AbortController
|
||||
startedAt: number
|
||||
}
|
||||
|
||||
let active: MonitorState | null = null
|
||||
|
||||
export function getActiveMonitor(): Readonly<MonitorState> | null {
|
||||
return active
|
||||
}
|
||||
|
||||
export function setActiveMonitor(state: MonitorState): void {
|
||||
if (active) throw new Error(`Monitor already active: ${active.repo}#${active.prNumber}`)
|
||||
active = state
|
||||
}
|
||||
|
||||
export function clearActiveMonitor(): void {
|
||||
if (active) {
|
||||
active.abortController.abort()
|
||||
active = null
|
||||
}
|
||||
}
|
||||
|
||||
export function isMonitoring(owner: string, repo: string, prNumber: number): boolean {
|
||||
return active?.owner === owner && active?.repo === repo && active?.prNumber === prNumber
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 `inProcessAgent.ts`
|
||||
|
||||
仿官方 `xd9` 函数:
|
||||
|
||||
```ts
|
||||
import { randomUUID, type UUID } from 'crypto'
|
||||
import { getCurrentSessionId } from '../../bootstrap/state.js'
|
||||
|
||||
export type AutofixTeammate = {
|
||||
agentId: UUID
|
||||
agentName: 'autofix-pr'
|
||||
teamName: '_autofix'
|
||||
color: undefined
|
||||
planModeRequired: false
|
||||
parentSessionId: UUID
|
||||
abortController: AbortController
|
||||
taskId: UUID
|
||||
}
|
||||
|
||||
export function createAutofixTeammate(
|
||||
initialMessage: string,
|
||||
target: string,
|
||||
): AutofixTeammate {
|
||||
return {
|
||||
agentId: randomUUID(),
|
||||
agentName: 'autofix-pr',
|
||||
teamName: '_autofix',
|
||||
color: undefined,
|
||||
planModeRequired: false,
|
||||
parentSessionId: getCurrentSessionId(),
|
||||
abortController: new AbortController(),
|
||||
taskId: randomUUID(),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.4 `skillDetect.ts`
|
||||
|
||||
```ts
|
||||
import { existsSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
|
||||
export function detectAutofixSkills(cwd: string): string[] {
|
||||
const candidates = [
|
||||
'AUTOFIX.md',
|
||||
'.claude/skills/autofix.md',
|
||||
'.claude/skills/autofix-pr/SKILL.md',
|
||||
]
|
||||
return candidates.filter(rel => existsSync(join(cwd, rel)))
|
||||
}
|
||||
|
||||
export function formatSkillsHint(skills: string[]): string {
|
||||
if (skills.length === 0) return ''
|
||||
return ` Run ${skills.join(' and ')} for custom instructions on how to autofix.`
|
||||
}
|
||||
```
|
||||
|
||||
### 6.5 `launchAutofixPr.ts`
|
||||
|
||||
主流程伪代码(约 250 行):
|
||||
|
||||
```ts
|
||||
import type { LocalJSXCommandCall } from '../../types/command.js'
|
||||
import { parseAutofixArgs } from './parseArgs.js'
|
||||
import { getActiveMonitor, setActiveMonitor, clearActiveMonitor, isMonitoring } from './monitorState.js'
|
||||
import { createAutofixTeammate } from './inProcessAgent.js'
|
||||
import { detectAutofixSkills, formatSkillsHint } from './skillDetect.js'
|
||||
import { teleportToRemote } from '../../utils/teleport.js'
|
||||
import { checkRemoteAgentEligibility, registerRemoteAgentTask, getRemoteTaskSessionUrl } from '../../tasks/RemoteAgentTask/RemoteAgentTask.js'
|
||||
import { detectCurrentRepositoryWithHost } from '../../utils/detectRepository.js'
|
||||
import { logEvent } from '../../services/analytics/index.js'
|
||||
|
||||
export const callAutofixPr: LocalJSXCommandCall = async (onDone, context, args) => {
|
||||
const parsed = parseAutofixArgs(args)
|
||||
|
||||
// 1. stop 子命令
|
||||
if (parsed.action === 'stop') {
|
||||
const m = getActiveMonitor()
|
||||
if (!m) {
|
||||
onDone('No active autofix monitor.', { display: 'system' })
|
||||
return null
|
||||
}
|
||||
clearActiveMonitor()
|
||||
onDone(`Stopped monitoring ${m.repo}#${m.prNumber}.`, { display: 'system' })
|
||||
return null
|
||||
}
|
||||
|
||||
// 2. invalid
|
||||
if (parsed.action === 'invalid') {
|
||||
return errorView(`Invalid args: ${parsed.reason}`)
|
||||
}
|
||||
|
||||
// 3. freeform — 暂不支持,提示用户
|
||||
if (parsed.action === 'freeform') {
|
||||
return errorView('Freeform prompt mode not yet supported. Use /autofix-pr <pr-number>.')
|
||||
}
|
||||
|
||||
// 4. start
|
||||
logEvent('tengu_autofix_pr_started', {
|
||||
action: 'start',
|
||||
has_pr_number: 'true',
|
||||
has_repo_path: String(!!process.cwd()),
|
||||
})
|
||||
|
||||
// 4.1 解析 owner/repo
|
||||
let owner = parsed.owner
|
||||
let repo = parsed.repo
|
||||
if (!owner || !repo) {
|
||||
const detected = await detectCurrentRepositoryWithHost()
|
||||
if (!detected || detected.host !== 'github.com') {
|
||||
return errorResult('Cannot detect GitHub repo from current directory.', 'session_create_failed')
|
||||
}
|
||||
owner = detected.owner
|
||||
repo = detected.name
|
||||
}
|
||||
|
||||
// 4.2 单例锁
|
||||
if (isMonitoring(owner, repo, parsed.prNumber)) {
|
||||
return errorResult(`already monitoring ${repo}#${parsed.prNumber} in background`, 'success_rc')
|
||||
}
|
||||
if (getActiveMonitor()) {
|
||||
const m = getActiveMonitor()!
|
||||
return errorResult(
|
||||
`already monitoring ${m.repo}#${m.prNumber}. Run /autofix-pr stop first.`,
|
||||
'rc_already_monitoring_other',
|
||||
)
|
||||
}
|
||||
|
||||
// 4.3 资格检查
|
||||
const eligibility = await checkRemoteAgentEligibility()
|
||||
if (!eligibility.eligible) {
|
||||
return errorResult('Remote agent not available.', 'session_create_failed')
|
||||
}
|
||||
|
||||
// 4.4 探测 skills
|
||||
const skills = detectAutofixSkills(process.cwd())
|
||||
const skillsHint = formatSkillsHint(skills)
|
||||
|
||||
// 4.5 拼初始消息
|
||||
const target = `${owner}/${repo}#${parsed.prNumber}`
|
||||
const branchName = `refs/pull/${parsed.prNumber}/head`
|
||||
const initialMessage = `Auto-fix failing CI checks on PR #${parsed.prNumber} in ${owner}/${repo}.${skillsHint}`
|
||||
|
||||
// 4.6 创建 in-process teammate
|
||||
const teammate = createAutofixTeammate(initialMessage, target)
|
||||
|
||||
// 4.7 调 teleport
|
||||
let bundleFailMsg: string | undefined
|
||||
const session = await teleportToRemote({
|
||||
initialMessage,
|
||||
source: 'autofix_pr',
|
||||
branchName,
|
||||
reuseOutcomeBranch: branchName,
|
||||
title: `Autofix PR: ${target} (${branchName})`,
|
||||
useDefaultEnvironment: true,
|
||||
signal: teammate.abortController.signal,
|
||||
githubPr: { owner, repo, number: parsed.prNumber },
|
||||
cwd: process.cwd(),
|
||||
onBundleFail: (msg) => { bundleFailMsg = msg },
|
||||
})
|
||||
|
||||
if (!session) {
|
||||
return errorResult(bundleFailMsg ?? 'remote session creation failed.', 'session_create_failed')
|
||||
}
|
||||
|
||||
// 4.8 注册任务到 store
|
||||
registerRemoteAgentTask({
|
||||
remoteTaskType: 'autofix-pr',
|
||||
session,
|
||||
command: `/autofix-pr ${parsed.prNumber}`,
|
||||
context,
|
||||
})
|
||||
|
||||
// 4.9 设置单例锁
|
||||
setActiveMonitor({
|
||||
taskId: teammate.taskId,
|
||||
owner,
|
||||
repo,
|
||||
prNumber: parsed.prNumber,
|
||||
abortController: teammate.abortController,
|
||||
startedAt: Date.now(),
|
||||
})
|
||||
|
||||
// 4.10 PR webhooks 订阅(feature-gated)
|
||||
if (feature('KAIROS_GITHUB_WEBHOOKS')) {
|
||||
await kairosSubscribePR(owner, repo, teammate.taskId).catch(() => {/* non-fatal */})
|
||||
}
|
||||
|
||||
// 4.11 返回 JSX 进度面板
|
||||
const sessionUrl = getRemoteTaskSessionUrl(session.id)
|
||||
logEvent('tengu_autofix_pr_launched', { target })
|
||||
onDone(
|
||||
`Autofix launched for ${target}. Track: ${sessionUrl}`,
|
||||
{ display: 'system' },
|
||||
)
|
||||
return null // 进度面板由 RemoteAgentTask 自动渲染
|
||||
}
|
||||
|
||||
function errorResult(message: string, code: string) {
|
||||
logEvent('tengu_autofix_pr_result', { result: 'failed', error_code: code })
|
||||
// ... 渲染错误 JSX
|
||||
}
|
||||
```
|
||||
|
||||
> **注意**:`feature('KAIROS_GITHUB_WEBHOOKS')` 必须直接放在 if 条件位置,不能赋值给变量(CLAUDE.md 红线)。
|
||||
|
||||
### 6.6 `teleport.tsx` 补 `source` 字段
|
||||
|
||||
```diff
|
||||
export async function teleportToRemote(options: {
|
||||
initialMessage: string | null
|
||||
branchName?: string
|
||||
title?: string
|
||||
description?: string
|
||||
+ /**
|
||||
+ * Identifies which command/flow originated this teleport. CCR backend
|
||||
+ * uses this for routing/billing/observability. Known values: 'autofix_pr',
|
||||
+ * 'ultrareview', 'ultraplan'. Pass-through field — not interpreted client-side.
|
||||
+ */
|
||||
+ source?: string
|
||||
model?: string
|
||||
permissionMode?: PermissionMode
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
并在内部构造 request 时透传到 session_context(具体字段名按现有 review/ultraplan 调用结构对齐)。
|
||||
|
||||
---
|
||||
|
||||
## 七、Feature Flag
|
||||
|
||||
### 7.1 新增 flag
|
||||
|
||||
`scripts/defines.ts` 已有的 flag 集合中加 `AUTOFIX_PR`。
|
||||
|
||||
### 7.2 启用矩阵
|
||||
|
||||
| 环境 | 是否默认开启 | 说明 |
|
||||
|---|---|---|
|
||||
| dev (`bun run dev`) | 是 | `scripts/dev.ts` 加进默认列表 |
|
||||
| build (production `bun run build`) | 否 | 灰度上线,需要 `FEATURE_AUTOFIX_PR=1` 显式开启 |
|
||||
| 测试 | 按需 | 测试文件通过 mock `bun:bundle` 控制 |
|
||||
|
||||
### 7.3 与官方上游同步策略
|
||||
|
||||
如果上游某天恢复官方实现,本仓库的本地实现优先(项目即 fork):
|
||||
1. 保留 `AUTOFIX_PR` flag 名
|
||||
2. 保留 `RemoteTaskType` 字段不动
|
||||
3. 冲突时合并:吸收上游的 `source` 字段值变更、env var 变更,保留我们的本地 launcher 函数
|
||||
|
||||
---
|
||||
|
||||
## 八、测试计划
|
||||
|
||||
### 8.1 测试文件
|
||||
|
||||
| 文件 | 覆盖目标 | 测试用例数 |
|
||||
|---|---|---|
|
||||
| `parseArgs.test.ts` | 参数解析全分支 | ~10 |
|
||||
| `monitorState.test.ts` | 单例锁正确性 | ~6 |
|
||||
| `launchAutofixPr.test.ts` | 主流程 happy path + 失败路径 | ~12 |
|
||||
| `index.test.ts` | bridge invocation error 校验 | ~5 |
|
||||
|
||||
### 8.2 关键断言
|
||||
|
||||
`launchAutofixPr.test.ts`:
|
||||
|
||||
```ts
|
||||
test('start with PR number teleports with correct args', async () => {
|
||||
// mock teleportToRemote, registerRemoteAgentTask, detectCurrentRepositoryWithHost
|
||||
await callAutofixPr(onDone, context, '386')
|
||||
expect(teleportMock).toHaveBeenCalledWith(expect.objectContaining({
|
||||
source: 'autofix_pr',
|
||||
useDefaultEnvironment: true,
|
||||
githubPr: { owner: 'amDosion', repo: 'claude-code-bast', number: 386 },
|
||||
branchName: 'refs/pull/386/head',
|
||||
reuseOutcomeBranch: 'refs/pull/386/head',
|
||||
}))
|
||||
expect(registerMock).toHaveBeenCalledWith(expect.objectContaining({
|
||||
remoteTaskType: 'autofix-pr',
|
||||
}))
|
||||
})
|
||||
|
||||
test('cross-repo syntax owner/repo#n parses correctly', async () => {
|
||||
await callAutofixPr(onDone, context, 'anthropics/claude-code#999')
|
||||
expect(teleportMock).toHaveBeenCalledWith(expect.objectContaining({
|
||||
githubPr: { owner: 'anthropics', repo: 'claude-code', number: 999 },
|
||||
}))
|
||||
})
|
||||
|
||||
test('singleton lock blocks second start', async () => {
|
||||
await callAutofixPr(onDone, context, '386')
|
||||
const result = await callAutofixPr(onDone, context, '999')
|
||||
expect(extractError(result)).toMatch(/already monitoring.*386.*Run \/autofix-pr stop first/)
|
||||
})
|
||||
|
||||
test('stop clears active monitor', async () => {
|
||||
await callAutofixPr(onDone, context, '386')
|
||||
await callAutofixPr(onDone, context, 'stop')
|
||||
expect(getActiveMonitor()).toBeNull()
|
||||
})
|
||||
```
|
||||
|
||||
### 8.3 Mock 策略
|
||||
|
||||
按本仓库 `tests/mocks/` 共享 mock 习惯:
|
||||
- `tests/mocks/log.ts` 和 `tests/mocks/debug.ts` —— 必 mock
|
||||
- `bun:bundle` —— mock `feature` 返回 `true`
|
||||
- `teleportToRemote` —— 模块级 mock,断言入参
|
||||
- `registerRemoteAgentTask` —— 模块级 mock,断言入参
|
||||
- `detectCurrentRepositoryWithHost` —— mock 返回 `{ owner, name, host }`
|
||||
|
||||
### 8.4 类型检查
|
||||
|
||||
```bash
|
||||
bun run typecheck # 必须零错误
|
||||
bun run test:all # 必须全绿
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、实施步骤(11 步清单)
|
||||
|
||||
```
|
||||
[ ] Step 1 scripts/defines.ts + scripts/dev.ts 加 AUTOFIX_PR flag
|
||||
[ ] Step 2 src/utils/teleport.tsx 加 source?: string 字段(约 5 行)
|
||||
[ ] Step 3 删除 src/commands/autofix-pr/{index.js, index.d.ts}
|
||||
新建 src/commands/autofix-pr/index.ts(约 50 行)
|
||||
[ ] Step 4 新建 src/commands/autofix-pr/parseArgs.ts(约 30 行)
|
||||
[ ] Step 5 新建 src/commands/autofix-pr/monitorState.ts(约 40 行)
|
||||
[ ] Step 6 新建 src/commands/autofix-pr/inProcessAgent.ts(约 60 行)
|
||||
[ ] Step 7 新建 src/commands/autofix-pr/skillDetect.ts(约 30 行)
|
||||
[ ] Step 8 新建 src/commands/autofix-pr/launchAutofixPr.ts(约 250 行)
|
||||
照抄 reviewRemote.ts,按 §2.2 差异表改造
|
||||
[ ] Step 9 新建四份测试文件(约 150 行)
|
||||
[ ] Step 10 bun run typecheck && bun run test:all 全绿
|
||||
[ ] Step 11 dev 模式手测:
|
||||
a. /autofix-pr 386 → 期望出现 RemoteSessionProgress 面板
|
||||
b. /autofix-pr stop → 期望提示已停止
|
||||
c. /autofix-pr anthropics/claude-code#999 → 期望跨仓库
|
||||
d. 第二次 /autofix-pr 386 → 期望被单例锁拒绝
|
||||
[ ] Step 12 commit:feat: implement /autofix-pr command (replace stub)
|
||||
```
|
||||
|
||||
预计工作量:约 600 行新增代码(含测试 150 行)。
|
||||
|
||||
---
|
||||
|
||||
## 十、风险与回退
|
||||
|
||||
| 风险 | 触发场景 | 回退策略 |
|
||||
|---|---|---|
|
||||
| `source` 字段 CCR 后端不识别 | 后端只认特定枚举 | 不传该字段,看是否能跑通;如不行回头看官方 cli.js 是否传了别的字段 |
|
||||
| `subscribePR` API 在本仓库 client 不完整 | KAIROS_GITHUB_WEBHOOKS 客户端代码缺失 | 用 `.catch(() => {})` 容忍失败,订阅是 nice-to-have |
|
||||
| 用户账号无 CCR 权限 | `checkRemoteAgentEligibility` 返回 false | 命令降级到错误文案,不破坏会话 |
|
||||
| 远端能起 session 但不修代码 | env vars 命名错误 | 看 `getRemoteTaskSessionUrl` 给的会话页容器日志,调整 |
|
||||
| PR 在 fork 仓库且 CCR 没访问权 | `git_repository source error` | 命令应在前置检查中识别并提示用户先把 PR 转到主仓 |
|
||||
| 上游恢复官方实现导致冲突 | 上游 sync 时 | 项目是 fork,本地实现优先;冲突手工 merge |
|
||||
|
||||
### 回退命令
|
||||
|
||||
```bash
|
||||
# 完全撤回本次实现
|
||||
git checkout main
|
||||
git worktree remove E:/Source_code/Claude-code-bast-autofix-pr
|
||||
git branch -D feat/autofix-pr
|
||||
```
|
||||
|
||||
`AUTOFIX_PR` flag 默认在 production 关闭,所以即使代码已合入 main,没显式 `FEATURE_AUTOFIX_PR=1` 时不会影响用户。
|
||||
|
||||
---
|
||||
|
||||
## 十一、验收清单
|
||||
|
||||
实施完成后逐项核对:
|
||||
|
||||
- [ ] R1:dev 模式下输入 `/au` 出现 `/autofix-pr` 补全
|
||||
- [ ] R2:`/autofix-pr anthropics/claude-code#999` 不报 repo-not-allowed
|
||||
- [ ] R3:远端 session 跑完后目标 PR 出现新 commit
|
||||
- [ ] R4:其他 stub(`share` 等)依然 hidden
|
||||
- [ ] R5:`bun run typecheck` 零错误
|
||||
- [ ] R6:通过 RC bridge 触发 `/autofix-pr 386` 能跑通
|
||||
- [ ] R7:`/autofix-pr stop` 终止当前监控
|
||||
- [ ] R8:第二次 `/autofix-pr` 不同 PR 时被锁拒绝并提示
|
||||
|
||||
---
|
||||
|
||||
## 十二、附录
|
||||
|
||||
### 附录 A:相关文件路径速查
|
||||
|
||||
| 路径 | 角色 |
|
||||
|---|---|
|
||||
| `E:\Source_code\Claude-code-bast-autofix-pr` | 实施 worktree |
|
||||
| `C:\Users\12180\.local\bin\claude.exe` | 反编译来源(242MB Bun 编译产物) |
|
||||
| `C:\Users\12180\.claude\projects\E--Source-code-Claude-code-bast\memory\project_autofix_pr_implementation.md` | 内存备忘(精简版) |
|
||||
| `src/commands/review/reviewRemote.ts` | 主模板 |
|
||||
| `src/utils/teleport.tsx:947` | `teleportToRemote` 入口 |
|
||||
| `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx:103` | `REMOTE_TASK_TYPES` |
|
||||
| `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx:526` | `registerRemoteAgentTask` |
|
||||
| `src/types/command.ts` | `Command` 类型定义 |
|
||||
|
||||
### 附录 B:未决问题
|
||||
|
||||
| # | 问题 | 当前处理 | 后续 |
|
||||
|---|---|---|---|
|
||||
| Q1 | `source` 字段在 CCR backend 是否被解析 | 暂传 `'autofix_pr'`,按官方做法 | 端到端测试时观察远端日志 |
|
||||
| Q2 | `subscribePR` 的 client SDK 在本仓库是否完整 | `try/catch` 容忍失败 | Step 11 手测时单独验证 |
|
||||
| Q3 | freeform prompt 模式是否实现 | 暂报"not supported" | 第二期再加 |
|
||||
|
||||
---
|
||||
|
||||
## 十三、变更日志
|
||||
|
||||
| 日期 | 作者 | 变更 |
|
||||
|---|---|---|
|
||||
| 2026-04-29 | Claude Opus 4.7 | 初始规格文档创建(基于 claude.exe 反编译 + 仓库现有基础设施盘点) |
|
||||
262
docs/testing/SLASH-COMMANDS-TEST-CHECKLIST.md
Normal file
262
docs/testing/SLASH-COMMANDS-TEST-CHECKLIST.md
Normal file
@@ -0,0 +1,262 @@
|
||||
# 斜杠命令完整测试清单
|
||||
|
||||
**日期**:2026-05-06
|
||||
**适用范围**:本 session 累积所有恢复/新建命令(PR-1 ~ PR-4 + audit-fix + H2 refactor)
|
||||
**起点 commit**:`origin/main` (4f1649e2)
|
||||
**最新 commit**:`fe99cf0e`(35+ commits ahead)
|
||||
|
||||
---
|
||||
|
||||
## 测试前准备
|
||||
|
||||
```bash
|
||||
cd E:/Source_code/Claude-code-bast-autofix-pr
|
||||
|
||||
# 1. 确保最新 dist 含全部 commits
|
||||
bun run build
|
||||
|
||||
# 2. 验证 dist 不是 stale
|
||||
stat -c '%Y %n' dist/cli.js
|
||||
git log -1 --format=%ct\ %h
|
||||
# dist mtime 必须 ≥ HEAD commit time
|
||||
|
||||
# 3. 完全退出当前 dev REPL(按 Ctrl+D 或 /quit)后重启
|
||||
bun run dev
|
||||
```
|
||||
|
||||
**关键提醒**:Bun 不会动态重载 dist,任何 source 改动都必须 `bun run build` + 重启 REPL。
|
||||
|
||||
---
|
||||
|
||||
## A 组 — 纯本地(无网络/无 key,立即可测)
|
||||
|
||||
**前置**:无
|
||||
|
||||
| # | 命令 | 输入 | 期望输出 | 通过 |
|
||||
|---|---|---|---|---|
|
||||
| A1 | `/version` | 直接跑 | 显示版本号(如 `1.10.10`) | ☐ |
|
||||
| A2 | `/env` | 直接跑 | runtime 信息 + env vars 白名单(CLAUDE_/FEATURE_/ANTHROPIC_/BUN_/NODE_/...)+ secrets masked | ☐ |
|
||||
| A3 | `/context` | 直接跑 | fork 原生命令:colored grid(走 `analyzeContextUsage()` 真实 API view,含 compact boundary + projectView 转换)+ token 数与 API 看到的一致 | ☐ |
|
||||
| A4 | `/context` 在压缩边界附近 | 直接跑 | 显示 compact boundary 后的 messages,不重复计 token | ☐ |
|
||||
| A5 | _(删 ctx_viz;`/context` 是唯一 context 可视化命令)_ | — | — | — |
|
||||
| A6 | `/debug-tool-call` | 默认 N=5 | 列最近 5 个 tool_use+tool_result 配对 | ☐ |
|
||||
| A7 | `/debug-tool-call 10` | 数字参数 | 列最近 10 个 | ☐ |
|
||||
| A8 | `/perf-issue` | 直接跑 | 写 `~/.claude/perf-reports/perf-<stamp>.md`(mem+cpu+token+per-tool) | ☐ |
|
||||
| A9 | `/perf-issue --format=json` | flag | 写 .json 格式 | ☐ |
|
||||
| A10 | `/perf-issue --limit 1000` | flag | 仅读 log 最后 1000 行 | ☐ |
|
||||
| A11 | `/break-cache` | 默认 once | 写 `~/.claude/.next-request-no-cache` marker | ☐ |
|
||||
| A12 | `/break-cache status` | 子命令 | 显示 marker 状态 + 累计 break 次数 | ☐ |
|
||||
| A13 | `/break-cache always` | 子命令 | 写 always flag 文件 | ☐ |
|
||||
| A14 | `/break-cache off` | 子命令 | 删 once + always | ☐ |
|
||||
| A15 | `/tui` | toggle | 切换 marker `~/.claude/.tui-mode` | ☐ |
|
||||
| A16 | `/tui status` | 子命令 | 显示当前 marker + env var 状态 | ☐ |
|
||||
| A17 | `/tui on` `/tui off` | 子命令 | marker write/unlink | ☐ |
|
||||
| A18 | `/onboarding status` | 子命令 | 显示 hasCompletedOnboarding / theme / lastVersion | ☐ |
|
||||
| A19 | `/onboarding theme` | 子命令 | 进入 ThemePicker | ☐ |
|
||||
| A20 | `/onboarding trust` | 子命令 | 清 trust dialog flag | ☐ |
|
||||
| A21 | `/onboarding reset` | 子命令 | 清 hasCompletedOnboarding,下次启动重跑 | ☐ |
|
||||
| A22 | `/recap` | 直接跑 | 一行 ≤40 字 session recap | ☐ |
|
||||
| A23 | `/away` `/catchup` | aliases of recap | 同 A22 | ☐ |
|
||||
| A24 | `/usage` | 直接跑 | 合并 cost + stats(Settings/Usage 或 Stats panel) | ☐ |
|
||||
| A25 | `/cost` `/stats` | aliases of usage | 同 A24 | ☐ |
|
||||
| A26 | `/summary` | 直接跑 | 调 manuallyExtractSessionMemory + 显示 summary.md | ☐ |
|
||||
|
||||
**A 组失败诊断**:
|
||||
- 命令找不到 → 检查 dist staleness + 重启 REPL
|
||||
- `feature() unsupported` → `bun run build` 时 feature flag 没注入
|
||||
|
||||
---
|
||||
|
||||
## B 组 — GitHub CLI(需 `gh auth login`)
|
||||
|
||||
**前置**:`gh auth status` 显示 logged-in;fork 仓库要有 issues enabled
|
||||
|
||||
| # | 命令 | 输入 | 期望输出 | 通过 |
|
||||
|---|---|---|---|---|
|
||||
| B1 | `/share` | 默认 secret gist | 调 `gh gist create`,输出 gist URL | ☐ |
|
||||
| B2 | `/share --public` | flag | public gist | ☐ |
|
||||
| B3 | `/share --mask-secrets` | flag | redact `sk-ant-*` `Bearer *` `ghp_*` 等模式 | ☐ |
|
||||
| B4 | `/share --summary-only` | flag | 仅前 200 字/turn | ☐ |
|
||||
| B5 | `/share --allow-public-fallback` | flag | gh 失败 → 0x0.st fallback | ☐ |
|
||||
| B6 | `/issue Fix login bug` | title 参数 | 调 `gh issue create`,rich body 含最近 5 turns + errors | ☐ |
|
||||
| B7 | `/issue --label bug --assignee me <title>` | 多 flag | label + assignee 生效 | ☐ |
|
||||
| B8 | `/issue` (仓库 issues disabled)| — | 自动降级到 GitHub Discussions | ☐ |
|
||||
| B9 | `/commit` | 直接跑(有 staged) | 生成 commit message 草稿 | ☐ |
|
||||
| B10 | `/commit-push-pr` | 直接跑 | commit + push + 创建 PR | ☐ |
|
||||
|
||||
**B 组失败诊断**:
|
||||
- `gh: command not found` → 装 https://cli.github.com/
|
||||
- `gh auth status` 未登录 → `gh auth login`
|
||||
- issues disabled → 看是否降级到 discussion
|
||||
|
||||
---
|
||||
|
||||
## C 组 — Subscription OAuth(已 `/login` claude.ai)
|
||||
|
||||
**前置**:`/login` 完成 claude.ai OAuth;`/login` 显示 `☑ Subscription`
|
||||
|
||||
| # | 命令 | 输入 | 期望输出 | 通过 |
|
||||
|---|---|---|---|---|
|
||||
| C1 | `/login` | 无参 | **3 plane summary**:☑ Subscription、☐/☑ Workspace API key、4 third-party providers(PR-4 新增) | ☐ |
|
||||
| C2 | `/teleport` | 无参 | 列最近 sessions(list-style picker) | ☐ |
|
||||
| C3 | `/teleport <session-uuid>` | 参数 | resume from claude.ai | ☐ |
|
||||
| C4 | `/tp <session-uuid>` | alias | 同 C3 | ☐ |
|
||||
| C5 | `/teleport <session-uuid> --print` | flag | print mode 直接输出 session URL | ☐ |
|
||||
| C6 | `/autofix-pr 386` | PR# | CCR 派发,输出 sessionUrl | ☐ |
|
||||
| C7 | `/autofix-pr stop` | 子命令 | 停止 active monitor | ☐ |
|
||||
| C8 | `/autofix-pr anthropics/claude-code#999` | cwd 不匹配 | 拒绝 `repo_mismatch`(不真创建会话) | ☐ |
|
||||
| C9 | `/schedule list` | 子命令 | `/v1/code/triggers` GET,返回 `data:[]` 或 trigger 列表 | ☐ |
|
||||
| C10 | `/schedule create <cron> <prompt>` | 子命令 | POST,cron expr UTC 验证 | ☐ |
|
||||
| C11 | `/schedule run <id>` | 子命令 | POST /run 立即触发 | ☐ |
|
||||
| C12 | `/schedule update <id> <field> <value>` | 子命令 | **POST**(不是 PATCH) | ☐ |
|
||||
| C13 | `/cron list` `/triggers list` | aliases | 同 C9 | ☐ |
|
||||
| C14 | `/init-verifiers` | 无参 | 创建项目 verifier skills | ☐ |
|
||||
| C15 | `/bridge-kick` | 无参 | bridge 故障注入测试 | ☐ |
|
||||
| C16 | `/subscribe-pr` | 无参 | 列本地 `~/.claude/pr-subscriptions.json` | ☐ |
|
||||
| C17 | `/ultrareview <PR#>` | 参数 | preflight gate(v1 已有) | ☐ |
|
||||
|
||||
**C 组失败诊断**:
|
||||
- 401 → 重 `/login`
|
||||
- `/v1/agents` 类 401 → 这些是 workspace endpoint,**预期会失败**,移到 F 组
|
||||
- `/schedule` 401 → 检查 dist 含 `ccr-triggers-2026-01-30` beta header
|
||||
|
||||
---
|
||||
|
||||
## D 组 — _(已删除 2026-05-06)_
|
||||
|
||||
`/providers` 命令在 2026-05-06 移除。理由:与 fork 原生 `/login` 的 "Anthropic Compatible Setup" form 功能重叠(同样配 OpenAI-compat Base URL + API Key),保留单一入口避免双 UI 混淆。
|
||||
|
||||
**第三方 provider 配置请用** `/login` 内的 form:选 provider 后填 Base URL + API Key + Haiku/Sonnet/Opus 类别按钮。
|
||||
|
||||
`src/services/providerRegistry/*` utility 模块 **保留**(4 内置 cerebras/groq/qwen/deepseek 元数据 + DeepSeek 三模式 compatMatrix),可被未来 fork form 的 "Quick Select" enhancement 复用。
|
||||
|
||||
---
|
||||
|
||||
|
||||
## E 组 — 本地兜底(PR-3 新增,订阅用户无 key 也能用)
|
||||
|
||||
**前置**:无
|
||||
|
||||
### E.1 `/local-vault`(OS keychain + AES fallback)
|
||||
|
||||
| # | 命令 | 输入 | 期望输出 | 通过 |
|
||||
|---|---|---|---|---|
|
||||
| E1 | `/local-vault list` | 无参 | 空列表(首次) | ☐ |
|
||||
| E2 | `/local-vault set test-key foo-secret-value` | 写 secret | onDone 显示 `[REDACTED]`,**不**显示原值 | ☐ |
|
||||
| E3 | `/local-vault list` | 再跑 | 显示 `test-key`(不含 value) | ☐ |
|
||||
| E4 | `/local-vault get test-key` | 默认 mask | `foo-...e (16 chars)` 类似格式 | ☐ |
|
||||
| E5 | `/local-vault get test-key --reveal` | 明文 + 警告 | `foo-secret-value` + 警告 "secret revealed in terminal" | ☐ |
|
||||
| E6 | `/local-vault set bad-key C:hack` | path traversal | 拒绝(CRITICAL E1 修复) | ☐ |
|
||||
| E7 | `/local-vault set ../traverse foo` | path traversal | 拒绝 | ☐ |
|
||||
| E8 | `/local-vault delete test-key` | 删 | OK | ☐ |
|
||||
| E9 | `/lv list` | alias | 同 E1 | ☐ |
|
||||
|
||||
**安全验证**:
|
||||
```bash
|
||||
# E1 加密文件存在 + value 不明文
|
||||
ls ~/.claude/local-vault.enc.json
|
||||
cat ~/.claude/local-vault.enc.json | grep -c "foo-secret-value" # 必须是 0
|
||||
# salt 16 字节存在
|
||||
cat ~/.claude/local-vault.enc.json | grep "_salt"
|
||||
```
|
||||
|
||||
### E.2 `/local-memory`(多 store 持久化)
|
||||
|
||||
| # | 命令 | 输入 | 期望输出 | 通过 |
|
||||
|---|---|---|---|---|
|
||||
| E10 | `/local-memory list` | 无参 | 空 | ☐ |
|
||||
| E11 | `/local-memory create my-store` | 创建 | `~/.claude/local-memory/my-store/` 建好 | ☐ |
|
||||
| E12 | `/local-memory store my-store key1 value1` | 写 entry | OK | ☐ |
|
||||
| E13 | `/local-memory fetch my-store key1` | 读 | `value1` | ☐ |
|
||||
| E14 | `/local-memory entries my-store` | 列 | `[key1]` | ☐ |
|
||||
| E15 | `/local-memory store my-store ../escape foo` | path traversal | 拒绝 | ☐ |
|
||||
| E16 | `/local-memory archive my-store` | 改名 | dir 改为 `my-store.archived` | ☐ |
|
||||
| E17 | `/lm list` | alias | 同 E10 | ☐ |
|
||||
|
||||
**E 组失败诊断**:
|
||||
- AES 错 passphrase → 提示重新 setSecret
|
||||
- keychain 不可用 → 自动 fallback 文件(warn 一次)
|
||||
- path traversal 接受 → audit-fix-all-40 修复未生效,重新 build
|
||||
|
||||
---
|
||||
|
||||
## F 组 — Workspace API key(需配 `ANTHROPIC_API_KEY=sk-ant-api03-*`)
|
||||
|
||||
**前置**:
|
||||
1. 从 https://console.anthropic.com/settings/keys 创建 API key(`sk-ant-api03-*`)
|
||||
2. Windows: `setx ANTHROPIC_API_KEY "sk-ant-api03-..."` 持久化
|
||||
3. **完全退出 dev REPL**(Ctrl+D / `/quit`) + 启动新 shell(让 setx 生效)+ `bun run dev`
|
||||
4. 验证:`/login` 应显示 `☑ Workspace API key ANTHROPIC_API_KEY set`
|
||||
|
||||
| # | 命令 | 输入 | 期望输出 | 通过 |
|
||||
|---|---|---|---|---|
|
||||
| F1 | `/help`(配 key 后) | — | 4 命令 `/agents-platform` `/vault` `/memory-stores` `/skill-store` 出现(之前 isHidden:true) | ☐ |
|
||||
| F2 | `/help`(不配 key) | — | 4 命令**不**出现(动态 isHidden) | ☐ |
|
||||
| F3 | `/agents-platform list` | 无参 | `/v1/agents` GET 200,返回 agents 数组 | ☐ |
|
||||
| F4 | `/vault list` | 无参 | `/v1/vaults` GET 200 | ☐ |
|
||||
| F5 | `/vault create test-vault` | 子命令 | 创建 vault | ☐ |
|
||||
| F6 | `/vault add-credential <vault_id> api-key sk-secret` | 子命令 | onDone 显示 `[REDACTED]`,stdout grep 不到 `sk-secret` | ☐ |
|
||||
| F7 | `/memory-stores list` | 无参 | `/v1/memory_stores` GET,beta `managed-agents-2026-04-01` | ☐ |
|
||||
| F8 | `/memory-stores create test-store` | 子命令 | POST | ☐ |
|
||||
| F9 | `/memory-stores update-memory <id> <mid> "new"` | 子命令 | **PATCH**(不是 POST) | ☐ |
|
||||
| F10 | `/skill-store list` | 无参 | `/v1/skills?beta=true` GET | ☐ |
|
||||
| F11 | `/skill-store install <id>` | 子命令 | 写 `~/.claude/skills/<name>/SKILL.md` | ☐ |
|
||||
| F12 | 错配(API key 不是 `sk-ant-api03-*` 前缀) | 配错 key | 友好错(不 401) | ☐ |
|
||||
| F13 | 不配 key 时调 `/vault list`(手动 `/help` 找不到,但直接输入命令名) | — | 501 + 文案 "ANTHROPIC_API_KEY required" | ☐ |
|
||||
|
||||
**F 组失败诊断**:
|
||||
- 401 with workspace key → key 没生效(重启 REPL + 检查 `echo $ANTHROPIC_API_KEY`)
|
||||
- 命令仍 isHidden → dist staleness(rebuild + 重启)
|
||||
- credential value 出现在 stdout → audit fix 未生效
|
||||
|
||||
---
|
||||
|
||||
## 全过验收标准
|
||||
|
||||
- [ ] A 组 26/26 pass
|
||||
- [ ] B 组 ≥8/10 pass(有 gh + 仓库权限的)
|
||||
- [ ] C 组 ≥10/17 pass(订阅环境完整)
|
||||
- [ ] D 组 8/8 pass
|
||||
- [ ] E 组 17/17 pass(path traversal 必须拒绝)
|
||||
- [ ] F 组 ≥10/13 pass(取决于 workspace key 是否配)
|
||||
|
||||
任何 fail 立即报告:命令 + 实际输出 + 期望输出。我针对 fail 立即修。
|
||||
|
||||
---
|
||||
|
||||
## 已知限制
|
||||
|
||||
| 命令 | 限制 |
|
||||
|---|---|
|
||||
| `/teleport` 无参 picker | 用 list-style 不是 Ink `<SelectInput>`(LocalJSXCommandCall 不能 mid-call suspend) |
|
||||
| `/autofix-pr` cross-repo | 仅元数据,git source 仍来自 cwd(`repo_mismatch` 显式拒绝跨 cwd) |
|
||||
| `/skill-store install` | 写到 `~/.claude/skills/`,fork 主流程不自动 load 该目录的 markdown skills(用户手动用) |
|
||||
| `/providers use <id>` | 输出 shell export 命令,**不**自动 mutate runtime(重启生效) |
|
||||
|
||||
---
|
||||
|
||||
## 测试报告模板
|
||||
|
||||
```markdown
|
||||
## 测试报告 - 2026-05-XX
|
||||
|
||||
### 环境
|
||||
- OS: Windows 11
|
||||
- Bun: <version>
|
||||
- dist mtime: <date>
|
||||
- HEAD: <commit-hash>
|
||||
- ANTHROPIC_API_KEY: 配/未配
|
||||
- gh CLI: 装/未装
|
||||
|
||||
### 结果
|
||||
- A: 26/26 ✅
|
||||
- B: 8/10(B5/B8 fail)
|
||||
- C: 12/17(C5/C13/C14/C15/C16 fail)
|
||||
- D: 8/8 ✅
|
||||
- E: 17/17 ✅
|
||||
- F: 12/13(F12 边界)
|
||||
|
||||
### 失败详情
|
||||
B5: <command> → 实际 <output>,期望 <expected>
|
||||
...
|
||||
```
|
||||
Reference in New Issue
Block a user