bosun 0.41.8 → 0.41.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.env.example +1 -1
  2. package/README.md +23 -1
  3. package/agent/agent-event-bus.mjs +31 -2
  4. package/agent/agent-pool.mjs +275 -40
  5. package/agent/agent-prompts.mjs +9 -1
  6. package/agent/agent-supervisor.mjs +22 -0
  7. package/agent/autofix.mjs +1 -1
  8. package/agent/primary-agent.mjs +115 -5
  9. package/cli.mjs +3 -2
  10. package/config/config.mjs +47 -33
  11. package/config/context-shredding-config.mjs +1 -1
  12. package/config/repo-root.mjs +41 -33
  13. package/desktop/main.mjs +350 -25
  14. package/desktop/preload.cjs +8 -0
  15. package/desktop/preload.mjs +19 -0
  16. package/entrypoint.mjs +332 -0
  17. package/git/sdk-conflict-resolver.mjs +1 -1
  18. package/infra/health-status.mjs +72 -0
  19. package/infra/library-manager.mjs +58 -1
  20. package/infra/maintenance.mjs +1 -2
  21. package/infra/monitor.mjs +26 -8
  22. package/infra/session-tracker.mjs +30 -3
  23. package/package.json +12 -4
  24. package/server/bosun-mcp-server.mjs +1004 -0
  25. package/server/setup-web-server.mjs +288 -259
  26. package/server/ui-server.mjs +1323 -26
  27. package/shell/claude-shell.mjs +14 -1
  28. package/shell/codex-config.mjs +1 -1
  29. package/shell/codex-model-profiles.mjs +170 -30
  30. package/shell/codex-shell.mjs +63 -18
  31. package/shell/opencode-providers.mjs +20 -8
  32. package/task/task-executor.mjs +28 -0
  33. package/task/task-store.mjs +13 -4
  34. package/telegram/telegram-sentinel.mjs +54 -3
  35. package/tools/list-todos.mjs +7 -1
  36. package/ui/app.js +3 -2
  37. package/ui/components/agent-selector.js +127 -0
  38. package/ui/components/session-list.js +15 -10
  39. package/ui/demo-defaults.js +334 -336
  40. package/ui/modules/router.js +2 -0
  41. package/ui/modules/state.js +13 -5
  42. package/ui/tabs/chat.js +3 -0
  43. package/ui/tabs/library.js +284 -52
  44. package/ui/tabs/tasks.js +5 -13
  45. package/ui/tabs/workflows.js +766 -3
  46. package/workflow/workflow-engine.mjs +246 -5
  47. package/workflow/workflow-nodes/definitions.mjs +37 -0
  48. package/workflow/workflow-nodes.mjs +1014 -184
  49. package/workflow/workflow-templates.mjs +0 -5
  50. package/workflow-templates/_helpers.mjs +253 -0
  51. package/workflow-templates/agents.mjs +199 -226
  52. package/workflow-templates/github.mjs +106 -16
  53. package/workflow-templates/sub-workflows.mjs +233 -0
  54. package/workflow-templates/task-execution.mjs +125 -471
  55. package/workflow-templates/task-lifecycle.mjs +11 -48
  56. package/workspace/command-diagnostics.mjs +460 -0
  57. package/workspace/context-cache.mjs +396 -28
  58. package/workspace/worktree-manager.mjs +1 -1
@@ -970,7 +970,7 @@
970
970
  "type": "action.run_command",
971
971
  "label": "Fetch Bosun PR State",
972
972
  "config": {
973
- "command": "node -e \" const {execFileSync}=require('child_process'); const hours=Number('{{lookbackHours}}')||24; const repoScope=String('{{repoScope}}'||'auto').trim(); const since=new Date(Date.now()-hours*3600000).toISOString(); function ghJson(args){ try{const o=execFileSync('gh',args,{encoding:'utf8',stdio:['pipe','pipe','pipe']}).trim();return o?JSON.parse(o):[];} catch{return [];} } const merged=ghJson(['pr','list','--state','merged','--label','bosun-attached','--json','number,title,body,headRefName,mergedAt','--limit','50']); const open=ghJson(['pr','list','--state','open','--label','bosun-attached','--json','number,title,body,headRefName,isDraft','--limit','50']); function extractTaskId(pr){ const src=String((pr.body||'')+'\\n'+(pr.title||'')); const m=src.match(/(?:Bosun-Task|VE-Task|Task-ID|task[_-]?id)[:\\s]+([a-zA-Z0-9_-]{4,64})/i); return m?m[1].trim():null; } const recentMerged=merged.filter(p=>!p.mergedAt||new Date(p.mergedAt)>=new Date(since)); console.log(JSON.stringify({ repoScope, merged:recentMerged.map(p=>({n:p.number,title:p.title,branch:p.headRefName,taskId:extractTaskId(p)})), open:open.filter(p=>!p.isDraft).map(p=>({n:p.number,title:p.title,branch:p.headRefName,taskId:extractTaskId(p)})), })); \"",
973
+ "command": "node -e \" const fs=require('fs'); const path=require('path'); const {execFileSync}=require('child_process'); const hours=Number('{{lookbackHours}}')||24; const repoScope=String('{{repoScope}}'||'auto').trim(); const since=new Date(Date.now()-hours*3600000).toISOString(); function ghJson(args){ try{const o=execFileSync('gh',args,{encoding:'utf8',stdio:['pipe','pipe','pipe']}).trim();return o?JSON.parse(o):[];} catch{return [];} } function configPath(){ const home=String(process.env.BOSUN_HOME||process.env.VK_PROJECT_DIR||'').trim(); return home?path.join(home,'bosun.config.json'):path.join(process.cwd(),'bosun.config.json'); } function collectReposFromConfig(){ const repos=[]; try{ const cfg=JSON.parse(fs.readFileSync(configPath(),'utf8')); const workspaces=Array.isArray(cfg?.workspaces)?cfg.workspaces:[]; if(workspaces.length>0){ const active=String(cfg?.activeWorkspace||'').trim().toLowerCase(); const activeWs=active?workspaces.find(w=>String(w?.id||'').trim().toLowerCase()===active):null; const wsList=activeWs?[activeWs]:workspaces; for(const ws of wsList){ for(const repo of (Array.isArray(ws?.repos)?ws.repos:[])){ const slug=typeof repo==='string'?String(repo).trim():String(repo?.slug||'').trim(); if(slug) repos.push(slug); } } } if(repos.length===0){ for(const repo of (Array.isArray(cfg?.repos)?cfg.repos:[])){ const slug=typeof repo==='string'?String(repo).trim():String(repo?.slug||'').trim(); if(slug) repos.push(slug); } } }catch{} return repos; } function resolveRepoTargets(){ if(repoScope&&repoScope!=='auto'&&repoScope!=='all'&&repoScope!=='current'){ return [...new Set(repoScope.split(',').map(v=>v.trim()).filter(Boolean))]; } if(repoScope==='current') return ['']; const fromConfig=collectReposFromConfig(); if(fromConfig.length>0) return [...new Set(fromConfig)]; const envRepo=String(process.env.GITHUB_REPOSITORY||'').trim(); if(envRepo) return [envRepo]; return ['']; } function parseRepoFromUrl(url){ const raw=String(url||''); const marker='github.com/'; const idx=raw.toLowerCase().indexOf(marker); if(idx<0) return ''; const tail=raw.slice(idx+marker.length).split('/'); if(tail.length<2) return ''; const owner=String(tail[0]||'').trim(); const repo=String(tail[1]||'').trim(); return owner&&repo?(owner+'/'+repo):''; } function extractTaskId(pr){ const src=String((pr.body||'')+'\\n'+(pr.title||'')); const m=src.match(/(?:Bosun-Task|VE-Task|Task-ID|task[_-]?id)[:\\s]+([a-zA-Z0-9_-]{4,64})/i); return m?m[1].trim():null; } const repoTargets=resolveRepoTargets(); const merged=[]; const open=[]; for(const target of repoTargets){ const repo=String(target||'').trim(); const mergedArgs=['pr','list','--state','merged','--label','bosun-attached','--json','number,title,body,headRefName,mergedAt,url','--limit','50']; const openArgs=['pr','list','--state','open','--label','bosun-attached','--json','number,title,body,headRefName,isDraft,url','--limit','50']; if(repo){ mergedArgs.push('--repo',repo); openArgs.push('--repo',repo); } for(const pr of ghJson(mergedArgs)){ merged.push({...pr,__repo:repo||parseRepoFromUrl(pr?.url)||String(process.env.GITHUB_REPOSITORY||'').trim()}); } for(const pr of ghJson(openArgs)){ open.push({...pr,__repo:repo||parseRepoFromUrl(pr?.url)||String(process.env.GITHUB_REPOSITORY||'').trim()}); } } const recentMerged=merged.filter(p=>!p.mergedAt||new Date(p.mergedAt)>=new Date(since)); console.log(JSON.stringify({ repoScope, reposScanned: repoTargets.length, merged:recentMerged.map(p=>({n:p.number,repo:p.__repo||'',title:p.title,branch:p.headRefName,taskId:extractTaskId(p)})), open:open.filter(p=>!p.isDraft).map(p=>({n:p.number,repo:p.__repo||'',title:p.title,branch:p.headRefName,taskId:extractTaskId(p)})), })); \"",
974
974
  "continueOnError": true
975
975
  },
