feat: integrate fork work onto upstream main (squashed)

Squash-merge of feat/autofix-pr-test (69 commits) onto upstream/main
with -X ours strategy (upstream as authoritative for content conflicts).

Key features brought in from fork:
- LocalMemoryRecall + VaultHttpFetch tools (end-to-end wired)
- /local-memory, /local-vault, /memory-stores, /skill-store interactive panels
- /agents-platform, /schedule, /vault command scaffolding
- /login: switch / replace / remove of workspace API key
- statusline refactor (built-in status row, /statusline as info command)
- autofix-pr command + workflow

Conflict resolutions (upstream-wins):
- 10 .js command stubs kept from upstream (alongside fork's .ts implementations)
- src/components/BuiltinStatusLine.tsx accepted upstream's deletion
  (fork's wire-up references in StatusLine.tsx will be cleaned up next)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
unraid
2026-05-08 16:47:29 +08:00
parent 73e54d4bbc
commit 8945f08708
233 changed files with 40597 additions and 341 deletions

769
docs/features/autofix-pr.md Normal file
View 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。
> **架构**RRemote-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 commitfeat: 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` 时不会影响用户。
---
## 十一、验收清单
实施完成后逐项核对:
- [ ] R1dev 模式下输入 `/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 反编译 + 仓库现有基础设施盘点) |