把飞书缺陷逐条拆到独立分支修复并留人工验收
29.6/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/11/2 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add PetterWong-L/dsh-feishu-bugs该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:仅索引
是什么
dsh 原生插件 · platform
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 6 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 198 处,展示前 103 条)
- 读取本地文件index.js:18
const VERSION=JSON.parse(readFileSync(new URL('./package.json',import.meta.url),'utf8')).version; - 删除本地文件递归index.js:113
await rm(buildDir(b),{recursive:true,force:true}).catch(()=>{}); - 读取本地文件index.js:133
const present=await stat(worktree).then(()=>true).catch(()=>false); - 删除本地文件递归index.js:139
try{await rm(buildDir(b),{recursive:true,force:true});}catch{} - 读取本地文件index.js:215
const root=await realpath(join(dir,'worktrees')).catch(()=>join(dir,'worktrees')); - 执行外部命令固定命令index.js:376
await new Promise((resolve,reject)=>execFile('open',['-a','Xcode',workspace],err=>err?reject(Error('Could not open Xcode')):resolve())); - 发起网络请求client.js:112
try{const r=await fetch(endpoint(),{credentials:'same-origin',cache:'no-store'});if(!r.ok)throw Error();const d=await r.json();if(alive.current&&seque - 发起网络请求client.js:120
try{const r=await fetch(endpoint(),{method:'POST',credentials:'same-origin',cache:'no-store',headers:{'Content-Type':'application/json','X-DSH-Feishu- - 写入本地文件store.js:19
await mkdir(dir,{recursive:true,mode:0o700});await chmod(dir,0o700); - 写入本地文件store.js:19
await mkdir(dir,{recursive:true,mode:0o700});await chmod(dir,0o700); - 读取本地文件store.js:21
try{state=JSON.parse(await readFile(file,'utf8'));}catch(e){if(e.code!=='ENOENT')throw e;state={version:1,bugs:[],audit:[],selection:{}};} - 写入本地文件store.js:23
let tail=Promise.resolve();const save=()=>{const text=JSON.stringify(state,null,2);const next=tail.then(async()=>{await writeFile(file+'.tmp',text,{mo - 写入本地文件store.js:23
let tail=Promise.resolve();const save=()=>{const text=JSON.stringify(state,null,2);const next=tail.then(async()=>{await writeFile(file+'.tmp',text,{mo - 读取本地文件git.js:9
const cwd=await realpath(b.cwd),repo=await realpath(b.repo); - 读取本地文件git.js:22
if(await realpath(a)!==await realpath(c))throw Error('Worktree belongs to another repository'); - 读取本地文件git.js:35
repo=await realpath(repo); - 写入本地文件git.js:42
await mkdir(runs,{recursive:true,mode:0o700}); - 读取本地文件git.js:52
repo=await realpath(repo); - 读取本地文件git.js:68
const root=await realpath(repo); - 读取本地文件git.js:86
const root=await realpath(repo); - 删除本地文件递归git.js:96
if(!gitRemoved)await rm(target,{recursive:true,force:true}); - 写入本地文件git.js:120
await mkdir(join(common,'info'),{recursive:true}); - 写入本地文件git.js:121
await appendFile(file,`${current&&!current.endsWith('\n')?'\n':''}${rel}\n`,'utf8'); - 写入本地文件git.js:174
await mkdir(to,{recursive:true}); - 写入本地文件git.js:179
else if(entry.isFile())await copyFile(src,dst); - 写入本地文件meegle.js:160
await mkdir(dir,{recursive:true}); - 读取本地文件meegle.js:169
const info=await stat(target); - 删除本地文件meegle.js:170
if(info.size>MAX_ATTACHMENT_BYTES){await rm(target,{force:true});skipped.push({name:e.name,reason:'attachment too large'});continue;} - 读取本地文件build.js:10
const entries=await readdir(cwd,{withFileTypes:true}).catch(()=>[]); - 执行外部命令固定命令build.js:43
execFile('xcodebuild',['-list','-workspace',workspace],{cwd,timeout:180000,maxBuffer:8*1024*1024},(err,stdout,stderr)=>{ - 执行外部命令固定命令build.js:56
execFile('xcodebuild',args,{cwd,timeout:timeoutMs,killSignal:'SIGKILL',maxBuffer:64*1024*1024},(err,stdout,stderr)=>{ - 读取本地文件build.js:61
const apps=await readdir(products).catch(()=>[]); - 写入本地文件build.test.js:17
await mkdir(join(root,'SevMinWorkout.xcworkspace')); - 写入本地文件build.test.js:18
await mkdir(join(root,'Pods.xcworkspace')); - 写入本地文件build.test.js:20
const empty=join(root,'empty');await mkdir(empty); - 删除本地文件递归build.test.js:22
}finally{await rm(root,{recursive:true,force:true});}}); - 写入本地文件build.test.js:29
await mkdir(bin,{recursive:true});await mkdir(cwd,{recursive:true}); - 写入本地文件build.test.js:30
await writeFile(join(bin,'xcodebuild'),`#!/bin/sh - 删除本地文件递归build.test.js:50
}finally{await rm(root,{recursive:true,force:true});}}); - 读取本地文件client-state.test.js:7
const source=await readFile(new URL('./client.js',import.meta.url),'utf8'); - 删除本地文件递归completion-host.test.js:13
t.after(async()=>{disposers.reverse().forEach(fn=>fn());await rm(dir,{recursive:true,force:true});}); - 写入本地文件completion-host.test.js:16
await writeFile(join(dir,'state.json'),JSON.stringify({version:1,selection:{},audit:[],bugs:[ - 读取本地文件completion-host.test.js:33
const saved=JSON.parse(await readFile(join(dir,'state.json'),'utf8'));assert.equal(saved.bugs[0].completedAt,completed.completedAt);assert.equal(saved - 执行外部命令固定命令git.test.js:2
test('in-repo fix branch refuses a dirty tree, then commits only the fix',async()=>{const root=await mkdtemp(join(tmpdir(),'feishu-git-inrepo-'));cons - 执行外部命令固定命令git.test.js:3
execFileSync('git',['init','-b','develop',repo]);git('config','user.email','t@e.test');git('config','user.name','T'); - 写入本地文件git.test.js:4
await writeFile(join(repo,'a'),'a');await mkdir(join(repo,'Pods'),{recursive:true});await writeFile(join(repo,'Pods','keep'),'x'); - 写入本地文件git.test.js:4
await writeFile(join(repo,'a'),'a');await mkdir(join(repo,'Pods'),{recursive:true});await writeFile(join(repo,'Pods','keep'),'x'); - 写入本地文件git.test.js:5
await writeFile(join(repo,'.gitignore'),'Pods/\n');git('add','.');git('commit','-m','init'); - 写入本地文件git.test.js:7
await writeFile(join(repo,'dirty'),'x'); - 删除本地文件git.test.js:9
await rm(join(repo,'dirty')); - 读取本地文件git.test.js:11
assert.equal(run.cwd,await realpath(repo));assert.equal(run.inRepo,true);assert.equal(git('branch','--show-current'),run.branch);assert.equal(git('rev - 写入本地文件git.test.js:13
await writeFile(join(repo,'fix.txt'),'fixed'); - 删除本地文件递归git.test.js:17
}finally{await rm(root,{recursive:true,force:true});}}); - 执行外部命令固定命令git.test.js:19
test('an unclean repository keeps your work and isolates the fix in a worktree',async()=>{const root=await mkdtemp(join(tmpdir(),'feishu-git-auto-')); - 执行外部命令固定命令git.test.js:20
execFileSync('git',['init','-b','develop',repo]);git('config','user.email','t@e.test');git('config','user.name','T'); - 写入本地文件git.test.js:21
await writeFile(join(repo,'a'),'a');await writeFile(join(repo,'.gitignore'),'Pods/\n'); - 读取本地文件git.test.js:28
assert.equal(run.inRepo,false);assert.equal(run.isolated,true);assert.notEqual(run.cwd,await realpath(repo)); - 读取本地文件git.test.js:30
assert.equal((await readFile(join(run.cwd,'wip.txt'),'utf8').catch(()=>null)),null); - 读取本地文件git.test.js:31
assert.equal((await readFile(join(run.cwd,'SevMinWorkout.xcworkspace','contents.xcworkspacedata'),'utf8')),'<Workspace/>'); - 读取本地文件git.test.js:32
assert.equal((await lstat(join(run.cwd,'Pods'))).isSymbolicLink(),true); - 执行外部命令固定命令git.test.js:33
assert.equal(execFileSync('git',['branch','--show-current'],{cwd:run.cwd,encoding:'utf8'}).trim(),run.branch); - 读取本地文件git.test.js:35
assert.equal(await readFile(join(repo,'wip.txt'),'utf8'),'my work in progress, edited'); - 执行外部命令固定命令git.test.js:36
const clean=join(root,'clean');execFileSync('git',['clone','-q',repo,clean]);execFileSync('git',['checkout','-q','develop'],{cwd:clean}); - 删除本地文件递归git.test.js:39
}finally{await rm(root,{recursive:true,force:true});}}); - 删除本地文件递归git.test.js:41
test('separate worktree leaves develop and working directory unchanged',async()=>{const root=await mkdtemp(join(tmpdir(),'feishu-git-test-'));const re - 删除本地文件git.test.js:59
await rm(join(repo,'dirty.txt')); - 删除本地文件递归git.test.js:61
}finally{await rm(root,{recursive:true,force:true});}}); - 删除本地文件递归host.test.js:10
finally{release?.(false);disposers.reverse().forEach(f=>f());await rm(dir,{recursive:true,force:true});} - 写入本地文件host.test.js:16
await writeFile(join(dir,'state.json'),JSON.stringify({version:1,selection:{},bugs:[{id:'p:bug:1',title:'Crash',status:'running',sessionId:'s1',cwd:'/ - 读取本地文件host.test.js:24
assert.equal(JSON.parse(await readFile(join(dir,'state.json'),'utf8')).bugs[1].status,'queued'); - 删除本地文件递归host.test.js:25
}finally{disposers.reverse().forEach(f=>f());await rm(dir,{recursive:true,force:true});} - 写入本地文件host.test.js:30
await writeFile(join(dir,'state.json'),JSON.stringify({version:1,selection:{},bugs:[{id:'p:bug:1',title:'Crash',status:'queued',repo:'/unused'}],audit - 读取本地文件host.test.js:38
assert.equal(JSON.parse(await readFile(join(dir,'state.json'),'utf8')).bugs[0].status,'queued'); - 删除本地文件递归host.test.js:39
}finally{disposers.reverse().forEach(f=>f());await rm(dir,{recursive:true,force:true});} - 写入本地文件host.test.js:45
await writeFile(join(dir,'state.json'),JSON.stringify({version:1,selection:{},bugs:[{id:'p:bug:1',title:'Crash',status:'failed',taskId:'task1',cwd:'/u - 读取本地文件host.test.js:50
const saved=JSON.parse(await readFile(join(dir,'state.json'),'utf8')); - 删除本地文件递归host.test.js:53
}finally{disposers.reverse().forEach(f=>f());await rm(dir,{recursive:true,force:true});} - 删除本地文件递归host.test.js:71
}finally{disposers.reverse().forEach(f=>f());await rm(dir,{recursive:true,force:true});} - 写入本地文件host.test.js:79
await writeFile(join(dir,'state.json'),JSON.stringify({version:1,selection:{},bugs:[{id:'p:bug:1',title:'Crash',status:'running',sessionId:'s1',cwd:'/ - 读取本地文件host.test.js:87
const saved=JSON.parse(await readFile(join(dir,'state.json'),'utf8')); - 删除本地文件递归host.test.js:90
}finally{disposers.reverse().forEach(f=>f());await rm(dir,{recursive:true,force:true});} - 写入本地文件host.test.js:95
await writeFile(join(dir,'state.json'),JSON.stringify({version:1,selection:{},bugs:[{id:'p:bug:1',title:'Crash',status:'running',sessionId:'s1',cwd:'/ - 写入本地文件host.test.js:107
await writeFile(join(dir,'state.json'),JSON.stringify({version:1,selection:{},bugs:[{id:'p:bug:1',title:'Crash',status:'preparing',cwd:'/unused',branc - 执行外部命令固定命令host.test.js:120
const git=(...args)=>execFileSync('git',args,{cwd:repo,encoding:'utf8'}).trim(); - 执行外部命令固定命令host.test.js:122
execFileSync('git',['init','-b','develop',repo]);git('config','user.email','t@e.test');git('config','user.name','T'); - 执行外部命令固定命令host.test.js:126
const wt=(...args)=>execFileSync('git',args,{cwd:worktree,encoding:'utf8'}).trim(); - 读取本地文件host.test.js:137
const saved=JSON.parse(await readFile(join(dir,'state.json'),'utf8')); - 读取本地文件host.test.js:142
assert.equal(await readFile(join(worktree,'fix.txt'),'utf8').catch(()=>null),null,'the directory is deleted'); - 执行外部命令固定命令host.test.js:152
const git=(...args)=>execFileSync('git',args,{cwd:repo,encoding:'utf8'}).trim(); - 执行外部命令固定命令host.test.js:154
execFileSync('git',['init','-b','develop',repo]);git('config','user.email','t@e.test');git('config','user.name','T'); - 执行外部命令固定命令host.test.js:198
const git=(...args)=>execFileSync('git',args,{cwd:repo,encoding:'utf8'}).trim(); - 读取本地文件regression.test.js:9
const source = await readFile(new URL('./client.js', import.meta.url), 'utf8'); - 读取本地文件regression.test.js:16
const source = await readFile(new URL('./index.js', import.meta.url), 'utf8'); - 读取本地文件regression.test.js:25
const source = await readFile(new URL('./client.js', import.meta.url), 'utf8'); - 读取本地文件regression.test.js:30
const host = await readFile(new URL('./workflow.js', import.meta.url), 'utf8'); - 读取本地文件regression.test.js:34
const source = await readFile(new URL('./index.js', import.meta.url), 'utf8'); - 读取本地文件regression.test.js:38
const source = await readFile(new URL('./client.js', import.meta.url), 'utf8'); - 读取本地文件remote-status.test.js:20
const saved=async()=>JSON.parse(await readFile(join(dir,'state.json'),'utf8')); - 写入本地文件remote-status.test.js:22
await writeFile(join(dir,'state.json'),JSON.stringify({version:1,selection:{},bugs:[{id:'accepted',remoteId:1,projectKey:'p',typeKey:'bug',status:'ver - 删除本地文件递归remote-status.test.js:35
}finally{disposers.reverse().forEach(f=>f());await rm(dir,{recursive:true,force:true});} - 读取本地文件requirements-host.test.js:18
const saved=async()=>JSON.parse(await readFile(join(dir,'state.json'),'utf8')); - 写入本地文件requirements-host.test.js:20
await writeFile(join(dir,'state.json'),JSON.stringify({version:1,selection:{},bugs:[],audit:[],catalog:{projectKey:'p',projects:[{key:'p',name:'项目',si - 删除本地文件递归requirements-host.test.js:29
}finally{disposers.reverse().forEach(f=>f());await rm(dir,{recursive:true,force:true});}
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
DeepSeek Harness 的飞书项目(Meegle)缺陷工作流:修复分支相互隔离,并设有人工查重、验证、合并三道关卡。
README
由 DeepSeek 最新模型翻译生成飞书缺陷工作流 · DSH 插件
在 DeepSeek Harness(DSH)Web GUI 里读取飞书项目(Feishu Project / Meegle)中分配给我的缺陷,逐条在独立分支上修复,把「重复确认、验收、合并」留给人工处理。
一条缺陷 = 一个分支 = 一个可审查的执行会话;修复提交只落在该缺陷的分支上,推送与合并始终由你完成。
一个 DSH 插件包,把飞书项目里的缺陷队列变成相互隔离、需人工审核的修复分支。重复判定、验证与合并只由人来做。
它解决什么
- 飞书项目里几十上百条 bug,人工挑选、拉分支、写上下文很碎。
- 希望每个 bug 独立分支、互不污染,修完由人验证。
- 重复缺陷不该重复开分支,但机器判断不可靠,需要人先确认。
界面
当前面板组件截图(使用只读状态快照渲染,本地路径已脱敏):
需求搜索与任务范围
面板采用双栏布局:左侧「连接与范围」集中放置扫码授权与本轮范围(项目 / 工作项类型 / 状态 / 所属需求 / 仓库目录 / 修复模型);右侧依次是四张门禁统计卡片(待修复、疑似重复待确认、修复中、待你验证)、「本轮缺陷」、「修复与验收」队列操作与操作历史。授权成功后授权区默认收起,范围配置默认展开;窄面板自动改为单栏,统计卡片改为两列。
展开完整文档(README 剩余 1 节)
飞书缺陷工作流 · DSH 插件 在 DeepSeek Harness(DSH)Web GUI 里读取飞书项目(Feishu Project / Meegle)中分配给我的缺陷,逐条在独立分支上修复,把「重复确认、验收、合并」留给人工处理。 一条缺陷 = 一个分支 = 一个可审查的执行会话;修复提交只落在该缺陷的分支上,推送与合并始终由你完成。 一个 DSH 插件包,把飞书项目里的缺陷队列变成相互隔离、需人工审核的修复分支。重复判定、验证与合并只由人来做。 它解决什么 - 飞书项目里几十上百条 bug,人工挑选、拉分支、写上下文很碎。 - 希望每个 bug 独立分支、互不污染,修完由人验证。 - 重复缺陷不该重复开分支,但机器判断不可靠,需要人先确认。 界面 当前面板组件截图(使用只读状态快照渲染,本地路径已脱敏): 需求搜索与任务范围 面板采用双栏布局:左侧「连接与范围」集中放置扫码授权与本轮范围(项目 / 工作项类型 / 状态 / 所属需求 / 仓库目录 / 修复模型);右侧依次是四张门禁统计卡片(待修复、疑似重复待确认、修复中、待你验证)、「本轮缺陷」、「修复与验收」队列操作与操作历史。授权成功后授权区默认收起,范围配置默认展开;窄面板自动改为单栏,统计卡片改为两列。缺陷搜索支持编号、标题和修复记录,忽略大小写及首尾空白,并与状态筛选组合生效;搜索仅在本地筛选,不会修改飞书数据。 「搜索所属需求」支持名称或编号子串匹配,也支持 #编号,忽略名称大小写和搜索词首尾空白;匹配数量与当前选择分别显示。清空搜索保留已选需求,切换项目或工作项类型则重置搜索和需求选择。 验证通过的缺陷卡片与手动修改飞书状态入口 卡片完整显示标题,将编译、打开 Xcode 等工具与主要动作放在底部;窄面板中按钮自动换行。可在「已读取 · 无需修复」页签查看无需进入修复队列的记录,在已验证卡片上手动修改飞书状态。截图只用于展示界面,不代表真实远端写入验收。 工作流 1. 扫码授权:面板生成官方 Meegle Device Code 二维码,用飞书扫码并在官方页面确认。插件不接触令牌,令牌由官方命令行存入系统钥匙串。 2. 读取范围:选项目 → 选工作项类型(缺陷)→ 按工作流状态筛选(默认 BUG,REOPENED)→(可选)按「所属需求(缺陷)」选择关联需求,默认「全部需求」;选项来自飞书并完整分页读取,与状态条件同时生效,读取后会保存选择,切换项目或工作项类型时清空 → 选本地仓库根目录 →(可选)选修复模型 → 读取分配给我的缺陷。只有飞书状态为 BUG、REOPEN / REOPENED 的条目进入待修复或重复确认,其他状态显示为「已读取 · 无需修复」;重新读取会纠正未开始条目的误标,并保留已有执行记录与人工判重决定。模型列表来自 Harness 当前可路由的模型,默认「跟随 Agent 默认」,只影响之后启动的修复任务。 3. 重复确认:标题相似度 ≥ 0.72 的条目标为「疑似重复 · 待确认」。确认重复 → 跳过、不开分支;确认不重复 → 才允许开始。 4. 隔离修复:开始前先看仓库状态。工作树干净时,直接在仓库里从本地 develop 检出 fix/feishu-<bug编号>[-标题片段];工作树里有你未提交的改动(比如正在 develop 上改别的 bug)时,绝不动它,自动改为在数据目录的 worktrees/ 下开一个隔离 worktree,复用主仓的 Pods 与 workspace,主仓继续留在你原来的分支。两种模式都拒绝已存在的同名分支。 5. 面板内验证(可选):缺陷卡片上的「编译验证」直接在该分支目录里跑模拟器构建(独立 DerivedData,不传 -sdk),把结果、耗时和 .app 路径回填到卡片,失败时给出日志尾部;「在 Xcode 打开」直接打开该目录的 .xcworkspace。两条路都不需要先合并 develop。 6. 人工验收:agent 在修复分支上提交(不推送、不合并),面板显示提交号。在主仓修复时分支已经检出,直接编译运行即可;隔离 worktree 里的修复用该目录下的 .xcworkspace 编译,主仓不受影响。不通过可「反馈并重试」(说明随任务带回 agent);通过只标记「待手动合并」,可用「主仓切回 develop」收尾。验证通过即释放隔离目录:该缺陷的 worktree、构建产物与截图一并删除,卡片改为显示释放的路径和体积,分支和提交仍在仓库里,合并照旧。工作树里还有未提交改动时宁可保留,并在历史里记一条原因;在主仓修复的记录本来就不占额外目录,不受影响。需要时可用「补齐构建前置」补齐 xcworkspace 与 Pods。 7. 修改飞书状态:验证通过后,卡片会出现「修改飞书状态」。点击后读取该缺陷当前可流转的真实状态,选择目标并「确认修改」;成功回读后显示飞书状态,并写入操作历史。需要填写额外表单的流转会提示到飞书完成;网络、权限或回读失败会提示错误,可刷新状态后再检查。验收通过本身不会自动修改飞书状态。 8. 失败与重试:执行失败时卡片直接显示原因(例如 agent 回合被中断、构建失败、同名分支已存在)。分支已经建好的,用「反馈并重试」继续;在准备分支阶段就失败的(例如同名分支已存在),用「重新开始」按当前 develop 重新准备并启动,同样可以补充可选说明(留空即直接开始)。 9. 你来合并:修复提交由 agent 完成,工作树仍有未提交改动时可在卡片上手动提交;推送与合并 develop 由你在自己的终端完成。 10. 标记已完成:任务卡片提供「标记已完成」,点击后保存本地完成状态与时间,可通过「已完成」页签筛选。完成后禁用编译、打开 Xcode、补齐构建前置,并关闭启动、重试、验收、提交、切分支和修改飞书状态等入口;服务端也会拒绝旧页面提交的这些操作。修复或编译仍在运行时须等待结束。该按钮仅标记本地任务,不修改飞书状态、不自动合并或清理目录;重新读取与重启后保留完成状态。 支持版本 | 组件 | 支持范围 | 本次验证 | | DeepSeek Harness | >= 0.2.1-rc.2(dsh.engines.dsh) | 0.2.1-rc.2(macOS) | | 面板运行环境 | DSH Web GUI(dsh web) | 当前桌面版 | | Node.js(随 DSH 提供) | >= 22 | 25.2.0 | | 官方 Meegle CLI | 固定 @lark-project/meegle@1.0.27 | 1.0.27 | | 必需 DSH 扩展 | 无(只用核心服务 webServer/connection/tools/slots/locale) | — | | 可选 DSH 扩展 | 无(不依赖任务面板插件) | — | | 面板内构建(可选) | Xcode + iOS Simulator SDK(xcodebuild,不传 -sdk) | Xcode 27.1 / iOS 27.1 SDK | | 操作系统 | 需要系统钥匙串与 git | macOS | 支持策略 - 1.13.x 为当前支持版本,修复以补丁版本发布(当前 1.13.13,后续改动见「未发布」记录)。 - DSH 或 Meegle CLI 出现破坏性变更时,发布新的次版本并在 CHANGELOG.md 标注兼容范围。 - 低于 0.2.1-rc.2 的 DSH 不受支持;任务面板插件不是依赖,关闭或卸载它不会影响面板加载与修复执行。 - 仅支持单用户本机使用:Host 必须运行,面板跟随 owner 浏览器身份。 安装 前置:DSH >= 0.2.1-rc.2。除此之外不硬依赖任何第三方插件: - 宿主只声明核心服务 webServer、connection、tools;修复会话直接用 DSH 自带的会话服务(sessionController)启动,不需要任务面板。工作区注册表(workspaceRegistry)是可选增强:用它把新会话挂到缺陷目录对应的工作区上,取不到就照常建会话,只是侧栏里会归到「未分组」。 - 客户端只声明核心服务 slots、locale;目录选择器(uiWorkspace)按需获取,取不到就退回手动输入路径。 - 因此关掉任务面板(@linxin666/dsh-client-ui-task-board)后,缺陷面板照常打开,「修复这条」「依次修复队列」「反馈并重试」照常可用:每次修复都是一个普通会话,出现在侧栏会话列表里。 git clone https://github.com/PetterWong-L/dsh-feishu-bugs.git cd dsh-feishu-bugs npm install --omit=dev 在 DSH 中把这个目录作为 bundle 安装(插件管理里选择该目录的绝对路径),然后重启 DSH。侧栏会出现「飞书缺陷工作流」。 给 AI 阅读的安装说明 当你作为 AI 代理帮助安装本插件时,按下面的顺序执行,不要猜测目录或依赖: 1. 确认项目目录:把包含 package.json、index.js、client.js 和 cordis.patch.yml 的仓库目录作为 bundle 根目录(按上面的命令克隆时目录名为 dsh-feishu-bugs)。后续命令都在该目录执行。 2. 检查运行环境:确认宿主是 macOS,已安装并能运行 DSH >= 0.2.1-rc.2、Node.js >= 22、git 和系统钥匙串。只有需要面板内编译验证时,才需要 Xcode 与 iOS Simulator SDK。 3. 安装生产依赖:执行 npm install --omit=dev。不要删除或替换锁文件,不要额外安装任务面板、工作区或会话插件;本插件只依赖 @lark-project/meegle@1.0.27 和 qrcode@1.5.4。 4. 注册 bundle:在 DSH 插件管理中选择该目录的绝对路径进行安装,然后重启 DSH。不要把 index.js 当作独立 Node.js 服务运行,也不要用 npm link 代替 bundle 安装。 5. 检查加载结果:打开 DSH Web GUI,确认侧栏出现「飞书缺陷工作流」。首次使用时在面板内扫码授权;令牌由官方 Meegle CLI 保存到系统钥匙串,AI 不应读取、复制或写入令牌。 6. 确认数据目录:默认数据目录是 ~/.dsh/feishu-bugs-data。如果项目配置了 storageDir,必须使用配置中的绝对路径,并确认该目录可读写;迁移源码目录后要重启 DSH,避免误判为空队列。 7. 安装后的最小验证:在 bundle 根目录执行 npm run check 和 npm test。若只验证运行时加载,则至少检查 DSH 版本、依赖安装、侧栏入口和数据目录;不要为了验证而读取真实缺陷、创建分支或修改远端飞书状态。 AI 执行安装或升级时应保留用户已有的数据目录、分支和选择;升级只需拉取新版本、重新执行依赖安装并重启 DSH。插件不会自动推送、合并或修改飞书状态。遇到安装失败时,先记录 DSH 版本、Node.js 版本、bundle 绝对路径和完整错误,再定位问题,不要盲目删除 node_modules 或数据目录。 数据默认写入 ~/.dsh/feishu-bugs-data;如需自定义,在 bundle 配置里设置绝对路径 storageDir。 升级:拉取新版本,执行 npm install --omit=dev,然后重启 DSH。保留已有 storageDir 配置与数据目录;执行状态、分支与选择都会持久化,重启不会重放分支创建。版本变更与兼容说明见 CHANGELOG.md。 卸载:在插件管理里禁用/移除该 bundle;数据目录不会自动删除。 安全模型 - 授权使用官方 CLI 的专用 profile dsh-feishu-bugs 与固定 host project.feishu.cn,不继承其他飞书 CLI 登录或环境令牌。 - 访问令牌只存在系统钥匙串;插件、浏览器、模型都不读取或转存。 - 面板 HTTP/SSE 使用 Harness 的 owner 连接鉴权;POST 额外校验同源、JSON 与大小。模型侧只有只读状态工具,无法启动授权、确认重复、验收或执行。 - CLI 通过 execFile 固定参数调用,不经 shell;原始错误不返回给浏览器。 - Bug 正文与截图按不可信数据处理;提示词禁止合并、推送、跨仓库修改和读取凭据,并要求图片只当证据、不得提交。 - 附件只下载 project.feishu.cn 签名地址上的图片(PNG/JPEG/GIF/WebP/HEIC),最多 5 张、单张 20MB;文件名重写为安全名,写入 .feishu-attachments/,不执行。 - 修复分支只从本地 develop 创建,不改写历史;插件与代理都不执行 push 或合并,也不动其它仓库。 - 这不是针对本机恶意进程的沙箱;组织级保护请对 develop 设置分支保护。 边界与已知限制 - 所属需求筛选仅支持同一项目、单一关联目标类型的字段配置;读取全部分页后才提供选项,元数据不支持或分页不完整时会报错,不展示部分目录。 - 状态、所属需求与「当前负责人是登录用户」组合筛选;项目提供完成标记时同时限定未完成项。「全部需求」表示不添加需求条件,完整同步会清理筛选范围外尚未开始的记录。 - BUG、REOPEN / REOPENED 的限制用于导入资格和新启动检查;已有修复分支的「反馈并重试」沿用原流程,不会重新检查远端状态。 - 修改飞书状态只对本地已验证记录开放。每次操作校验授权,写前重读可用流转,写后回读目标状态;失败保留本地验证结果,需刷新远端状态确认。远端写后回读失败时,飞书上的状态可能已改变。 - 重复检测只做标题文本相似度,不能替代语义判断。 - 一次只运行一个修复会话;队列在重启后需手动继续。 - 主仓有未提交改动时会自动切到隔离 worktree;该目录在侧栏注册为一个临时工作区行(Feishu <缺陷编号>),会话就落在这一行里,验收通过后工作区行、目录和构建产物一起删除(分支与提交保留)。 - 修复会话跑在缺陷目录里:在主仓修复时挂到仓库自己的工作区分组(不新增行),在隔离 worktree 里修复时为该目录注册一个临时工作区行,验收通过后随之删除。清理时保留尚有未提交改动的工作树,避免丢失工作。 - 开始修复前检查主仓工作树:干净时在主仓创建分支,有未提交改动时使用隔离 worktree;同名分支已存在会拒绝。切换分支、提交修复由插件/代理完成,推送与合并始终由你做。 - 「编译验证」在本机串行执行,同一时间只允许一个构建;构建日志只回填末尾 60 行。 - 执行失败后的「重新开始」会复用原缺陷记录,不会新建重复条目;若该缺陷的会话仍在运行,会拒绝重试并提示先去处理。 - 验证通过后 worktrees/ 下该缺陷的目录与 build/ 下的 DerivedData 会被删除,磁盘占用不会随缺陷数无限增长;被删的只是检出的副本,分支、提交与主仓的 Pods 都不动。删除前会重新检查工作树,有未提交改动就跳过并在「最近操作」里记一条 worktree-kept。 - 修复跑在普通会话里,因此不会为缺陷建任务;会话一定落在某个工作区分组下(主仓模式复用仓库分组,worktree 模式是临时行),可以在侧栏直接找到并继续追问,面板会在 30 分钟内把它重新显示为「修复中」。 - 无飞书消息推送、系统通知、定时同步或 webhook。 - 单次最多导入最近更新的 200 条缺陷(面板会提示截断)。 - 面板「编译验证」只做模拟器 Debug 构建,不签名、不装真机;需要真机时用 Xcode 打开该分支自己跑。 - 构建验证优先使用已连接的 Xcode MCP(mcp__xcode__),未连接时才退回 xcrun xcodebuild;任务总结会注明走了哪条路径。 - 修复质量取决于 DSH 会话;完成不等于修好,必须人工验收。 - 只读取附件字段里的图片,不解析富文本正文中的内联图片;非图片附件(日志、录屏)不会下载。 - 截图下载失败不会卡住修复,但会把失败原因写进提示词,代理需显式说明缺图。 - 读取、筛选、分支、截图下载与面板状态流转已用真实账号验证;真实缺陷的修复闭环建议先小范围试用。 开发 npm install npm test # node --test .test.js npm run check # index.js / client.js 语法检查 自动化测试覆盖需求分页与组合查询、服务端权限和持久化、修复资格转换,以及客户端搜索、选择和状态修改确认。客户端交互用例使用 Hooks mock;文档截图使用浏览器挂载当前组件并读取只读快照,两者均不等同于真实飞书 API 的端到端验收。 | 文件 | 职责 | | index.js | Host 扩展:动作、状态机、任务启动、HTTP/SSE 路由 | | client.js | 侧栏与主面板 UI(React,经 DSH 插槽挂载) | | meegle.js / mql.js | 官方 CLI 调用、MQL 查询、分页与 typed 字段解码 | | auth.js / cli.js | Device Code 授权、CLI 执行封装 | | git.js | 仓库内分支创建与校验、脏仓库隔离、旧 worktree 处理 | | build.js | 面板内构建:workspace/scheme 发现、xcodebuild 执行、产物与日志 | | workflow.js / store.js | 人工门禁状态机(含失败重试/重新开始)与持久化 | | http.js | owner 鉴权路由 | 改完不用重启 DSH DSH 只在启动时求值插件的 ESM 模块并缓存,所以默认情况下改代码必须重启才生效。开启 HMR 后可以省掉这一步: ~/.dsh/profiles/desktop/cordis.patch.yml - id: hmr config: root: - "/absolute/path/to/dsh-feishu-bugs" @deepseek-ai/dsh-hmr 会监听该目录(node_modules 默认排除),文件变动后重建插件实例:ctx.set/ctx.tool/事件 重新注册,进行中的任务不受影响。改完执行: npm run reload # touch index.js / client.js,触发 watcher 生效范围:宿主侧(工具、动作、工作流、清理逻辑)立即生效;client.js 的面板 UI 需要刷新一次页面(⌘R);cordis.patch.yml 里的 config 改动、以及 package.json 的依赖变更仍需重启。副作用是插件实例重建、内存态丢失(本插件状态都在 storageDir,无影响)。 版本记录 见 CHANGELOG.md。 License MIT
预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
PetterWong-L/dsh-feishu-bugs
把飞书缺陷逐条拆到独立分支修复并留人工验收
相关插件 · 平台集成
智能体工程课walkinglabs/learn-harness-engineering
一门项目制课程,教你搭建环境、管理状态、做验证、加控制机制,让 AI 编程智能体稳定干活。
★ 21,155已汉化DSH
71.7
视频翻译配音liuzhao1225/YouDub-webui
开源视频本地化工作台:导入视频,识别、翻译并逐句克隆原声,生成字幕和配音成片。
★ 5,792已汉化Cordis
71.4
失败经验库Ikalus1988/MisakaNet
📚 零依赖、由 git 支撑的微课程库,让 AI 智能体异步分享并检索经过验证的调试经验。| https://misakanet.org
★ 550已汉化DSH
71.2
IM 机器人接入xmanrui/dsh-im
让 DeepSeek Harness 触手可及 Connecting DeepSeek Harness
★ 3,170已汉化DeepSeek 客户端
71
桌面智能体工作区freestylefly/wesight
WeSight 是开源桌面控制台,面向本地 AI 智能体。它帮你安装或复用 Claude…
★ 933已汉化MCP
71
中国平台研究引擎Jesseovo/last30days-skill-cn
给 AI Agent 的中文互联网研究 Skill:检索 8 大平台最近 30…
★ 2,041已汉化DSH
70.7
这是你的仓库?认领条目