976
976
  "position": {
@@ -1001,7 +1001,7 @@
1001
1001
  "type": "action.run_command",
1002
1002
  "label": "Sync PR State → Kanban (Programmatic)",
1003
1003
  "config": {
1004
- "command": "node -e \" const {execFileSync}=require('child_process'); const fs=require('fs'); const raw=String(process.env.BOSUN_FETCH_PR_STATE||''); const data=(()=>{try{return JSON.parse(raw||'{}')}catch{return {}}})(); const merged=Array.isArray(data.merged)?data.merged:[]; const open=Array.isArray(data.open)?data.open:[]; const updates=[]; const unresolved=[]; const taskCli=['task-cli.mjs','task/task-cli.mjs'].find(p=>fs.existsSync(p))||''; if(!taskCli){ console.log(JSON.stringify({updated:0,unresolved:[{reason:'task_cli_missing'}],needsAgent:true})); process.exit(0); } function runTask(args){return execFileSync('node',[taskCli,...args],{encoding:'utf8',stdio:['pipe','pipe','pipe']}).trim();} function parseJsonObject(raw){const txt=String(raw||'').trim();if(!txt)return null;try{return JSON.parse(txt);}catch{}const lines=txt.split(/\\r?\\n/).map(s=>s.trim()).filter(Boolean);for(let i=lines.length-1;i>=0;i--){const line=lines[i];if(!(line.startsWith('{')||line.startsWith('[')))continue;try{return JSON.parse(line);}catch{}}const start=txt.indexOf('{');const end=txt.lastIndexOf('}');if(start>=0&&end>start){try{return JSON.parse(txt.slice(start,end+1));}catch{}}return null;} function getTaskSnapshot(id){ try{const raw=runTask(['get',id,'--json']);const task=parseJsonObject(raw);return {status:task?.status||null,reviewStatus:task?.reviewStatus||null};}catch{return {status:null,reviewStatus:null};} } for(const item of merged){ const id=String(item?.taskId||'').trim(); if(!id) continue; try{runTask(['update',id,'--status','done']);updates.push({taskId:id,status:'done'});}catch(e){unresolved.push({taskId:id,status:'done',error:String(e?.message||e)});} } for(const item of open){ const id=String(item?.taskId||'').trim(); if(!id) continue; try{const snap=getTaskSnapshot(id);const current=snap?.status;const review=String(snap?.reviewStatus||'').toLowerCase();if(current==='inreview'||current==='done'){updates.push({taskId:id,status:current,skipped:true});continue;}if(current==='todo'||current==='inprogress'){const reason=(review==='changes_requested'||review==='change_requested'||review==='requested_changes')?'changes_requested_pending_fix':'local_progress_state';updates.push({taskId:id,status:current,skipped:true,reason});continue;}runTask(['update',id,'--status','inreview']);updates.push({taskId:id,status:'inreview'});}catch(e){unresolved.push({taskId:id,status:'inreview',error:String(e?.message||e)});} } console.log(JSON.stringify({updated:updates.length,updates,unresolved,needsAgent:unresolved.length>0})); \"",
1004
+ "command": "node -e \" const {execFileSync}=require('child_process'); const fs=require('fs'); const raw=String(process.env.BOSUN_FETCH_PR_STATE||''); const data=(()=>{try{return JSON.parse(raw||'{}')}catch{return {}}})(); const merged=Array.isArray(data.merged)?data.merged:[]; const open=Array.isArray(data.open)?data.open:[]; const updates=[]; const unresolved=[]; const maxBuffer=25*1024*1024; const cliPath=fs.existsSync('cli.mjs')?'cli.mjs':''; const taskCli=['task-cli.mjs','task/task-cli.mjs'].find(p=>fs.existsSync(p))||''; const taskRunner=cliPath?'cli':(taskCli?'task-cli':''); if(!taskRunner){ console.log(JSON.stringify({updated:0,unresolved:[{reason:'task_command_missing'}],needsAgent:true})); process.exit(0); } function runTask(args){const cmdArgs=taskRunner==='cli'?['cli.mjs','task',...args,'--config-dir','.bosun','--repo-root','.']:[taskCli,...args];return execFileSync('node',cmdArgs,{encoding:'utf8',stdio:['pipe','pipe','pipe'],maxBuffer}).trim();} function parseJsonObject(raw){const txt=String(raw||'').trim();if(!txt)return null;try{return JSON.parse(txt);}catch{}const lines=txt.split(/\\r?\\n/);for(let start=0;start<lines.length;start++){const token=lines[start].trim();if(!(token==='['||token==='{'||token.startsWith('[{')||token.startsWith('{\"')||token.startsWith('[\"')))continue;const candidate=lines.slice(start).join('\\n').trim();try{return JSON.parse(candidate);}catch{}}const compact=lines.map(s=>s.trim()).filter(Boolean);for(let i=compact.length-1;i>=0;i--){const line=compact[i];if(!(line.startsWith('{')||line.startsWith('[')))continue;try{return JSON.parse(line);}catch{}}const start=txt.indexOf('{');const end=txt.lastIndexOf('}');if(start>=0&&end>start){try{return JSON.parse(txt.slice(start,end+1));}catch{}}return null;} let taskListCache=null; function normalizeRepo(value){return String(value||'').trim().toLowerCase();} function listTasks(){ if(Array.isArray(taskListCache)) return taskListCache; try{const raw=runTask(['list','--json']);const tasks=parseJsonObject(raw);taskListCache=Array.isArray(tasks)?tasks:[];return taskListCache;}catch{taskListCache=[];return taskListCache;} } function resolveTaskId(item){ const explicit=String(item?.taskId||'').trim(); if(explicit) return explicit; const branch=String(item?.branch||'').trim(); if(!branch) return ''; const repo=normalizeRepo(item?.repo); const matches=listTasks().filter((task)=>{ const taskBranch=String(task?.branchName||'').trim(); if(taskBranch!==branch) return false; const taskRepo=normalizeRepo(task?.repository||''); if(!repo || !taskRepo) return true; return taskRepo===repo; }); if(matches.length===1) return String(matches[0]?.id||'').trim(); const exactRepo=matches.find((task)=>normalizeRepo(task?.repository||'')===repo); return exactRepo?String(exactRepo?.id||'').trim():''; } function getTaskSnapshot(id){ try{const raw=runTask(['get',id,'--json']);const task=parseJsonObject(raw);return {status:task?.status||null,reviewStatus:task?.reviewStatus||null};}catch{return {status:null,reviewStatus:null};} } for(const item of merged){ const id=resolveTaskId(item); if(!id){unresolved.push({taskId:null,repo:String(item?.repo||''),branch:String(item?.branch||''),status:'done',reason:'task_lookup_failed'});continue;} try{runTask(['update',id,'--status','done']);updates.push({taskId:id,status:'done'});}catch(e){unresolved.push({taskId:id,status:'done',error:String(e?.message||e)});} } for(const item of open){ const id=resolveTaskId(item); if(!id){unresolved.push({taskId:null,repo:String(item?.repo||''),branch:String(item?.branch||''),status:'inreview',reason:'task_lookup_failed'});continue;} try{const snap=getTaskSnapshot(id);const current=String(snap?.status||'').trim().toLowerCase();const review=String(snap?.reviewStatus||'').toLowerCase();if(current==='inreview'||current==='done'){updates.push({taskId:id,status:current,skipped:true});continue;}runTask(['update',id,'--status','inreview']);updates.push({taskId:id,status:'inreview',fromStatus:current||null,reviewStatus:review||null});}catch(e){unresolved.push({taskId:id,status:'inreview',error:String(e?.message||e)});} } const actionableUnresolved=unresolved.filter((item)=>String(item?.taskId||'').trim()); console.log(JSON.stringify({updated:updates.length,updates,unresolved,needsAgent:actionableUnresolved.length>0})); \"",
1005
1005
  "continueOnError": true,
1006
1006
  "failOnError": false,
1007
1007
  "env": {
@@ -1021,7 +1021,7 @@
1021
1021
  "type": "condition.expression",
1022
1022
  "label": "Needs Agent Sync?",
1023
1023
  "config": {
1024
- "expression": "(()=>{try{const raw=$ctx.getNodeOutput('sync-programmatic')?.output||'{}';const d=JSON.parse(raw);return d?.needsAgent===true || (Array.isArray(d?.unresolved)&&d.unresolved.length>0);}catch{return true;}})()"
1024
+ "expression": "(()=>{try{const raw=$ctx.getNodeOutput('sync-programmatic')?.output||'{}';const d=JSON.parse(raw);const actionable=Array.isArray(d?.unresolved)?d.unresolved.some((item)=>String(item?.taskId||'').trim()):false;return d?.needsAgent===true || actionable;}catch{return true;}})()"
1025
1025
  },
1026
1026
  "position": {
1027
1027
  "x": 400,
@@ -4607,7 +4607,7 @@
4607
4607
  "multi-language"
4608
4608
  ],
4609
4609
  "nodeCount": 29,
4610
- "edgeCount": 30,
4610
+ "edgeCount": 28,
4611
4611
  "recommended": true,
4612
4612
  "enabled": true,
4613
4613
  "trigger": "trigger.task_assigned",
@@ -4714,7 +4714,7 @@
4714
4714
  ]
4715
4715
  },
4716
4716
  {
4717
- "id": "build",
4717
+ "id": "main-build",
4718
4718
  "type": "validation.build",
4719
4719
  "label": "Build Check",
4720
4720
  "config": {
@@ -4723,29 +4723,29 @@
4723
4723
  },
4724
4724
  "position": {
4725
4725
  "x": 400,
4726
- "y": 650
4726
+ "y": 0
4727
4727
  },
4728
4728
  "outputs": [
4729
4729
  "default"
4730
4730
  ]
4731
4731
  },
4732
4732
  {
4733
- "id": "test-final",
4733
+ "id": "main-test",
4734
4734
  "type": "validation.tests",
4735
- "label": "Final Test Run",
4735
+ "label": "Test Run",
4736
4736
  "config": {
4737
4737
  "command": "{{testCommand}}"
4738
4738
  },
4739
4739
  "position": {
4740
4740
  "x": 400,
4741
- "y": 780
4741
+ "y": 130
4742
4742
  },
4743
4743
  "outputs": [
4744
4744
  "default"
4745
4745
  ]
4746
4746
  },
4747
4747
  {
4748
- "id": "lint",
4748
+ "id": "main-lint",
4749
4749
  "type": "validation.lint",
4750
4750
  "label": "Lint Check",
4751
4751
  "config": {
@@ -4753,7 +4753,7 @@
4753
4753
  },
4754
4754
  "position": {
4755
4755
  "x": 400,
4756
- "y": 910
4756
+ "y": 260
4757
4757
  },
4758
4758
  "outputs": [
4759
4759
  "default"
@@ -4764,7 +4764,7 @@
4764
4764
  "type": "condition.expression",
4765
4765
  "label": "All Checks Passed?",
4766
4766
  "config": {
4767
- "expression": "$ctx.getNodeOutput('build')?.passed === true && $ctx.getNodeOutput('test-final')?.passed === true && $ctx.getNodeOutput('lint')?.passed === true"
4767
+ "expression": "$ctx.getNodeOutput('main-build')?.passed === true && $ctx.getNodeOutput('main-test')?.passed === true && $ctx.getNodeOutput('main-lint')?.passed === true"
4768
4768
  },
4769
4769
  "position": {
4770
4770
  "x": 400,
@@ -4795,22 +4795,6 @@
4795
4795
  "default"
4796
4796
  ]
4797
4797
  },
4798
- {
4799
- "id": "push-ok",
4800
- "type": "condition.expression",
4801
- "label": "Push OK?",
4802
- "config": {
4803
- "expression": "$ctx.getNodeOutput('push-branch')?.pushed === true"
4804
- },
4805
- "position": {
4806
- "x": 250,
4807
- "y": 1175
4808
- },
4809
- "outputs": [
4810
- "yes",
4811
- "no"
4812
- ]
4813
- },
4814
4798
  {
4815
4799
  "id": "create-pr",
4816
4800
  "type": "action.create_pr",
@@ -4834,15 +4818,15 @@
4834
4818
  ]
4835
4819
  },
4836
4820
  {
4837
- "id": "pr-created",
4821
+ "id": "main-pr-ok",
4838
4822
  "type": "condition.expression",
4839
- "label": "Handoff Recorded?",
4823
+ "label": "PR Created?",
4840
4824
  "config": {
4841
- "expression": "Boolean($ctx.getNodeOutput('create-pr')?.prNumber || $ctx.getNodeOutput('create-pr')?.prUrl)"
4825
+ "expression": "Boolean($ctx.getNodeOutput($edge.source)?.prNumber || $ctx.getNodeOutput($edge.source)?.prUrl)"
4842
4826
  },
4843
4827
  "position": {
4844
- "x": 250,
4845
- "y": 1240
4828
+ "x": 400,
4829
+ "y": 0
4846
4830
  },
4847
4831
  "outputs": [
4848
4832
  "yes",
@@ -4850,7 +4834,7 @@
4850
4834
  ]
4851
4835
  },
4852
4836
  {
4853
- "id": "set-inreview",
4837
+ "id": "main-set-inreview",
4854
4838
  "type": "action.update_task_status",
4855
4839
  "label": "Set In-Review",
4856
4840
  "config": {
@@ -4859,8 +4843,30 @@
4859
4843
  "taskTitle": "{{taskTitle}}"
4860
4844
  },
4861
4845
  "position": {
4862
- "x": 180,
4863
- "y": 1320
4846
+ "x": 300,
4847
+ "y": 130
4848
+ },
4849
+ "outputs": [
4850
+ "default"
4851
+ ]
4852
+ },
4853
+ {
4854
+ "id": "main-handoff-progressor",
4855
+ "type": "action.execute_workflow",
4856
+ "label": "Handoff PR Progressor",
4857
+ "config": {
4858
+ "workflowId": "template-bosun-pr-progressor",
4859
+ "mode": "dispatch",
4860
+ "input": {
4861
+ "taskId": "{{taskId}}",
4862
+ "taskTitle": "{{taskTitle}}",
4863
+ "branch": "{{branch}}",
4864
+ "baseBranch": "{{baseBranch}}"
4865
+ }
4866
+ },
4867
+ "position": {
4868
+ "x": 300,
4869
+ "y": 260
4864
4870
  },
4865
4871
  "outputs": [
4866
4872
  "default"
@@ -4903,7 +4909,7 @@
4903
4909
  "label": "Summarize Validation Output",
4904
4910
  "config": {
4905
4911
  "key": "validationSummary",
4906
- "value": "(() => { const implement = $ctx.getNodeOutput('implement') || {}; const build = $ctx.getNodeOutput('build') || {}; const test = $ctx.getNodeOutput('test-final') || {}; const lint = $ctx.getNodeOutput('lint') || {}; return ['- implement.success: ' + (implement.success === true), '- build.passed: ' + (build.passed === true), '- test-final.passed: ' + (test.passed === true), '- lint.passed: ' + (lint.passed === true), '', 'Build output:', String(build.output || '').slice(0, 6000), '', 'Test output:', String(test.output || '').slice(0, 6000), '', 'Lint output:', String(lint.output || '').slice(0, 6000)].join('\\n'); })()",
4912
+ "value": "(() => { const implement = $ctx.getNodeOutput('implement') || {}; const build = $ctx.getNodeOutput('main-build') || {}; const test = $ctx.getNodeOutput('main-test') || {}; const lint = $ctx.getNodeOutput('main-lint') || {}; return ['- implement.success: ' + (implement.success === true), '- build.passed: ' + (build.passed === true), '- test-final.passed: ' + (test.passed === true), '- lint.passed: ' + (lint.passed === true), '', 'Build output:', String(build.output || '').slice(0, 6000), '', 'Test output:', String(test.output || '').slice(0, 6000), '', 'Lint output:', String(lint.output || '').slice(0, 6000)].join('\\n'); })()",
4907
4913
  "isExpression": true
4908
4914
  },
4909
4915
  "position": {
@@ -4932,46 +4938,46 @@
4932
4938
  ]
4933
4939
  },
4934
4940
  {
4935
- "id": "build-retry",
4941
+ "id": "retry-build",
4936
4942
  "type": "validation.build",
4937
- "label": "Build Check (Retry)",
4943
+ "label": "Build Check",
4938
4944
  "config": {
4939
4945
  "command": "{{buildCommand}}",
4940
4946
  "zeroWarnings": true
4941
4947
  },
4942
4948
  "position": {
4943
- "x": 620,
4944
- "y": 1300
4949
+ "x": 400,
4950
+ "y": 0
4945
4951
  },
4946
4952
  "outputs": [
4947
4953
  "default"
4948
4954
  ]
4949
4955
  },
4950
4956
  {
4951
- "id": "test-retry",
4957
+ "id": "retry-test",
4952
4958
  "type": "validation.tests",
4953
- "label": "Final Test Run (Retry)",
4959
+ "label": "Test Run",
4954
4960
  "config": {
4955
4961
  "command": "{{testCommand}}"
4956
4962
  },
4957
4963
  "position": {
4958
- "x": 620,
4959
- "y": 1430
4964
+ "x": 400,
4965
+ "y": 130
4960
4966
  },
4961
4967
  "outputs": [
4962
4968
  "default"
4963
4969
  ]
4964
4970
  },
4965
4971
  {
4966
- "id": "lint-retry",
4972
+ "id": "retry-lint",
4967
4973
  "type": "validation.lint",
4968
- "label": "Lint Check (Retry)",
4974
+ "label": "Lint Check",
4969
4975
  "config": {
4970
4976
  "command": "{{lintCommand}}"
4971
4977
  },
4972
4978
  "position": {
4973
- "x": 620,
4974
- "y": 1560
4979
+ "x": 400,
4980
+ "y": 260
4975
4981
  },
4976
4982
  "outputs": [
4977
4983
  "default"
@@ -4982,7 +4988,7 @@
4982
4988
  "type": "condition.expression",
4983
4989
  "label": "Retry Checks Passed?",
4984
4990
  "config": {
4985
- "expression": "$ctx.getNodeOutput('build-retry')?.passed === true && $ctx.getNodeOutput('test-retry')?.passed === true && $ctx.getNodeOutput('lint-retry')?.passed === true"
4991
+ "expression": "$ctx.getNodeOutput('retry-build')?.passed === true && $ctx.getNodeOutput('retry-test')?.passed === true && $ctx.getNodeOutput('retry-lint')?.passed === true"
4986
4992
  },
4987
4993
  "position": {
4988
4994
  "x": 620,
@@ -5013,22 +5019,6 @@
5013
5019
  "default"
5014
5020
  ]
5015
5021
  },
5016
- {
5017
- "id": "push-ok-retry",
5018
- "type": "condition.expression",
5019
- "label": "Push OK? (Retry)",
5020
- "config": {
5021
- "expression": "$ctx.getNodeOutput('push-branch-retry')?.pushed === true"
5022
- },
5023
- "position": {
5024
- "x": 450,
5025
- "y": 1825
5026
- },
5027
- "outputs": [
5028
- "yes",
5029
- "no"
5030
- ]
5031
- },
5032
5022
  {
5033
5023
  "id": "create-pr-retry",
5034
5024
  "type": "action.create_pr",
@@ -5052,15 +5042,15 @@
5052
5042
  ]
5053
5043
  },
5054
5044
  {
5055
- "id": "pr-created-retry",
5045
+ "id": "retry-pr-ok",
5056
5046
  "type": "condition.expression",
5057
- "label": "Handoff Recorded (Retry Path)?",
5047
+ "label": "PR Created?",
5058
5048
  "config": {
5059
- "expression": "Boolean($ctx.getNodeOutput('create-pr-retry')?.prNumber || $ctx.getNodeOutput('create-pr-retry')?.prUrl)"
5049
+ "expression": "Boolean($ctx.getNodeOutput($edge.source)?.prNumber || $ctx.getNodeOutput($edge.source)?.prUrl)"
5060
5050
  },
5061
5051
  "position": {
5062
- "x": 450,
5063
- "y": 1890
5052
+ "x": 400,
5053
+ "y": 0
5064
5054
  },
5065
5055
  "outputs": [
5066
5056
  "yes",
@@ -5068,17 +5058,39 @@
5068
5058
  ]
5069
5059
  },
5070
5060
  {
5071
- "id": "set-inreview-retry",
5061
+ "id": "retry-set-inreview",
5072
5062
  "type": "action.update_task_status",
5073
- "label": "Set In-Review (Retry)",
5063
+ "label": "Set In-Review",
5074
5064
  "config": {
5075
5065
  "taskId": "{{taskId}}",
5076
5066
  "status": "inreview",
5077
5067
  "taskTitle": "{{taskTitle}}"
5078
5068
  },
5079
5069
  "position": {
5080
- "x": 360,
5081
- "y": 1980
5070
+ "x": 300,
5071
+ "y": 130
5072
+ },
5073
+ "outputs": [
5074
+ "default"
5075
+ ]
5076
+ },
5077
+ {
5078
+ "id": "retry-handoff-progressor",
5079
+ "type": "action.execute_workflow",
5080
+ "label": "Handoff PR Progressor",
5081
+ "config": {
5082
+ "workflowId": "template-bosun-pr-progressor",
5083
+ "mode": "dispatch",
5084
+ "input": {
5085
+ "taskId": "{{taskId}}",
5086
+ "taskTitle": "{{taskTitle}}",
5087
+ "branch": "{{branch}}",
5088
+ "baseBranch": "{{baseBranch}}"
5089
+ }
5090
+ },
5091
+ "position": {
5092
+ "x": 300,
5093
+ "y": 260
5082
5094
  },
5083
5095
  "outputs": [
5084
5096
  "default"
@@ -5151,26 +5163,26 @@
5151
5163
  "sourcePort": "default"
5152
5164
  },
5153
5165
  {
5154
- "id": "implement->build",
5166
+ "id": "implement->main-build",
5155
5167
  "source": "implement",
5156
- "target": "build",
5168
+ "target": "main-build",
5157
5169
  "sourcePort": "default"
5158
5170
  },
5159
5171
  {
5160
- "id": "build->test-final",
5161
- "source": "build",
5162
- "target": "test-final",
5172
+ "id": "main-build->main-test",
5173
+ "source": "main-build",
5174
+ "target": "main-test",
5163
5175
  "sourcePort": "default"
5164
5176
  },
5165
5177
  {
5166
- "id": "test-final->lint",
5167
- "source": "test-final",
5168
- "target": "lint",
5178
+ "id": "main-test->main-lint",
5179
+ "source": "main-test",
5180
+ "target": "main-lint",
5169
5181
  "sourcePort": "default"
5170
5182
  },
5171
5183
  {
5172
- "id": "lint->all-passed",
5173
- "source": "lint",
5184
+ "id": "main-lint->all-passed",
5185
+ "source": "main-lint",
5174
5186
  "target": "all-passed",
5175
5187
  "sourcePort": "default"
5176
5188
  },
@@ -5189,78 +5201,69 @@
5189
5201
  "condition": "$output?.result !== true"
5190
5202
  },
5191
5203
  {
5192
- "id": "set-validation-summary->auto-fix",
5193
- "source": "set-validation-summary",
5194
- "target": "auto-fix",
5195
- "sourcePort": "default"
5196
- },
5197
- {
5198
- "id": "push-branch->push-ok",
5204
+ "id": "push-branch->create-pr",
5199
5205
  "source": "push-branch",
5200
- "target": "push-ok",
5206
+ "target": "create-pr",
5201
5207
  "sourcePort": "default"
5202
5208
  },
5203
5209
  {
5204
- "id": "push-ok->create-pr",
5205
- "source": "push-ok",
5206
- "target": "create-pr",
5207
- "sourcePort": "yes",
5208
- "condition": "$output?.result === true"
5210
+ "id": "create-pr->main-pr-ok",
5211
+ "source": "create-pr",
5212
+ "target": "main-pr-ok",
5213
+ "sourcePort": "default"
5209
5214
  },
5210
5215
  {
5211
- "id": "push-ok->notify-pr-failed",
5212
- "source": "push-ok",
5213
- "target": "notify-pr-failed",
5214
- "sourcePort": "no",
5215
- "condition": "$output?.result !== true"
5216
+ "id": "main-pr-ok->main-set-inreview",
5217
+ "source": "main-pr-ok",
5218
+ "target": "main-set-inreview",
5219
+ "sourcePort": "yes"
5216
5220
  },
5217
5221
  {
5218
- "id": "create-pr->pr-created",
5219
- "source": "create-pr",
5220
- "target": "pr-created",
5222
+ "id": "main-set-inreview->main-handoff-progressor",
5223
+ "source": "main-set-inreview",
5224
+ "target": "main-handoff-progressor",
5221
5225
  "sourcePort": "default"
5222
5226
  },
5223
5227
  {
5224
- "id": "pr-created->set-inreview",
5225
- "source": "pr-created",
5226
- "target": "set-inreview",
5227
- "sourcePort": "yes",
5228
- "condition": "$output?.result === true"
5229
- },
5230
- {
5231
- "id": "set-inreview->notify-done",
5232
- "source": "set-inreview",
5228
+ "id": "main-handoff-progressor->notify-done",
5229
+ "source": "main-handoff-progressor",
5233
5230
  "target": "notify-done",
5234
5231
  "sourcePort": "default"
5235
5232
  },
5236
5233
  {
5237
- "id": "pr-created->notify-pr-failed",
5238
- "source": "pr-created",
5234
+ "id": "main-pr-ok->notify-pr-failed",
5235
+ "source": "main-pr-ok",
5239
5236
  "target": "notify-pr-failed",
5240
5237
  "sourcePort": "no",
5241
5238
  "condition": "$output?.result !== true"
5242
5239
  },
5243
5240
  {
5244
- "id": "auto-fix->build-retry",
5241
+ "id": "set-validation-summary->auto-fix",
5242
+ "source": "set-validation-summary",
5243
+ "target": "auto-fix",
5244
+ "sourcePort": "default"
5245
+ },
5246
+ {
5247
+ "id": "auto-fix->retry-build",
5245
5248
  "source": "auto-fix",
5246
- "target": "build-retry",
5249
+ "target": "retry-build",
5247
5250
  "sourcePort": "default"
5248
5251
  },
5249
5252
  {
5250
- "id": "build-retry->test-retry",
5251
- "source": "build-retry",
5252
- "target": "test-retry",
5253
+ "id": "retry-build->retry-test",
5254
+ "source": "retry-build",
5255
+ "target": "retry-test",
5253
5256
  "sourcePort": "default"
5254
5257
  },
5255
5258
  {
5256
- "id": "test-retry->lint-retry",
5257
- "source": "test-retry",
5258
- "target": "lint-retry",
5259
+ "id": "retry-test->retry-lint",
5260
+ "source": "retry-test",
5261
+ "target": "retry-lint",
5259
5262
  "sourcePort": "default"
5260
5263
  },
5261
5264
  {
5262
- "id": "lint-retry->retry-passed",
5263
- "source": "lint-retry",
5265
+ "id": "retry-lint->retry-passed",
5266
+ "source": "retry-lint",
5264
5267
  "target": "retry-passed",
5265
5268
  "sourcePort": "default"
5266
5269
  },
@@ -5279,47 +5282,38 @@
5279
5282
  "condition": "$output?.result !== true"
5280
5283
  },
5281
5284
  {
5282
- "id": "push-branch-retry->push-ok-retry",
5285
+ "id": "push-branch-retry->create-pr-retry",
5283
5286
  "source": "push-branch-retry",
5284
- "target": "push-ok-retry",
5287
+ "target": "create-pr-retry",
5285
5288
  "sourcePort": "default"
5286
5289
  },
5287
5290
  {
5288
- "id": "push-ok-retry->create-pr-retry",
5289
- "source": "push-ok-retry",
5290
- "target": "create-pr-retry",
5291
- "sourcePort": "yes",
5292
- "condition": "$output?.result === true"
5291
+ "id": "create-pr-retry->retry-pr-ok",
5292
+ "source": "create-pr-retry",
5293
+ "target": "retry-pr-ok",
5294
+ "sourcePort": "default"
5293
5295
  },
5294
5296
  {
5295
- "id": "push-ok-retry->notify-pr-failed-retry",
5296
- "source": "push-ok-retry",
5297
- "target": "notify-pr-failed-retry",
5298
- "sourcePort": "no",
5299
- "condition": "$output?.result !== true"
5297
+ "id": "retry-pr-ok->retry-set-inreview",
5298
+ "source": "retry-pr-ok",
5299
+ "target": "retry-set-inreview",
5300
+ "sourcePort": "yes"
5300
5301
  },
5301
5302
  {
5302
- "id": "create-pr-retry->pr-created-retry",
5303
- "source": "create-pr-retry",
5304
- "target": "pr-created-retry",
5303
+ "id": "retry-set-inreview->retry-handoff-progressor",
5304
+ "source": "retry-set-inreview",
5305
+ "target": "retry-handoff-progressor",
5305
5306
  "sourcePort": "default"
5306
5307
  },
5307
5308
  {
5308
- "id": "pr-created-retry->set-inreview-retry",
5309
- "source": "pr-created-retry",
5310
- "target": "set-inreview-retry",
5311
- "sourcePort": "yes",
5312
- "condition": "$output?.result === true"
5313
- },
5314
- {
5315
- "id": "set-inreview-retry->notify-done-retry",
5316
- "source": "set-inreview-retry",
5309
+ "id": "retry-handoff-progressor->notify-done-retry",
5310
+ "source": "retry-handoff-progressor",
5317
5311
  "target": "notify-done-retry",
5318
5312
  "sourcePort": "default"
5319
5313
  },
5320
5314
  {
5321
- "id": "pr-created-retry->notify-pr-failed-retry",
5322
- "source": "pr-created-retry",
5315
+ "id": "retry-pr-ok->notify-pr-failed-retry",
5316
+ "source": "retry-pr-ok",
5323
5317
  "target": "notify-pr-failed-retry",
5324
5318
  "sourcePort": "no",
5325
5319
  "condition": "$output?.result !== true"
@@ -14372,7 +14366,7 @@
14372
14366
  },
14373
14367
  "position": {
14374
14368
  "x": 400,
14375
- "y": 380
14369
+ "y": 340
14376
14370
  },
14377
14371
  "outputs": [
14378
14372
  "default"
@@ -14397,7 +14391,7 @@
14397
14391
  },
14398
14392
  "position": {
14399
14393
  "x": 400,
14400
- "y": 560
14394
+ "y": 500
14401
14395
  },
14402
14396
  "outputs": [
14403
14397
  "default"
@@ -14412,7 +14406,7 @@
14412
14406
  },
14413
14407
  "position": {
14414
14408
  "x": 400,
14415
- "y": 720
14409
+ "y": 660
14416
14410
  },
14417
14411
  "outputs": [
14418
14412
  "default"
@@ -14753,7 +14747,7 @@
14753
14747
  },
14754
14748
  "position": {
14755
14749
  "x": 400,
14756
- "y": 380
14750
+ "y": 340
14757
14751
  },
14758
14752
  "outputs": [
14759
14753
  "default"
@@ -14778,7 +14772,7 @@
14778
14772
  },
14779
14773
  "position": {
14780
14774
  "x": 400,
14781
- "y": 560
14775
+ "y": 500
14782
14776
  },
14783
14777
  "outputs": [
14784
14778
  "default"
@@ -14793,7 +14787,7 @@
14793
14787
  },
14794
14788
  "position": {
14795
14789
  "x": 400,
14796
- "y": 720
14790
+ "y": 660
14797
14791
  },
14798
14792
  "outputs": [
14799
14793
  "default"
@@ -15255,7 +15249,7 @@
15255
15249
  },
15256
15250
  "position": {
15257
15251
  "x": 400,
15258
- "y": 380
15252
+ "y": 340
15259
15253
  },
15260
15254
  "outputs": [
15261
15255
  "default"
@@ -15280,7 +15274,7 @@
15280
15274
  },
15281
15275
  "position": {
15282
15276
  "x": 400,
15283
- "y": 560
15277
+ "y": 500
15284
15278
  },
15285
15279
  "outputs": [
15286
15280
  "default"
@@ -15295,7 +15289,7 @@
15295
15289
  },
15296
15290
  "position": {
15297
15291
  "x": 400,
15298
- "y": 720
15292
+ "y": 660
15299
15293
  },
15300
15294
  "outputs": [
15301
15295
  "default"
@@ -15346,7 +15340,7 @@
15346
15340
  ],
15347
15341
  "nodeCount": 5,
15348
15342
  "edgeCount": 4,
15349
- "recommended": false,
15343
+ "recommended": true,
15350
15344
  "enabled": true,
15351
15345
  "trigger": "trigger.task_assigned",
15352
15346
  "variables": {
@@ -15431,7 +15425,7 @@
15431
15425
  },
15432
15426
  "position": {
15433
15427
  "x": 400,
15434
- "y": 380
15428
+ "y": 340
15435
15429
  },
15436
15430
  "outputs": [
15437
15431
  "default"
@@ -15456,7 +15450,7 @@
15456
15450
  },
15457
15451
  "position": {
15458
15452
  "x": 400,
15459
- "y": 560
15453
+ "y": 500
15460
15454
  },
15461
15455
  "outputs": [
15462
15456
  "default"
@@ -15471,7 +15465,7 @@
15471
15465
  },
15472
15466
  "position": {
15473
15467
  "x": 400,
15474
- "y": 720
15468
+ "y": 660
15475
15469
  },
15476
15470
  "outputs": [
15477
15471
  "default"
@@ -15791,7 +15785,7 @@
15791
15785
  },
15792
15786
  "position": {
15793
15787
  "x": 400,
15794
- "y": 380
15788
+ "y": 340
15795
15789
  },
15796
15790
  "outputs": [
15797
15791
  "default"
@@ -15816,7 +15810,7 @@
15816
15810
  },
15817
15811
  "position": {
15818
15812
  "x": 400,
15819
- "y": 560
15813
+ "y": 500
15820
15814
  },
15821
15815
  "outputs": [
15822
15816
  "default"
@@ -15831,7 +15825,7 @@
15831
15825
  },
15832
15826
  "position": {
15833
15827
  "x": 400,
15834
- "y": 720
15828
+ "y": 660
15835
15829
  },
15836
15830
  "outputs": [
15837
15831
  "default"
@@ -18724,6 +18718,7 @@
18724
18718
  "timeoutMs": "{{taskTimeoutMs}}",
18725
18719
  "maxRetries": "{{maxRetries}}",
18726
18720
  "maxContinues": "{{maxContinues}}",
18721
+ "resolveMode": "library",
18727
18722
  "failOnError": false
18728
18723
  },
18729
18724
  "position": {
@@ -18748,6 +18743,7 @@
18748
18743
  "timeoutMs": "{{taskTimeoutMs}}",
18749
18744
  "maxRetries": "{{maxRetries}}",
18750
18745
  "maxContinues": "{{maxContinues}}",
18746
+ "resolveMode": "library",
18751
18747
  "failOnError": false
18752
18748
  },
18753
18749
  "position": {
@@ -18772,6 +18768,7 @@
18772
18768
  "timeoutMs": "{{taskTimeoutMs}}",
18773
18769
  "maxRetries": "{{maxRetries}}",
18774
18770
  "maxContinues": "{{maxContinues}}",
18771
+ "resolveMode": "library",
18775
18772
  "failOnError": false
18776
18773
  },
18777
18774
  "position": {
@@ -20080,6 +20077,8 @@
20080
20077
  "cwd": "{{worktreePath}}",
20081
20078
  "timeoutMs": "{{taskTimeoutMs}}",
20082
20079
  "maxRetries": "{{maxRetries}}",
20080
+ "maxContinues": "{{maxContinues}}",
20081
+ "resolveMode": "library",
20083
20082
  "failOnError": false
20084
20083
  },
20085
20084
  "position": {
@@ -21732,7 +21731,7 @@
21732
21731
  "type": "action.run_command",
21733
21732
  "label": "Fetch Bosun PR State",
21734
21733
  "config": {
21735
- "command": "node -e \" const {execFileSync}=require('child_process'); const hours=Number('{{lookbackHours}}')||24; const repoScope=String('{{repoScope}}'||'auto').trim(); const since=new Date(Date.now()-hours*3600000).toISOString(); function ghJson(args){ try{const o=execFileSync('gh',args,{encoding:'utf8',stdio:['pipe','pipe','pipe']}).trim();return o?JSON.parse(o):[];} catch{return [];} } const merged=ghJson(['pr','list','--state','merged','--label','bosun-attached','--json','number,title,body,headRefName,mergedAt','--limit','50']); const open=ghJson(['pr','list','--state','open','--label','bosun-attached','--json','number,title,body,headRefName,isDraft','--limit','50']); function extractTaskId(pr){ const src=String((pr.body||'')+'\\n'+(pr.title||'')); const m=src.match(/(?:Bosun-Task|VE-Task|Task-ID|task[_-]?id)[:\\s]+([a-zA-Z0-9_-]{4,64})/i); return m?m[1].trim():null; } const recentMerged=merged.filter(p=>!p.mergedAt||new Date(p.mergedAt)>=new Date(since)); console.log(JSON.stringify({ repoScope, merged:recentMerged.map(p=>({n:p.number,title:p.title,branch:p.headRefName,taskId:extractTaskId(p)})), open:open.filter(p=>!p.isDraft).map(p=>({n:p.number,title:p.title,branch:p.headRefName,taskId:extractTaskId(p)})), })); \"",
21734
+ "command": "node -e \" const fs=require('fs'); const path=require('path'); const {execFileSync}=require('child_process'); const hours=Number('{{lookbackHours}}')||24; const repoScope=String('{{repoScope}}'||'auto').trim(); const since=new Date(Date.now()-hours*3600000).toISOString(); function ghJson(args){ try{const o=execFileSync('gh',args,{encoding:'utf8',stdio:['pipe','pipe','pipe']}).trim();return o?JSON.parse(o):[];} catch{return [];} } function configPath(){ const home=String(process.env.BOSUN_HOME||process.env.VK_PROJECT_DIR||'').trim(); return home?path.join(home,'bosun.config.json'):path.join(process.cwd(),'bosun.config.json'); } function collectReposFromConfig(){ const repos=[]; try{ const cfg=JSON.parse(fs.readFileSync(configPath(),'utf8')); const workspaces=Array.isArray(cfg?.workspaces)?cfg.workspaces:[]; if(workspaces.length>0){ const active=String(cfg?.activeWorkspace||'').trim().toLowerCase(); const activeWs=active?workspaces.find(w=>String(w?.id||'').trim().toLowerCase()===active):null; const wsList=activeWs?[activeWs]:workspaces; for(const ws of wsList){ for(const repo of (Array.isArray(ws?.repos)?ws.repos:[])){ const slug=typeof repo==='string'?String(repo).trim():String(repo?.slug||'').trim(); if(slug) repos.push(slug); } } } if(repos.length===0){ for(const repo of (Array.isArray(cfg?.repos)?cfg.repos:[])){ const slug=typeof repo==='string'?String(repo).trim():String(repo?.slug||'').trim(); if(slug) repos.push(slug); } } }catch{} return repos; } function resolveRepoTargets(){ if(repoScope&&repoScope!=='auto'&&repoScope!=='all'&&repoScope!=='current'){ return [...new Set(repoScope.split(',').map(v=>v.trim()).filter(Boolean))]; } if(repoScope==='current') return ['']; const fromConfig=collectReposFromConfig(); if(fromConfig.length>0) return [...new Set(fromConfig)]; const envRepo=String(process.env.GITHUB_REPOSITORY||'').trim(); if(envRepo) return [envRepo]; return ['']; } function parseRepoFromUrl(url){ const raw=String(url||''); const marker='github.com/'; const idx=raw.toLowerCase().indexOf(marker); if(idx<0) return ''; const tail=raw.slice(idx+marker.length).split('/'); if(tail.length<2) return ''; const owner=String(tail[0]||'').trim(); const repo=String(tail[1]||'').trim(); return owner&&repo?(owner+'/'+repo):''; } function extractTaskId(pr){ const src=String((pr.body||'')+'\\n'+(pr.title||'')); const m=src.match(/(?:Bosun-Task|VE-Task|Task-ID|task[_-]?id)[:\\s]+([a-zA-Z0-9_-]{4,64})/i); return m?m[1].trim():null; } const repoTargets=resolveRepoTargets(); const merged=[]; const open=[]; for(const target of repoTargets){ const repo=String(target||'').trim(); const mergedArgs=['pr','list','--state','merged','--label','bosun-attached','--json','number,title,body,headRefName,mergedAt,url','--limit','50']; const openArgs=['pr','list','--state','open','--label','bosun-attached','--json','number,title,body,headRefName,isDraft,url','--limit','50']; if(repo){ mergedArgs.push('--repo',repo); openArgs.push('--repo',repo); } for(const pr of ghJson(mergedArgs)){ merged.push({...pr,__repo:repo||parseRepoFromUrl(pr?.url)||String(process.env.GITHUB_REPOSITORY||'').trim()}); } for(const pr of ghJson(openArgs)){ open.push({...pr,__repo:repo||parseRepoFromUrl(pr?.url)||String(process.env.GITHUB_REPOSITORY||'').trim()}); } } const recentMerged=merged.filter(p=>!p.mergedAt||new Date(p.mergedAt)>=new Date(since)); console.log(JSON.stringify({ repoScope, reposScanned: repoTargets.length, merged:recentMerged.map(p=>({n:p.number,repo:p.__repo||'',title:p.title,branch:p.headRefName,taskId:extractTaskId(p)})), open:open.filter(p=>!p.isDraft).map(p=>({n:p.number,repo:p.__repo||'',title:p.title,branch:p.headRefName,taskId:extractTaskId(p)})), })); \"",
21736
21735
  "continueOnError": true
21737
21736
  },
21738
21737
  "position": {
@@ -21763,7 +21762,7 @@
21763
21762
  "type": "action.run_command",
21764
21763
  "label": "Sync PR State → Kanban (Programmatic)",
21765
21764
  "config": {
21766
- "command": "node -e \" const {execFileSync}=require('child_process'); const fs=require('fs'); const raw=String(process.env.BOSUN_FETCH_PR_STATE||''); const data=(()=>{try{return JSON.parse(raw||'{}')}catch{return {}}})(); const merged=Array.isArray(data.merged)?data.merged:[]; const open=Array.isArray(data.open)?data.open:[]; const updates=[]; const unresolved=[]; const taskCli=['task-cli.mjs','task/task-cli.mjs'].find(p=>fs.existsSync(p))||''; if(!taskCli){ console.log(JSON.stringify({updated:0,unresolved:[{reason:'task_cli_missing'}],needsAgent:true})); process.exit(0); } function runTask(args){return execFileSync('node',[taskCli,...args],{encoding:'utf8',stdio:['pipe','pipe','pipe']}).trim();} function parseJsonObject(raw){const txt=String(raw||'').trim();if(!txt)return null;try{return JSON.parse(txt);}catch{}const lines=txt.split(/\\r?\\n/).map(s=>s.trim()).filter(Boolean);for(let i=lines.length-1;i>=0;i--){const line=lines[i];if(!(line.startsWith('{')||line.startsWith('[')))continue;try{return JSON.parse(line);}catch{}}const start=txt.indexOf('{');const end=txt.lastIndexOf('}');if(start>=0&&end>start){try{return JSON.parse(txt.slice(start,end+1));}catch{}}return null;} function getTaskSnapshot(id){ try{const raw=runTask(['get',id,'--json']);const task=parseJsonObject(raw);return {status:task?.status||null,reviewStatus:task?.reviewStatus||null};}catch{return {status:null,reviewStatus:null};} } for(const item of merged){ const id=String(item?.taskId||'').trim(); if(!id) continue; try{runTask(['update',id,'--status','done']);updates.push({taskId:id,status:'done'});}catch(e){unresolved.push({taskId:id,status:'done',error:String(e?.message||e)});} } for(const item of open){ const id=String(item?.taskId||'').trim(); if(!id) continue; try{const snap=getTaskSnapshot(id);const current=snap?.status;const review=String(snap?.reviewStatus||'').toLowerCase();if(current==='inreview'||current==='done'){updates.push({taskId:id,status:current,skipped:true});continue;}if(current==='todo'||current==='inprogress'){const reason=(review==='changes_requested'||review==='change_requested'||review==='requested_changes')?'changes_requested_pending_fix':'local_progress_state';updates.push({taskId:id,status:current,skipped:true,reason});continue;}runTask(['update',id,'--status','inreview']);updates.push({taskId:id,status:'inreview'});}catch(e){unresolved.push({taskId:id,status:'inreview',error:String(e?.message||e)});} } console.log(JSON.stringify({updated:updates.length,updates,unresolved,needsAgent:unresolved.length>0})); \"",
21765
+ "command": "node -e \" const {execFileSync}=require('child_process'); const fs=require('fs'); const raw=String(process.env.BOSUN_FETCH_PR_STATE||''); const data=(()=>{try{return JSON.parse(raw||'{}')}catch{return {}}})(); const merged=Array.isArray(data.merged)?data.merged:[]; const open=Array.isArray(data.open)?data.open:[]; const updates=[]; const unresolved=[]; const maxBuffer=25*1024*1024; const cliPath=fs.existsSync('cli.mjs')?'cli.mjs':''; const taskCli=['task-cli.mjs','task/task-cli.mjs'].find(p=>fs.existsSync(p))||''; const taskRunner=cliPath?'cli':(taskCli?'task-cli':''); if(!taskRunner){ console.log(JSON.stringify({updated:0,unresolved:[{reason:'task_command_missing'}],needsAgent:true})); process.exit(0); } function runTask(args){const cmdArgs=taskRunner==='cli'?['cli.mjs','task',...args,'--config-dir','.bosun','--repo-root','.']:[taskCli,...args];return execFileSync('node',cmdArgs,{encoding:'utf8',stdio:['pipe','pipe','pipe'],maxBuffer}).trim();} function parseJsonObject(raw){const txt=String(raw||'').trim();if(!txt)return null;try{return JSON.parse(txt);}catch{}const lines=txt.split(/\\r?\\n/);for(let start=0;start<lines.length;start++){const token=lines[start].trim();if(!(token==='['||token==='{'||token.startsWith('[{')||token.startsWith('{\"')||token.startsWith('[\"')))continue;const candidate=lines.slice(start).join('\\n').trim();try{return JSON.parse(candidate);}catch{}}const compact=lines.map(s=>s.trim()).filter(Boolean);for(let i=compact.length-1;i>=0;i--){const line=compact[i];if(!(line.startsWith('{')||line.startsWith('[')))continue;try{return JSON.parse(line);}catch{}}const start=txt.indexOf('{');const end=txt.lastIndexOf('}');if(start>=0&&end>start){try{return JSON.parse(txt.slice(start,end+1));}catch{}}return null;} let taskListCache=null; function normalizeRepo(value){return String(value||'').trim().toLowerCase();} function listTasks(){ if(Array.isArray(taskListCache)) return taskListCache; try{const raw=runTask(['list','--json']);const tasks=parseJsonObject(raw);taskListCache=Array.isArray(tasks)?tasks:[];return taskListCache;}catch{taskListCache=[];return taskListCache;} } function resolveTaskId(item){ const explicit=String(item?.taskId||'').trim(); if(explicit) return explicit; const branch=String(item?.branch||'').trim(); if(!branch) return ''; const repo=normalizeRepo(item?.repo); const matches=listTasks().filter((task)=>{ const taskBranch=String(task?.branchName||'').trim(); if(taskBranch!==branch) return false; const taskRepo=normalizeRepo(task?.repository||''); if(!repo || !taskRepo) return true; return taskRepo===repo; }); if(matches.length===1) return String(matches[0]?.id||'').trim(); const exactRepo=matches.find((task)=>normalizeRepo(task?.repository||'')===repo); return exactRepo?String(exactRepo?.id||'').trim():''; } function getTaskSnapshot(id){ try{const raw=runTask(['get',id,'--json']);const task=parseJsonObject(raw);return {status:task?.status||null,reviewStatus:task?.reviewStatus||null};}catch{return {status:null,reviewStatus:null};} } for(const item of merged){ const id=resolveTaskId(item); if(!id){unresolved.push({taskId:null,repo:String(item?.repo||''),branch:String(item?.branch||''),status:'done',reason:'task_lookup_failed'});continue;} try{runTask(['update',id,'--status','done']);updates.push({taskId:id,status:'done'});}catch(e){unresolved.push({taskId:id,status:'done',error:String(e?.message||e)});} } for(const item of open){ const id=resolveTaskId(item); if(!id){unresolved.push({taskId:null,repo:String(item?.repo||''),branch:String(item?.branch||''),status:'inreview',reason:'task_lookup_failed'});continue;} try{const snap=getTaskSnapshot(id);const current=String(snap?.status||'').trim().toLowerCase();const review=String(snap?.reviewStatus||'').toLowerCase();if(current==='inreview'||current==='done'){updates.push({taskId:id,status:current,skipped:true});continue;}runTask(['update',id,'--status','inreview']);updates.push({taskId:id,status:'inreview',fromStatus:current||null,reviewStatus:review||null});}catch(e){unresolved.push({taskId:id,status:'inreview',error:String(e?.message||e)});} } const actionableUnresolved=unresolved.filter((item)=>String(item?.taskId||'').trim()); console.log(JSON.stringify({updated:updates.length,updates,unresolved,needsAgent:actionableUnresolved.length>0})); \"",
21767
21766
  "continueOnError": true,
21768
21767
  "failOnError": false,
21769
21768
  "env": {
@@ -21783,7 +21782,7 @@
21783
21782
  "type": "condition.expression",
21784
21783
  "label": "Needs Agent Sync?",
21785
21784
  "config": {
21786
- "expression": "(()=>{try{const raw=$ctx.getNodeOutput('sync-programmatic')?.output||'{}';const d=JSON.parse(raw);return d?.needsAgent===true || (Array.isArray(d?.unresolved)&&d.unresolved.length>0);}catch{return true;}})()"
21785
+ "expression": "(()=>{try{const raw=$ctx.getNodeOutput('sync-programmatic')?.output||'{}';const d=JSON.parse(raw);const actionable=Array.isArray(d?.unresolved)?d.unresolved.some((item)=>String(item?.taskId||'').trim()):false;return d?.needsAgent===true || actionable;}catch{return true;}})()"
21787
21786
  },
21788
21787
  "position": {
21789
21788
  "x": 400,
@@ -25233,7 +25232,7 @@
25233
25232
  ]
25234
25233
  },
25235
25234
  {
25236
- "id": "build",
25235
+ "id": "main-build",
25237
25236
  "type": "validation.build",
25238
25237
  "label": "Build Check",
25239
25238
  "config": {
@@ -25242,29 +25241,29 @@
25242
25241
  },
25243
25242
  "position": {
25244
25243
  "x": 400,
25245
- "y": 650
25244
+ "y": 0
25246
25245
  },
25247
25246
  "outputs": [
25248
25247
  "default"
25249
25248
  ]
25250
25249
  },
25251
25250
  {
25252
- "id": "test-final",
25251
+ "id": "main-test",
25253
25252
  "type": "validation.tests",
25254
- "label": "Final Test Run",
25253
+ "label": "Test Run",
25255
25254
  "config": {
25256
25255
  "command": "{{testCommand}}"
25257
25256
  },
25258
25257
  "position": {
25259
25258
  "x": 400,
25260
- "y": 780
25259
+ "y": 130
25261
25260
  },
25262
25261
  "outputs": [
25263
25262
  "default"
25264
25263
  ]
25265
25264
  },
25266
25265
  {
25267
- "id": "lint",
25266
+ "id": "main-lint",
25268
25267
  "type": "validation.lint",
25269
25268
  "label": "Lint Check",
25270
25269
  "config": {
@@ -25272,7 +25271,7 @@
25272
25271
  },
25273
25272
  "position": {
25274
25273
  "x": 400,
25275
- "y": 910
25274
+ "y": 260
25276
25275
  },
25277
25276
  "outputs": [
25278
25277
  "default"
@@ -25283,7 +25282,7 @@
25283
25282
  "type": "condition.expression",
25284
25283
  "label": "All Checks Passed?",
25285
25284
  "config": {
25286
- "expression": "$ctx.getNodeOutput('build')?.passed === true && $ctx.getNodeOutput('test-final')?.passed === true && $ctx.getNodeOutput('lint')?.passed === true"
25285
+ "expression": "$ctx.getNodeOutput('main-build')?.passed === true && $ctx.getNodeOutput('main-test')?.passed === true && $ctx.getNodeOutput('main-lint')?.passed === true"
25287
25286
  },
25288
25287
  "position": {
25289
25288
  "x": 400,
@@ -25314,22 +25313,6 @@
25314
25313
  "default"
25315
25314
  ]
25316
25315
  },
25317
- {
25318
- "id": "push-ok",
25319
- "type": "condition.expression",
25320
- "label": "Push OK?",
25321
- "config": {
25322
- "expression": "$ctx.getNodeOutput('push-branch')?.pushed === true"
25323
- },
25324
- "position": {
25325
- "x": 250,
25326
- "y": 1175
25327
- },
25328
- "outputs": [
25329
- "yes",
25330
- "no"
25331
- ]
25332
- },
25333
25316
  {
25334
25317
  "id": "create-pr",
25335
25318
  "type": "action.create_pr",
@@ -25353,15 +25336,15 @@
25353
25336
  ]
25354
25337
  },
25355
25338
  {
25356
- "id": "pr-created",
25339
+ "id": "main-pr-ok",
25357
25340
  "type": "condition.expression",
25358
- "label": "Handoff Recorded?",
25341
+ "label": "PR Created?",
25359
25342
  "config": {
25360
- "expression": "Boolean($ctx.getNodeOutput('create-pr')?.prNumber || $ctx.getNodeOutput('create-pr')?.prUrl)"
25343
+ "expression": "Boolean($ctx.getNodeOutput($edge.source)?.prNumber || $ctx.getNodeOutput($edge.source)?.prUrl)"
25361
25344
  },
25362
25345
  "position": {
25363
- "x": 250,
25364
- "y": 1240
25346
+ "x": 400,
25347
+ "y": 0
25365
25348
  },
25366
25349
  "outputs": [
25367
25350
  "yes",
@@ -25369,7 +25352,7 @@
25369
25352
  ]
25370
25353
  },
25371
25354
  {
25372
- "id": "set-inreview",
25355
+ "id": "main-set-inreview",
25373
25356
  "type": "action.update_task_status",
25374
25357
  "label": "Set In-Review",
25375
25358
  "config": {
@@ -25378,8 +25361,30 @@
25378
25361
  "taskTitle": "{{taskTitle}}"
25379
25362
  },
25380
25363
  "position": {
25381
- "x": 180,
25382
- "y": 1320
25364
+ "x": 300,
25365
+ "y": 130
25366
+ },
25367
+ "outputs": [
25368
+ "default"
25369
+ ]
25370
+ },
25371
+ {
25372
+ "id": "main-handoff-progressor",
25373
+ "type": "action.execute_workflow",
25374
+ "label": "Handoff PR Progressor",
25375
+ "config": {
25376
+ "workflowId": "template-bosun-pr-progressor",
25377
+ "mode": "dispatch",
25378
+ "input": {
25379
+ "taskId": "{{taskId}}",
25380
+ "taskTitle": "{{taskTitle}}",
25381
+ "branch": "{{branch}}",
25382
+ "baseBranch": "{{baseBranch}}"
25383
+ }
25384
+ },
25385
+ "position": {
25386
+ "x": 300,
25387
+ "y": 260
25383
25388
  },
25384
25389
  "outputs": [
25385
25390
  "default"
@@ -25422,7 +25427,7 @@
25422
25427
  "label": "Summarize Validation Output",
25423
25428
  "config": {
25424
25429
  "key": "validationSummary",
25425
- "value": "(() => { const implement = $ctx.getNodeOutput('implement') || {}; const build = $ctx.getNodeOutput('build') || {}; const test = $ctx.getNodeOutput('test-final') || {}; const lint = $ctx.getNodeOutput('lint') || {}; return ['- implement.success: ' + (implement.success === true), '- build.passed: ' + (build.passed === true), '- test-final.passed: ' + (test.passed === true), '- lint.passed: ' + (lint.passed === true), '', 'Build output:', String(build.output || '').slice(0, 6000), '', 'Test output:', String(test.output || '').slice(0, 6000), '', 'Lint output:', String(lint.output || '').slice(0, 6000)].join('\\n'); })()",
25430
+ "value": "(() => { const implement = $ctx.getNodeOutput('implement') || {}; const build = $ctx.getNodeOutput('main-build') || {}; const test = $ctx.getNodeOutput('main-test') || {}; const lint = $ctx.getNodeOutput('main-lint') || {}; return ['- implement.success: ' + (implement.success === true), '- build.passed: ' + (build.passed === true), '- test-final.passed: ' + (test.passed === true), '- lint.passed: ' + (lint.passed === true), '', 'Build output:', String(build.output || '').slice(0, 6000), '', 'Test output:', String(test.output || '').slice(0, 6000), '', 'Lint output:', String(lint.output || '').slice(0, 6000)].join('\\n'); })()",
25426
25431
  "isExpression": true
25427
25432
  },
25428
25433
  "position": {
@@ -25451,46 +25456,46 @@
25451
25456
  ]
25452
25457
  },
25453
25458
  {
25454
- "id": "build-retry",
25459
+ "id": "retry-build",
25455
25460
  "type": "validation.build",
25456
- "label": "Build Check (Retry)",
25461
+ "label": "Build Check",
25457
25462
  "config": {
25458
25463
  "command": "{{buildCommand}}",
25459
25464
  "zeroWarnings": true
25460
25465
  },
25461
25466
  "position": {
25462
- "x": 620,
25463
- "y": 1300
25467
+ "x": 400,
25468
+ "y": 0
25464
25469
  },
25465
25470
  "outputs": [
25466
25471
  "default"
25467
25472
  ]
25468
25473
  },
25469
25474
  {
25470
- "id": "test-retry",
25475
+ "id": "retry-test",
25471
25476
  "type": "validation.tests",
25472
- "label": "Final Test Run (Retry)",
25477
+ "label": "Test Run",
25473
25478
  "config": {
25474
25479
  "command": "{{testCommand}}"
25475
25480
  },
25476
25481
  "position": {
25477
- "x": 620,
25478
- "y": 1430
25482
+ "x": 400,
25483
+ "y": 130
25479
25484
  },
25480
25485
  "outputs": [
25481
25486
  "default"
25482
25487
  ]
25483
25488
  },
25484
25489
  {
25485
- "id": "lint-retry",
25490
+ "id": "retry-lint",
25486
25491
  "type": "validation.lint",
25487
- "label": "Lint Check (Retry)",
25492
+ "label": "Lint Check",
25488
25493
  "config": {
25489
25494
  "command": "{{lintCommand}}"
25490
25495
  },
25491
25496
  "position": {
25492
- "x": 620,
25493
- "y": 1560
25497
+ "x": 400,
25498
+ "y": 260
25494
25499
  },
25495
25500
  "outputs": [
25496
25501
  "default"
@@ -25501,7 +25506,7 @@
25501
25506
  "type": "condition.expression",
25502
25507
  "label": "Retry Checks Passed?",
25503
25508
  "config": {
25504
- "expression": "$ctx.getNodeOutput('build-retry')?.passed === true && $ctx.getNodeOutput('test-retry')?.passed === true && $ctx.getNodeOutput('lint-retry')?.passed === true"
25509
+ "expression": "$ctx.getNodeOutput('retry-build')?.passed === true && $ctx.getNodeOutput('retry-test')?.passed === true && $ctx.getNodeOutput('retry-lint')?.passed === true"
25505
25510
  },
25506
25511
  "position": {
25507
25512
  "x": 620,
@@ -25532,22 +25537,6 @@
25532
25537
  "default"
25533
25538
  ]
25534
25539
  },
25535
- {
25536
- "id": "push-ok-retry",
25537
- "type": "condition.expression",
25538
- "label": "Push OK? (Retry)",
25539
- "config": {
25540
- "expression": "$ctx.getNodeOutput('push-branch-retry')?.pushed === true"
25541
- },
25542
- "position": {
25543
- "x": 450,
25544
- "y": 1825
25545
- },
25546
- "outputs": [
25547
- "yes",
25548
- "no"
25549
- ]
25550
- },
25551
25540
  {
25552
25541
  "id": "create-pr-retry",
25553
25542
  "type": "action.create_pr",
@@ -25571,15 +25560,15 @@
25571
25560
  ]
25572
25561
  },
25573
25562
  {
25574
- "id": "pr-created-retry",
25563
+ "id": "retry-pr-ok",
25575
25564
  "type": "condition.expression",
25576
- "label": "Handoff Recorded (Retry Path)?",
25565
+ "label": "PR Created?",
25577
25566
  "config": {
25578
- "expression": "Boolean($ctx.getNodeOutput('create-pr-retry')?.prNumber || $ctx.getNodeOutput('create-pr-retry')?.prUrl)"
25567
+ "expression": "Boolean($ctx.getNodeOutput($edge.source)?.prNumber || $ctx.getNodeOutput($edge.source)?.prUrl)"
25579
25568
  },
25580
25569
  "position": {
25581
- "x": 450,
25582
- "y": 1890
25570
+ "x": 400,
25571
+ "y": 0
25583
25572
  },
25584
25573
  "outputs": [
25585
25574
  "yes",
@@ -25587,17 +25576,39 @@
25587
25576
  ]
25588
25577
  },
25589
25578
  {
25590
- "id": "set-inreview-retry",
25579
+ "id": "retry-set-inreview",
25591
25580
  "type": "action.update_task_status",
25592
- "label": "Set In-Review (Retry)",
25581
+ "label": "Set In-Review",
25593
25582
  "config": {
25594
25583
  "taskId": "{{taskId}}",
25595
25584
  "status": "inreview",
25596
25585
  "taskTitle": "{{taskTitle}}"
25597
25586
  },
25598
25587
  "position": {
25599
- "x": 360,
25600
- "y": 1980
25588
+ "x": 300,
25589
+ "y": 130
25590
+ },
25591
+ "outputs": [
25592
+ "default"
25593
+ ]
25594
+ },
25595
+ {
25596
+ "id": "retry-handoff-progressor",
25597
+ "type": "action.execute_workflow",
25598
+ "label": "Handoff PR Progressor",
25599
+ "config": {
25600
+ "workflowId": "template-bosun-pr-progressor",
25601
+ "mode": "dispatch",
25602
+ "input": {
25603
+ "taskId": "{{taskId}}",
25604
+ "taskTitle": "{{taskTitle}}",
25605
+ "branch": "{{branch}}",
25606
+ "baseBranch": "{{baseBranch}}"
25607
+ }
25608
+ },
25609
+ "position": {
25610
+ "x": 300,
25611
+ "y": 260
25601
25612
  },
25602
25613
  "outputs": [
25603
25614
  "default"
@@ -25670,26 +25681,26 @@
25670
25681
  "sourcePort": "default"
25671
25682
  },
25672
25683
  {
25673
- "id": "implement->build",
25684
+ "id": "implement->main-build",
25674
25685
  "source": "implement",
25675
- "target": "build",
25686
+ "target": "main-build",
25676
25687
  "sourcePort": "default"
25677
25688
  },
25678
25689
  {
25679
- "id": "build->test-final",
25680
- "source": "build",
25681
- "target": "test-final",
25690
+ "id": "main-build->main-test",
25691
+ "source": "main-build",
25692
+ "target": "main-test",
25682
25693
  "sourcePort": "default"
25683
25694
  },
25684
25695
  {
25685
- "id": "test-final->lint",
25686
- "source": "test-final",
25687
- "target": "lint",
25696
+ "id": "main-test->main-lint",
25697
+ "source": "main-test",
25698
+ "target": "main-lint",
25688
25699
  "sourcePort": "default"
25689
25700
  },
25690
25701
  {
25691
- "id": "lint->all-passed",
25692
- "source": "lint",
25702
+ "id": "main-lint->all-passed",
25703
+ "source": "main-lint",
25693
25704
  "target": "all-passed",
25694
25705
  "sourcePort": "default"
25695
25706
  },
@@ -25708,78 +25719,69 @@
25708
25719
  "condition": "$output?.result !== true"
25709
25720
  },
25710
25721
  {
25711
- "id": "set-validation-summary->auto-fix",
25712
- "source": "set-validation-summary",
25713
- "target": "auto-fix",
25714
- "sourcePort": "default"
25715
- },
25716
- {
25717
- "id": "push-branch->push-ok",
25722
+ "id": "push-branch->create-pr",
25718
25723
  "source": "push-branch",
25719
- "target": "push-ok",
25724
+ "target": "create-pr",
25720
25725
  "sourcePort": "default"
25721
25726
  },
25722
25727
  {
25723
- "id": "push-ok->create-pr",
25724
- "source": "push-ok",
25725
- "target": "create-pr",
25726
- "sourcePort": "yes",
25727
- "condition": "$output?.result === true"
25728
+ "id": "create-pr->main-pr-ok",
25729
+ "source": "create-pr",
25730
+ "target": "main-pr-ok",
25731
+ "sourcePort": "default"
25728
25732
  },
25729
25733
  {
25730
- "id": "push-ok->notify-pr-failed",
25731
- "source": "push-ok",
25732
- "target": "notify-pr-failed",
25733
- "sourcePort": "no",
25734
- "condition": "$output?.result !== true"
25734
+ "id": "main-pr-ok->main-set-inreview",
25735
+ "source": "main-pr-ok",
25736
+ "target": "main-set-inreview",
25737
+ "sourcePort": "yes"
25735
25738
  },
25736
25739
  {
25737
- "id": "create-pr->pr-created",
25738
- "source": "create-pr",
25739
- "target": "pr-created",
25740
+ "id": "main-set-inreview->main-handoff-progressor",
25741
+ "source": "main-set-inreview",
25742
+ "target": "main-handoff-progressor",
25740
25743
  "sourcePort": "default"
25741
25744
  },
25742
25745
  {
25743
- "id": "pr-created->set-inreview",
25744
- "source": "pr-created",
25745
- "target": "set-inreview",
25746
- "sourcePort": "yes",
25747
- "condition": "$output?.result === true"
25748
- },
25749
- {
25750
- "id": "set-inreview->notify-done",
25751
- "source": "set-inreview",
25746
+ "id": "main-handoff-progressor->notify-done",
25747
+ "source": "main-handoff-progressor",
25752
25748
  "target": "notify-done",
25753
25749
  "sourcePort": "default"
25754
25750
  },
25755
25751
  {
25756
- "id": "pr-created->notify-pr-failed",
25757
- "source": "pr-created",
25752
+ "id": "main-pr-ok->notify-pr-failed",
25753
+ "source": "main-pr-ok",
25758
25754
  "target": "notify-pr-failed",
25759
25755
  "sourcePort": "no",
25760
25756
  "condition": "$output?.result !== true"
25761
25757
  },
25762
25758
  {
25763
- "id": "auto-fix->build-retry",
25759
+ "id": "set-validation-summary->auto-fix",
25760
+ "source": "set-validation-summary",
25761
+ "target": "auto-fix",
25762
+ "sourcePort": "default"
25763
+ },
25764
+ {
25765
+ "id": "auto-fix->retry-build",
25764
25766
  "source": "auto-fix",
25765
- "target": "build-retry",
25767
+ "target": "retry-build",
25766
25768
  "sourcePort": "default"
25767
25769
  },
25768
25770
  {
25769
- "id": "build-retry->test-retry",
25770
- "source": "build-retry",
25771
- "target": "test-retry",
25771
+ "id": "retry-build->retry-test",
25772
+ "source": "retry-build",
25773
+ "target": "retry-test",
25772
25774
  "sourcePort": "default"
25773
25775
  },
25774
25776
  {
25775
- "id": "test-retry->lint-retry",
25776
- "source": "test-retry",
25777
- "target": "lint-retry",
25777
+ "id": "retry-test->retry-lint",
25778
+ "source": "retry-test",
25779
+ "target": "retry-lint",
25778
25780
  "sourcePort": "default"
25779
25781
  },
25780
25782
  {
25781
- "id": "lint-retry->retry-passed",
25782
- "source": "lint-retry",
25783
+ "id": "retry-lint->retry-passed",
25784
+ "source": "retry-lint",
25783
25785
  "target": "retry-passed",
25784
25786
  "sourcePort": "default"
25785
25787
  },
@@ -25798,47 +25800,38 @@
25798
25800
  "condition": "$output?.result !== true"
25799
25801
  },
25800
25802
  {
25801
- "id": "push-branch-retry->push-ok-retry",
25803
+ "id": "push-branch-retry->create-pr-retry",
25802
25804
  "source": "push-branch-retry",
25803
- "target": "push-ok-retry",
25805
+ "target": "create-pr-retry",
25804
25806
  "sourcePort": "default"
25805
25807
  },
25806
25808
  {
25807
- "id": "push-ok-retry->create-pr-retry",
25808
- "source": "push-ok-retry",
25809
- "target": "create-pr-retry",
25810
- "sourcePort": "yes",
25811
- "condition": "$output?.result === true"
25809
+ "id": "create-pr-retry->retry-pr-ok",
25810
+ "source": "create-pr-retry",
25811
+ "target": "retry-pr-ok",
25812
+ "sourcePort": "default"
25812
25813
  },
25813
25814
  {
25814
- "id": "push-ok-retry->notify-pr-failed-retry",
25815
- "source": "push-ok-retry",
25816
- "target": "notify-pr-failed-retry",
25817
- "sourcePort": "no",
25818
- "condition": "$output?.result !== true"
25815
+ "id": "retry-pr-ok->retry-set-inreview",
25816
+ "source": "retry-pr-ok",
25817
+ "target": "retry-set-inreview",
25818
+ "sourcePort": "yes"
25819
25819
  },
25820
25820
  {
25821
- "id": "create-pr-retry->pr-created-retry",
25822
- "source": "create-pr-retry",
25823
- "target": "pr-created-retry",
25821
+ "id": "retry-set-inreview->retry-handoff-progressor",
25822
+ "source": "retry-set-inreview",
25823
+ "target": "retry-handoff-progressor",
25824
25824
  "sourcePort": "default"
25825
25825
  },
25826
25826
  {
25827
- "id": "pr-created-retry->set-inreview-retry",
25828
- "source": "pr-created-retry",
25829
- "target": "set-inreview-retry",
25830
- "sourcePort": "yes",
25831
- "condition": "$output?.result === true"
25832
- },
25833
- {
25834
- "id": "set-inreview-retry->notify-done-retry",
25835
- "source": "set-inreview-retry",
25827
+ "id": "retry-handoff-progressor->notify-done-retry",
25828
+ "source": "retry-handoff-progressor",
25836
25829
  "target": "notify-done-retry",
25837
25830
  "sourcePort": "default"
25838
25831
  },
25839
25832
  {
25840
- "id": "pr-created-retry->notify-pr-failed-retry",
25841
- "source": "pr-created-retry",
25833
+ "id": "retry-pr-ok->notify-pr-failed-retry",
25834
+ "source": "retry-pr-ok",
25842
25835
  "target": "notify-pr-failed-retry",
25843
25836
  "sourcePort": "no",
25844
25837
  "condition": "$output?.result !== true"
@@ -34356,7 +34349,7 @@
34356
34349
  },
34357
34350
  "position": {
34358
34351
  "x": 400,
34359
- "y": 380
34352
+ "y": 340
34360
34353
  },
34361
34354
  "outputs": [
34362
34355
  "default"
@@ -34381,7 +34374,7 @@
34381
34374
  },
34382
34375
  "position": {
34383
34376
  "x": 400,
34384
- "y": 560
34377
+ "y": 500
34385
34378
  },
34386
34379
  "outputs": [
34387
34380
  "default"
@@ -34396,7 +34389,7 @@
34396
34389
  },
34397
34390
  "position": {
34398
34391
  "x": 400,
34399
- "y": 720
34392
+ "y": 660
34400
34393
  },
34401
34394
  "outputs": [
34402
34395
  "default"
@@ -34715,7 +34708,7 @@
34715
34708
  },
34716
34709
  "position": {
34717
34710
  "x": 400,
34718
- "y": 380
34711
+ "y": 340
34719
34712
  },
34720
34713
  "outputs": [
34721
34714
  "default"
@@ -34740,7 +34733,7 @@
34740
34733
  },
34741
34734
  "position": {
34742
34735
  "x": 400,
34743
- "y": 560
34736
+ "y": 500
34744
34737
  },
34745
34738
  "outputs": [
34746
34739
  "default"
@@ -34755,7 +34748,7 @@
34755
34748
  },
34756
34749
  "position": {
34757
34750
  "x": 400,
34758
- "y": 720
34751
+ "y": 660
34759
34752
  },
34760
34753
  "outputs": [
34761
34754
  "default"
@@ -35191,7 +35184,7 @@
35191
35184
  },
35192
35185
  "position": {
35193
35186
  "x": 400,
35194
- "y": 380
35187
+ "y": 340
35195
35188
  },
35196
35189
  "outputs": [
35197
35190
  "default"
@@ -35216,7 +35209,7 @@
35216
35209
  },
35217
35210
  "position": {
35218
35211
  "x": 400,
35219
- "y": 560
35212
+ "y": 500
35220
35213
  },
35221
35214
  "outputs": [
35222
35215
  "default"
@@ -35231,7 +35224,7 @@
35231
35224
  },
35232
35225
  "position": {
35233
35226
  "x": 400,
35234
- "y": 720
35227
+ "y": 660
35235
35228
  },
35236
35229
  "outputs": [
35237
35230
  "default"
@@ -35354,7 +35347,7 @@
35354
35347
  },
35355
35348
  "position": {
35356
35349
  "x": 400,
35357
- "y": 380
35350
+ "y": 340
35358
35351
  },
35359
35352
  "outputs": [
35360
35353
  "default"
@@ -35379,7 +35372,7 @@
35379
35372
  },
35380
35373
  "position": {
35381
35374
  "x": 400,
35382
- "y": 560
35375
+ "y": 500
35383
35376
  },
35384
35377
  "outputs": [
35385
35378
  "default"
@@ -35394,7 +35387,7 @@
35394
35387
  },
35395
35388
  "position": {
35396
35389
  "x": 400,
35397
- "y": 720
35390
+ "y": 660
35398
35391
  },
35399
35392
  "outputs": [
35400
35393
  "default"
@@ -35693,7 +35686,7 @@
35693
35686
  },
35694
35687
  "position": {
35695
35688
  "x": 400,
35696
- "y": 380
35689
+ "y": 340
35697
35690
  },
35698
35691
  "outputs": [
35699
35692
  "default"
@@ -35718,7 +35711,7 @@
35718
35711
  },
35719
35712
  "position": {
35720
35713
  "x": 400,
35721
- "y": 560
35714
+ "y": 500
35722
35715
  },
35723
35716
  "outputs": [
35724
35717
  "default"
@@ -35733,7 +35726,7 @@
35733
35726
  },
35734
35727
  "position": {
35735
35728
  "x": 400,
35736
- "y": 720
35729
+ "y": 660
35737
35730
  },
35738
35731
  "outputs": [
35739
35732
  "default"
@@ -38493,6 +38486,7 @@
38493
38486
  "timeoutMs": "{{taskTimeoutMs}}",
38494
38487
  "maxRetries": "{{maxRetries}}",
38495
38488
  "maxContinues": "{{maxContinues}}",
38489
+ "resolveMode": "library",
38496
38490
  "failOnError": false
38497
38491
  },
38498
38492
  "position": {
@@ -38517,6 +38511,7 @@
38517
38511
  "timeoutMs": "{{taskTimeoutMs}}",
38518
38512
  "maxRetries": "{{maxRetries}}",
38519
38513
  "maxContinues": "{{maxContinues}}",
38514
+ "resolveMode": "library",
38520
38515
  "failOnError": false
38521
38516
  },
38522
38517
  "position": {
@@ -38541,6 +38536,7 @@
38541
38536
  "timeoutMs": "{{taskTimeoutMs}}",
38542
38537
  "maxRetries": "{{maxRetries}}",
38543
38538
  "maxContinues": "{{maxContinues}}",
38539
+ "resolveMode": "library",
38544
38540
  "failOnError": false
38545
38541
  },
38546
38542
  "position": {
@@ -39828,6 +39824,8 @@
39828
39824
  "cwd": "{{worktreePath}}",
39829
39825
  "timeoutMs": "{{taskTimeoutMs}}",
39830
39826
  "maxRetries": "{{maxRetries}}",
39827
+ "maxContinues": "{{maxContinues}}",
39828
+ "resolveMode": "library",
39831
39829
  "failOnError": false
39832
39830
  },
39833
39831
  "position": {